@madgex/design-system-ce 5.1.2 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js CHANGED
@@ -1,7 +1,14 @@
1
1
  module.exports = {
2
- extends: ['plugin:vue/essential', '@vue/prettier'],
3
- rules: {},
4
2
  env: {
5
3
  browser: true,
6
4
  },
5
+ extends: ['eslint:recommended', 'plugin:vue/vue3-recommended', 'prettier'],
6
+ rules: {
7
+ 'vue/no-v-html': 0,
8
+ 'no-debugger': 0,
9
+ 'vue/singleline-html-element-content-newline': 'off',
10
+ 'vue/multiline-html-element-content-newline': 'off',
11
+ 'vue/html-indent': 'off',
12
+ 'vue/html-self-closing': ['error', { html: { void: 'any' } }],
13
+ },
7
14
  };
package/CHANGELOG.md CHANGED
@@ -3,6 +3,55 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [5.3.0](https://github.com/projects/MDS/repos/mds-branding/compare/diff?targetBranch=refs/tags/@madgex/design-system-ce@5.2.0&sourceBranch=refs/tags/@madgex/design-system-ce@5.3.0) (2022-11-25)
7
+
8
+
9
+ ### Features
10
+
11
+ * add link for text editor ([288ad29](https://github.com/projects/MDS/repos/mds-branding/commits/288ad2988076cebddeebab420e3e5438a5d2f818))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * automatically apply protocol to textEditor link ([fee5fc7](https://github.com/projects/MDS/repos/mds-branding/commits/fee5fc760d7fc3e01f3bff56022b5fd942cb6d44))
17
+ * remove link editor popover/button ([db6c5f8](https://github.com/projects/MDS/repos/mds-branding/commits/db6c5f8bb64d2a2d44a5ce13b7f4d943664a7aad))
18
+ * remove link editor popover/button ([0b263f8](https://github.com/projects/MDS/repos/mds-branding/commits/0b263f824e7deebd74655aa990e5c1c9e771b831))
19
+ * remove unused code ([b29e960](https://github.com/projects/MDS/repos/mds-branding/commits/b29e96030d7f53242d65335022a2b0af49fff733))
20
+ * texteditor anchor validation ([5e40b46](https://github.com/projects/MDS/repos/mds-branding/commits/5e40b462974a26473d36c2b033de4d1eb606c368))
21
+ * texteditor applyMissingProtocol mailto ([af94428](https://github.com/projects/MDS/repos/mds-branding/commits/af944282bc8f2aa239031a09310112cf0d153924))
22
+ * textEditor link enabled if valid selection ([654dc8d](https://github.com/projects/MDS/repos/mds-branding/commits/654dc8d61d1846e684d3c3cd3b88b84d011cbc87))
23
+ * textEditor link popover spacing ([2236c64](https://github.com/projects/MDS/repos/mds-branding/commits/2236c6442261f05dee13ab82319bc503d9262832))
24
+ * textEditor links always underline ([d7851e3](https://github.com/projects/MDS/repos/mds-branding/commits/d7851e355b1e8dcbbf4c75c6ad8eac8b6c60ed44))
25
+ * textEditor linkUrl i18n, for/id couple ([2cdad01](https://github.com/projects/MDS/repos/mds-branding/commits/2cdad0129cbc5354441ebf1830a7324559386533))
26
+ * textEditor remove auto-focus on open link editor ([1c42419](https://github.com/projects/MDS/repos/mds-branding/commits/1c42419ced20efe5bb0cb1e80221eab509b29645))
27
+ * textEditori18n ([6bf5d03](https://github.com/projects/MDS/repos/mds-branding/commits/6bf5d0352ce1ec77908c03a9e597d8ddbc7f3ba9))
28
+ * textEdtorPopover ([13c39be](https://github.com/projects/MDS/repos/mds-branding/commits/13c39bee9c0d676fa87bf815c64873b92ba9ee7e))
29
+ * tweak text editor link ([32e3992](https://github.com/projects/MDS/repos/mds-branding/commits/32e399236782169c3075a4a2ebc853c6a690f966))
30
+
31
+
32
+
33
+ ## [5.2.0](https://github.com/projects/MDS/repos/mds-branding/compare/diff?targetBranch=refs/tags/@madgex/design-system-ce@5.1.2&sourceBranch=refs/tags/@madgex/design-system-ce@5.2.0) (2022-08-18)
34
+
35
+
36
+ ### Features
37
+
38
+ * add text editor (wip) ([1d424b3](https://github.com/projects/MDS/repos/mds-branding/commits/1d424b399f1ce71a1db99b644dc72aa650eee1f0))
39
+ * add text editor (wip) ([804cabe](https://github.com/projects/MDS/repos/mds-branding/commits/804cabee541b65e8943a3e29b9c6108313a3400a))
40
+ * manage tabindex in toolbar, add examples and documentation ([74e1442](https://github.com/projects/MDS/repos/mds-branding/commits/74e1442437e9f27580b9f3f56d2c37a3874e98f6))
41
+ * refactoring toolbar, adding keyboard interaction and fixing icons ([b4c5121](https://github.com/projects/MDS/repos/mds-branding/commits/b4c5121ddfd1d1323ac3fb3d87ca52b85470768a))
42
+
43
+
44
+ ### Bug Fixes
45
+
46
+ * adding reusable icon vue component + svgs for editor menu ([f8bfaae](https://github.com/projects/MDS/repos/mds-branding/commits/f8bfaae311d607ce1ef15cf65f857b442d1fcbc5))
47
+ * export each custom element separately ([da9f94d](https://github.com/projects/MDS/repos/mds-branding/commits/da9f94de94c6612c8a645855f9e32a6abeee9194))
48
+ * move aria-pressed to toolbar instead of button ([5114f9d](https://github.com/projects/MDS/repos/mds-branding/commits/5114f9dd829ca10a5dbfd88a5784163575c2ac12))
49
+ * removed the disabled attribute to only aria-disabled to avoid focus issue ([2076227](https://github.com/projects/MDS/repos/mds-branding/commits/2076227c8a98c5eb359320271ccf5dc6e6a5bded))
50
+ * trying to separate the text editor from the main js ([db089b8](https://github.com/projects/MDS/repos/mds-branding/commits/db089b8779bbbf0d4a7c1bfbb2de6c9fc20d947d))
51
+ * update filenames ([7ad354f](https://github.com/projects/MDS/repos/mds-branding/commits/7ad354fb77629e738ca55a00f71dc87f365382ae))
52
+
53
+
54
+
6
55
  ## [5.1.2](https://github.com/projects/MDS/repos/mds-branding/compare/diff?targetBranch=refs/tags/@madgex/design-system-ce@5.1.1&sourceBranch=refs/tags/@madgex/design-system-ce@5.1.2) (2022-07-12)
7
56
 
8
57
  **Note:** Version bump only for package @madgex/design-system-ce
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <span>
3
+ <svg aria-hidden="true" focusable="false" class="mds-icon" :class="`mds-icon--${iconName} ${classes}`">
4
+ <use :href="`${iconPath}#icon-${iconName}`" />
5
+ </svg>
6
+ <span v-if="visuallyHiddenLabel" class="mds-visually-hidden">{{ visuallyHiddenLabel }}</span>
7
+ </span>
8
+ </template>
9
+
10
+ <script>
11
+ export default {
12
+ name: 'MdsIcon',
13
+ props: {
14
+ iconName: {
15
+ type: String,
16
+ default: '',
17
+ },
18
+ classes: {
19
+ type: String,
20
+ default: '',
21
+ },
22
+ visuallyHiddenLabel: {
23
+ type: String,
24
+ default: '',
25
+ },
26
+ },
27
+ inject: ['iconPath'],
28
+ };
29
+ </script>
@@ -0,0 +1,71 @@
1
+ <template>
2
+ <div>
3
+ <TextEditorContent v-model="content" />
4
+ <input type="hidden" :name="editorid" :value="content" />
5
+ </div>
6
+ </template>
7
+
8
+ <script>
9
+ import TextEditorContent from './TextEditorContent.vue';
10
+
11
+ export default {
12
+ name: 'TextEditor',
13
+ components: {
14
+ TextEditorContent,
15
+ },
16
+ provide() {
17
+ return {
18
+ iconPath: this.iconpath,
19
+ id: this.editorid,
20
+ customMenuButtons: this.customMenuButtons,
21
+ i18nText: this.i18nText,
22
+ };
23
+ },
24
+ props: {
25
+ editorid: {
26
+ type: String,
27
+ required: true,
28
+ },
29
+ iconpath: {
30
+ type: String,
31
+ default: '/assets/icons.svg',
32
+ },
33
+ menuButtons: {
34
+ type: String,
35
+ default: '',
36
+ },
37
+ i18n: {
38
+ type: String,
39
+ default: '',
40
+ },
41
+ value: {
42
+ type: String,
43
+ default: '',
44
+ },
45
+ },
46
+ data() {
47
+ return {
48
+ content: this.value,
49
+ };
50
+ },
51
+ computed: {
52
+ customMenuButtons() {
53
+ return this.menuButtons ? JSON.parse(this.menuButtons) : null;
54
+ },
55
+ i18nText() {
56
+ const parsedI18n = this.i18n ? JSON.parse(this.i18n) : {};
57
+ return {
58
+ toolbarLabel: 'Text formatting',
59
+ addLink: 'Add link',
60
+ removeLink: 'Remove link',
61
+ visuallyHiddenLinkUrlLabel: 'Link url',
62
+ ...parsedI18n,
63
+ };
64
+ },
65
+ },
66
+ mounted() {
67
+ const fallback = document.querySelector(`#text-editor-fallback-${this.editorid}`);
68
+ if (fallback) fallback.remove();
69
+ },
70
+ };
71
+ </script>
@@ -0,0 +1,60 @@
1
+ <template>
2
+ <button
3
+ type="button"
4
+ :title="label"
5
+ :aria-label="label"
6
+ class="mds-text-editor__button"
7
+ :class="{ 'mds-text-editor__button--active': isActive }"
8
+ :tabindex="tabindex"
9
+ :aria-disabled="disabled"
10
+ :aria-pressed="ariaPressed"
11
+ @focus="$emit('updateTabIndex', id)"
12
+ >
13
+ <MdsIcon :icon-name="iconName" />
14
+ </button>
15
+ </template>
16
+
17
+ <script>
18
+ import MdsIcon from '../internals/MdsIcon.vue';
19
+ export default {
20
+ name: 'TextEditorButton',
21
+ components: {
22
+ MdsIcon,
23
+ },
24
+ emits: ['updateTabIndex'],
25
+ props: {
26
+ id: {
27
+ type: String,
28
+ required: true,
29
+ },
30
+ label: {
31
+ type: String,
32
+ default: '',
33
+ },
34
+ iconName: {
35
+ type: String,
36
+ default: '',
37
+ },
38
+ isActive: {
39
+ type: Boolean,
40
+ default: false,
41
+ },
42
+ initialTabindex: {
43
+ type: Number,
44
+ default: -1,
45
+ },
46
+ ariaPressed: {
47
+ type: Boolean,
48
+ default: false,
49
+ },
50
+ disabled: {
51
+ type: Boolean,
52
+ },
53
+ },
54
+ data() {
55
+ return {
56
+ tabindex: this.initialTabindex,
57
+ };
58
+ },
59
+ };
60
+ </script>
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <div class="mds-text-editor" v-if="editor">
3
+ <TextEditorToolbar :editor="editor" />
4
+ <EditorContent :editor="editor" />
5
+ </div>
6
+ </template>
7
+
8
+ <script>
9
+ import StarterKit from '@tiptap/starter-kit';
10
+ import { Editor, EditorContent } from '@tiptap/vue-3';
11
+ import Link from '@tiptap/extension-link';
12
+ import TextEditorToolbar from './TextEditorToolbar.vue';
13
+
14
+ export default {
15
+ name: 'TextEditorContent',
16
+ components: {
17
+ EditorContent,
18
+ TextEditorToolbar,
19
+ },
20
+ props: {
21
+ modelValue: {
22
+ type: String,
23
+ default: '',
24
+ },
25
+ },
26
+ inject: ['id'],
27
+
28
+ emits: ['update:modelValue'],
29
+
30
+ data() {
31
+ return {
32
+ editor: null,
33
+ };
34
+ },
35
+
36
+ watch: {
37
+ modelValue(value) {
38
+ const isSame = this.editor.getHTML() === value;
39
+
40
+ if (isSame) {
41
+ return;
42
+ }
43
+
44
+ this.editor.commands.setContent(value, false);
45
+ },
46
+ },
47
+
48
+ mounted() {
49
+ this.editor = new Editor({
50
+ extensions: [
51
+ StarterKit,
52
+ Link.configure({
53
+ protocols: ['http', 'https', 'mailto'], // automatically create links of text with these protocols
54
+ openOnClick: false,
55
+ }),
56
+ ],
57
+ editorProps: {
58
+ attributes: {
59
+ class: 'mds-text-editor__content mds-edited-text',
60
+ id: this.id,
61
+ role: 'textbox',
62
+ [`aria-labelledby`]: `text-editor-label-${this.id}`,
63
+ },
64
+ },
65
+ content: this.modelValue,
66
+ onUpdate: () => {
67
+ this.$emit('update:modelValue', this.editor.getHTML());
68
+ },
69
+ });
70
+ },
71
+
72
+ beforeUnmount() {
73
+ this.editor.destroy();
74
+ },
75
+ };
76
+ </script>
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <div ref="popperRef" class="mds-popover" :class="{ 'mds-popover--active': visible }" :aria-hidden="!visible">
3
+ <div class="mds-popover__arrow" data-popper-arrow />
4
+ <div class="mds-tooltip__inner">
5
+ <slot />
6
+ </div>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup>
11
+ import { onMounted, onUnmounted, ref, watch } from 'vue';
12
+ import { createPopper } from '@popperjs/core';
13
+ import { isNodeSelection, posToDOMRect } from '@tiptap/core';
14
+
15
+ // eslint-disable-next-line no-undef
16
+ const props = defineProps({
17
+ editor: {
18
+ type: Object,
19
+ required: true,
20
+ },
21
+ id: {
22
+ type: String,
23
+ required: true,
24
+ },
25
+ visible: {
26
+ type: Boolean,
27
+ default: false,
28
+ },
29
+ });
30
+
31
+ // eslint-disable-next-line no-undef
32
+ const emit = defineEmits(['update:visible']);
33
+
34
+ const popperRef = ref(null);
35
+ const instance = ref(undefined);
36
+
37
+ const baseSize = 4;
38
+
39
+ const destroy = () => {
40
+ instance.value?.destroy();
41
+ instance.value = undefined;
42
+ };
43
+
44
+ const virtualElement = {
45
+ getBoundingClientRect: () => {
46
+ const { view, state } = props.editor;
47
+ const { ranges } = state.selection;
48
+ const from = Math.min(...ranges.map((range) => range.$from.pos));
49
+ const to = Math.max(...ranges.map((range) => range.$to.pos));
50
+ if (isNodeSelection(state.selection)) {
51
+ const node = view.nodeDOM(from);
52
+ if (node) return node.getBoundingClientRect();
53
+ }
54
+ return posToDOMRect(view, from, to);
55
+ },
56
+ };
57
+
58
+ const initPopper = () => {
59
+ if (instance.value) destroy();
60
+ if (!popperRef.value) return;
61
+ instance.value = createPopper(virtualElement, popperRef.value, {
62
+ placement: 'bottom',
63
+ strategy: 'fixed',
64
+ modifiers: [
65
+ { name: 'offset', options: { offset: [0, baseSize * 3] } },
66
+ // arrow will always be at least 5px away from the edge
67
+ { name: 'arrow', options: { padding: 5 } },
68
+ ],
69
+ });
70
+ };
71
+
72
+ const hideMouseDown = (event) => {
73
+ if (!popperRef.value?.contains(event.target) && props.visible) {
74
+ console.log('visiblevisible');
75
+
76
+ emit('update:visible', false);
77
+ }
78
+ };
79
+ const hideKeyDown = (event) => {
80
+ if (event.keyCode === 27) {
81
+ emit('update:visible', false);
82
+ }
83
+ };
84
+
85
+ const updateInstance = () => instance.value?.update();
86
+ const onEditorFocus = () => {
87
+ emit('update:visible', false);
88
+ };
89
+ watch(
90
+ () => props.visible,
91
+ (val) => {
92
+ if (val) {
93
+ updateInstance();
94
+ }
95
+ }
96
+ );
97
+
98
+ onMounted(() => {
99
+ initPopper();
100
+ props.editor.on('focus', onEditorFocus);
101
+ props.editor.on('blur', updateInstance);
102
+ document.addEventListener('mousedown', hideMouseDown);
103
+ document.addEventListener('keydown', hideKeyDown);
104
+ });
105
+
106
+ onUnmounted(() => {
107
+ props.editor.off('focus', onEditorFocus);
108
+ props.editor.off('blur', updateInstance);
109
+ document.removeEventListener('mousedown', hideMouseDown);
110
+ document.removeEventListener('keydown', hideKeyDown);
111
+ destroy();
112
+ });
113
+ </script>
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <TextEditorPopover id="link-editor" v-model:visible="visibleModel" :editor="editor">
3
+ <div class="mds-grid">
4
+ <div class="mds-grid-row">
5
+ <span class="mds-grid-col mds-grid-fit-content">
6
+ <label class="mds-visually-hidden" for="link-url">{{ i18nText.visuallyHiddenLinkUrlLabel }}</label>
7
+ <input
8
+ id="link-url"
9
+ ref="linkInput"
10
+ v-model="linkUrlModel"
11
+ class="mds-form-control"
12
+ placeholder="example.com"
13
+ />
14
+ </span>
15
+ <span class="mds-grid-col">
16
+ <button class="mds-button mds-width-full" @click="setLink">{{ i18nText.addLink }}</button>
17
+ <button class="mds-button mds-button--plain mds-width-full" @click="removeLink">
18
+ {{ i18nText.removeLink }}
19
+ </button>
20
+ </span>
21
+ </div>
22
+ </div>
23
+ </TextEditorPopover>
24
+ </template>
25
+
26
+ <script setup>
27
+ import { computed, inject } from 'vue';
28
+ import TextEditorPopover from './TextEditorPopover.vue';
29
+ import { applyMissingProtocol } from './apply-missing-protocol';
30
+ import { nextTick } from 'process';
31
+
32
+ // eslint-disable-next-line no-undef
33
+ const props = defineProps({
34
+ visible: {
35
+ type: Boolean,
36
+ default: false,
37
+ },
38
+ linkUrl: {
39
+ type: String,
40
+ default: '',
41
+ },
42
+ editor: {
43
+ type: Object,
44
+ required: true,
45
+ },
46
+ });
47
+ // eslint-disable-next-line no-undef
48
+ const emit = defineEmits(['update:visible', 'update:linkUrl']);
49
+
50
+ const i18nText = inject('i18nText');
51
+
52
+ const visibleModel = computed({
53
+ get() {
54
+ return props.visible;
55
+ },
56
+ set(val) {
57
+ emit('update:visible', val);
58
+ },
59
+ });
60
+ const linkUrlModel = computed({
61
+ get() {
62
+ return props.linkUrl;
63
+ },
64
+ set(val) {
65
+ emit('update:linkUrl', val);
66
+ },
67
+ });
68
+
69
+ const setLink = () => {
70
+ // cancelled
71
+ if (linkUrlModel.value === null) {
72
+ visibleModel.value = false;
73
+ return;
74
+ }
75
+
76
+ // unset link if empty
77
+ if (linkUrlModel.value?.trim() === '') {
78
+ props.editor.chain().focus().extendMarkRange('link').unsetLink().run();
79
+ visibleModel.value = false;
80
+ return;
81
+ }
82
+ linkUrlModel.value = applyMissingProtocol(linkUrlModel.value);
83
+
84
+ // update link
85
+ nextTick(() => {
86
+ props.editor
87
+ .chain()
88
+ .focus()
89
+ .extendMarkRange('link')
90
+ .setLink({ href: linkUrlModel.value })
91
+ .setTextSelection(0)
92
+ .run();
93
+ visibleModel.value = false;
94
+ });
95
+ };
96
+ const removeLink = () => {
97
+ props.editor.chain().focus().extendMarkRange('link').unsetLink().run();
98
+ linkUrlModel.value = false;
99
+ };
100
+ </script>