@zipify/wysiwyg 1.0.0-dev.3 → 1.0.0-dev.30

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.
Files changed (151) hide show
  1. package/.eslintignore +1 -0
  2. package/.github/dependabot.yaml +1 -0
  3. package/.release-it.json +3 -1
  4. package/.stylelintignore +1 -0
  5. package/README.md +3 -1
  6. package/config/jest/setupTests.js +7 -1
  7. package/config/webpack/example.config.js +2 -0
  8. package/config/webpack/lib.config.js +47 -0
  9. package/config/webpack/loaders/style-loader.js +3 -1
  10. package/config/webpack/loaders/svg-loader.js +1 -1
  11. package/dist/wysiwyg.css +1118 -0
  12. package/dist/wysiwyg.js +43805 -0
  13. package/example/ExampleApp.vue +51 -32
  14. package/example/example.js +26 -0
  15. package/example/pageBlocks.js +31 -0
  16. package/example/presets.js +4 -2
  17. package/lib/Wysiwyg.vue +41 -21
  18. package/lib/assets/icons/alignment-center.svg +3 -0
  19. package/lib/assets/icons/alignment-justify.svg +3 -0
  20. package/lib/assets/icons/alignment-left.svg +3 -0
  21. package/lib/assets/icons/alignment-right.svg +3 -0
  22. package/lib/assets/icons/arrow.svg +3 -0
  23. package/lib/assets/icons/background-color.svg +3 -0
  24. package/lib/assets/icons/case-style.svg +3 -0
  25. package/lib/assets/icons/font-color.svg +5 -0
  26. package/lib/assets/icons/italic.svg +3 -0
  27. package/lib/assets/icons/line-height.svg +3 -0
  28. package/lib/assets/icons/link.svg +3 -0
  29. package/lib/assets/icons/list-circle.svg +3 -0
  30. package/lib/assets/icons/list-decimal.svg +3 -0
  31. package/lib/assets/icons/list-disc.svg +3 -0
  32. package/lib/assets/icons/list-latin.svg +3 -0
  33. package/lib/assets/icons/list-roman.svg +3 -0
  34. package/lib/assets/icons/list-square.svg +3 -0
  35. package/lib/assets/icons/remove-format.svg +3 -0
  36. package/lib/assets/icons/reset-styles.svg +3 -0
  37. package/lib/assets/icons/strike-through.svg +3 -0
  38. package/lib/assets/icons/superscript.svg +3 -0
  39. package/lib/assets/icons/underline.svg +3 -0
  40. package/lib/assets/icons/unlink.svg +3 -0
  41. package/lib/components/base/Button.vue +21 -1
  42. package/lib/components/base/Checkbox.vue +89 -0
  43. package/lib/components/base/FieldLabel.vue +2 -1
  44. package/lib/components/base/Icon.vue +18 -10
  45. package/lib/components/base/Modal.vue +0 -1
  46. package/lib/components/base/TextField.vue +106 -0
  47. package/lib/components/base/__tests__/Icon.test.js +6 -13
  48. package/lib/components/base/__tests__/TextField.test.js +57 -0
  49. package/lib/components/base/__tests__/__snapshots__/TextField.test.js.snap +9 -0
  50. package/lib/components/base/colorPicker/composables/usePickerHotkeys.js +2 -1
  51. package/lib/components/base/composables/index.js +1 -0
  52. package/lib/components/base/composables/useValidator.js +19 -0
  53. package/lib/components/base/dropdown/Dropdown.vue +15 -3
  54. package/lib/components/base/dropdown/DropdownActivator.vue +19 -3
  55. package/lib/components/base/index.js +3 -1
  56. package/lib/components/toolbar/Toolbar.vue +49 -9
  57. package/lib/components/toolbar/ToolbarFull.vue +10 -2
  58. package/lib/components/toolbar/__tests__/Toolbar.test.js +6 -0
  59. package/lib/components/toolbar/controls/FontSizeControl.vue +7 -0
  60. package/lib/components/toolbar/controls/ListControl.vue +1 -5
  61. package/lib/components/toolbar/controls/UnderlineControl.vue +2 -2
  62. package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +4 -0
  63. package/lib/components/toolbar/controls/index.js +1 -0
  64. package/lib/components/toolbar/controls/link/LinkControl.vue +152 -0
  65. package/lib/components/toolbar/controls/link/LinkControlApply.vue +35 -0
  66. package/lib/components/toolbar/controls/link/LinkControlHeader.vue +67 -0
  67. package/lib/components/toolbar/controls/link/composables/index.js +1 -0
  68. package/lib/components/toolbar/controls/link/composables/useLink.js +61 -0
  69. package/lib/components/toolbar/controls/link/destination/LinkControlDestination.vue +103 -0
  70. package/lib/components/toolbar/controls/link/destination/LinkControlPageBlock.vue +54 -0
  71. package/lib/components/toolbar/controls/link/destination/LinkControlUrl.vue +52 -0
  72. package/lib/components/toolbar/controls/link/destination/index.js +1 -0
  73. package/lib/components/toolbar/controls/link/index.js +1 -0
  74. package/lib/composables/__tests__/useEditor.test.js +14 -5
  75. package/lib/composables/useEditor.js +13 -8
  76. package/lib/composables/useToolbar.js +14 -29
  77. package/lib/directives/outClick.js +20 -4
  78. package/lib/enums/LinkDestinations.js +4 -0
  79. package/lib/enums/LinkTargets.js +4 -0
  80. package/lib/enums/TextSettings.js +3 -1
  81. package/lib/enums/index.js +2 -0
  82. package/lib/extensions/Alignment.js +21 -3
  83. package/lib/extensions/BackgroundColor.js +16 -1
  84. package/lib/extensions/FontColor.js +16 -1
  85. package/lib/extensions/FontFamily.js +26 -2
  86. package/lib/extensions/FontSize.js +28 -3
  87. package/lib/extensions/FontStyle.js +23 -2
  88. package/lib/extensions/FontWeight.js +33 -1
  89. package/lib/extensions/LineHeight.js +29 -3
  90. package/lib/extensions/Link.js +101 -0
  91. package/lib/extensions/StylePreset.js +36 -6
  92. package/lib/extensions/TextDecoration.js +29 -3
  93. package/lib/extensions/__tests__/Alignment.test.js +30 -3
  94. package/lib/extensions/__tests__/BackgroundColor.test.js +38 -3
  95. package/lib/extensions/__tests__/CaseStyle.test.js +4 -3
  96. package/lib/extensions/__tests__/FontColor.test.js +38 -3
  97. package/lib/extensions/__tests__/FontFamily.test.js +59 -5
  98. package/lib/extensions/__tests__/FontSize.test.js +38 -3
  99. package/lib/extensions/__tests__/FontStyle.test.js +46 -3
  100. package/lib/extensions/__tests__/FontWeight.test.js +66 -3
  101. package/lib/extensions/__tests__/LineHeight.test.js +49 -3
  102. package/lib/extensions/__tests__/StylePreset.test.js +143 -4
  103. package/lib/extensions/__tests__/TextDecoration.test.js +87 -3
  104. package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +70 -2
  105. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +121 -1
  106. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +109 -1
  107. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +179 -1
  108. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +132 -2
  109. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +142 -1
  110. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +179 -1
  111. package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +118 -2
  112. package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +171 -2
  113. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +300 -3
  114. package/lib/extensions/core/CopyPasteProcessor.js +10 -0
  115. package/lib/extensions/core/TextProcessor.js +10 -0
  116. package/lib/extensions/core/__tests__/NodeProcessor.test.js +4 -3
  117. package/lib/extensions/core/__tests__/SelectionProcessor.test.js +9 -6
  118. package/lib/extensions/core/__tests__/TextProcessor.test.js +139 -10
  119. package/lib/extensions/core/__tests__/__snapshots__/TextProcessor.test.js.snap +26 -0
  120. package/lib/extensions/core/index.js +11 -2
  121. package/lib/extensions/core/plugins/PastePlugin.js +48 -0
  122. package/lib/extensions/core/plugins/ProseMirrorPlugin.js +20 -0
  123. package/lib/extensions/core/plugins/index.js +1 -0
  124. package/lib/extensions/index.js +41 -33
  125. package/lib/extensions/list/List.js +34 -0
  126. package/lib/extensions/list/__tests__/List.test.js +115 -5
  127. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +481 -0
  128. package/lib/index.js +4 -1
  129. package/lib/injectionTokens.js +2 -1
  130. package/lib/services/ContentNormalizer.js +157 -0
  131. package/lib/services/ContextWidnow.js +23 -0
  132. package/lib/services/Storage.js +1 -13
  133. package/lib/services/__tests__/ContentNormalizer.test.js +74 -0
  134. package/lib/services/__tests__/FavoriteColors.test.js +20 -0
  135. package/lib/services/__tests__/JsonSerializer.test.js +23 -0
  136. package/lib/services/__tests__/Storage.test.js +79 -0
  137. package/lib/services/index.js +2 -0
  138. package/lib/styles/content.css +96 -9
  139. package/lib/styles/helpers/offsets.css +16 -0
  140. package/lib/styles/variables.css +6 -0
  141. package/lib/utils/__tests__/__snapshots__/renderInlineSetting.test.js.snap +4 -4
  142. package/lib/utils/__tests__/convertColor.test.js +19 -0
  143. package/lib/utils/__tests__/createKeyboardShortcut.test.js +25 -0
  144. package/lib/utils/__tests__/renderInlineSetting.test.js +26 -0
  145. package/lib/utils/convertColor.js +7 -0
  146. package/lib/utils/importIcon.js +12 -0
  147. package/lib/utils/index.js +2 -0
  148. package/lib/utils/renderInlineSetting.js +2 -2
  149. package/package.json +18 -14
  150. package/lib/assets/icons.svg +0 -69
  151. package/lib/composables/__tests__/useToolbar.test.js +0 -56
@@ -1,5 +1,21 @@
1
+ import { ContextWindow } from '../services';
2
+
1
3
  const dataStorage = new WeakMap();
2
4
 
5
+ function attachListener(onClick) {
6
+ if (ContextWindow.window !== window) {
7
+ window.document.addEventListener('click', onClick);
8
+ }
9
+ ContextWindow.document.addEventListener('click', onClick);
10
+ }
11
+
12
+ function removeListener(onClick) {
13
+ if (ContextWindow.window !== window) {
14
+ window.document.removeEventListener('click', onClick);
15
+ }
16
+ ContextWindow.document.removeEventListener('click', onClick);
17
+ }
18
+
3
19
  export const outClick = {
4
20
  bind(el, { value }) {
5
21
  const onOutClick = value.onOutClick || value;
@@ -14,7 +30,7 @@ export const outClick = {
14
30
  dataStorage.set(el, { callback: onClick, isEnabled });
15
31
 
16
32
  if (isEnabled) {
17
- setTimeout(() => document.addEventListener('click', onClick));
33
+ setTimeout(() => attachListener(onClick));
18
34
  }
19
35
  },
20
36
 
@@ -25,13 +41,13 @@ export const outClick = {
25
41
  if (isEnabled === data.isEnabled) return;
26
42
 
27
43
  isEnabled
28
- ? document.addEventListener('click', data.callback)
29
- : document.removeEventListener('click', data.callback);
44
+ ? attachListener(data.callback)
45
+ : removeListener(data.callback);
30
46
 
31
47
  dataStorage.set(el, { callback: data.callback, isEnabled });
32
48
  },
33
49
 
34
50
  unbind(el) {
35
- document.removeEventListener('click', dataStorage.get(el).callback);
51
+ removeListener(dataStorage.get(el).callback);
36
52
  }
37
53
  };
@@ -0,0 +1,4 @@
1
+ export const LinkDestinations = Object.freeze({
2
+ URL: 'url',
3
+ BLOCK: 'block'
4
+ });
@@ -0,0 +1,4 @@
1
+ export const LinkTargets = Object.freeze({
2
+ BLANK: '_blank',
3
+ SELF: '_self'
4
+ });
@@ -8,6 +8,7 @@ export const TextSettings = Object.freeze({
8
8
  FONT_WEIGHT: 'font_weight',
9
9
  LINE_HEIGHT: 'line_height',
10
10
  TEXT_DECORATION: 'text_decoration',
11
+ SUPERSCRIPT: 'superscript',
11
12
 
12
13
  get attributes() {
13
14
  return [
@@ -24,7 +25,8 @@ export const TextSettings = Object.freeze({
24
25
  this.FONT_SIZE,
25
26
  this.FONT_STYLE,
26
27
  this.FONT_WEIGHT,
27
- this.TEXT_DECORATION
28
+ this.TEXT_DECORATION,
29
+ this.SUPERSCRIPT
28
30
  ];
29
31
  }
30
32
  });
@@ -4,3 +4,5 @@ export { Alignments } from './Alignments';
4
4
  export { NodeTypes } from './NodeTypes';
5
5
  export { ListTypes } from './ListTypes';
6
6
  export { TextSettings } from './TextSettings';
7
+ export { LinkTargets } from './LinkTargets';
8
+ export { LinkDestinations } from './LinkDestinations';
@@ -19,13 +19,31 @@ export const Alignment = Extension.create({
19
19
  [TextSettings.ALIGNMENT]: {
20
20
  isRequired: false,
21
21
 
22
+ parseHTML({ style }) {
23
+ if (style.textAlign) {
24
+ return {
25
+ desktop: style.textAlign,
26
+ tablet: style.textAlign,
27
+ mobile: style.textAlign
28
+ };
29
+ }
30
+
31
+ const mobile = style.getPropertyValue('--zw-text-align-mobile') || null;
32
+ const tablet = style.getPropertyValue('--zw-text-align-tablet') || null;
33
+ const desktop = style.getPropertyValue('--zw-text-align-desktop') || null;
34
+
35
+ if (!mobile && !tablet && !desktop) return null;
36
+
37
+ return { desktop, tablet, mobile };
38
+ },
39
+
22
40
  renderHTML(attrs) {
23
41
  if (!attrs.alignment) return null;
24
42
 
25
43
  return renderInlineSetting({
26
- mobile_text_align: attrs.alignment.mobile,
27
- tablet_text_align: attrs.alignment.tablet,
28
- desktop_text_align: attrs.alignment.desktop
44
+ text_align_mobile: attrs.alignment.mobile,
45
+ text_align_tablet: attrs.alignment.tablet,
46
+ text_align_desktop: attrs.alignment.desktop
29
47
  });
30
48
  }
31
49
  }
@@ -1,6 +1,6 @@
1
1
  import { Mark } from '@tiptap/vue-2';
2
2
  import { computed } from '@vue/composition-api';
3
- import { createCommand, renderMark } from '../utils';
3
+ import { convertColor, createCommand, renderMark } from '../utils';
4
4
  import { TextSettings } from '../enums';
5
5
 
6
6
  export const BackgroundColor = Mark.create({
@@ -22,6 +22,21 @@ export const BackgroundColor = Mark.create({
22
22
  };
23
23
  },
24
24
 
25
+ parseHTML() {
26
+ const getAttrs = (value) => ({ value: convertColor(value) });
27
+
28
+ return [
29
+ {
30
+ style: '--zw-background-color',
31
+ getAttrs
32
+ },
33
+ {
34
+ style: 'background-color',
35
+ getAttrs
36
+ }
37
+ ];
38
+ },
39
+
25
40
  renderHTML({ HTMLAttributes: attrs }) {
26
41
  return renderMark({ background_color: attrs.value });
27
42
  }
@@ -1,6 +1,6 @@
1
1
  import { Mark } from '@tiptap/vue-2';
2
2
  import { computed } from '@vue/composition-api';
3
- import { createCommand, renderMark } from '../utils';
3
+ import { convertColor, createCommand, renderMark } from '../utils';
4
4
  import { TextSettings } from '../enums';
5
5
 
6
6
  export const FontColor = Mark.create({
@@ -30,6 +30,21 @@ export const FontColor = Mark.create({
30
30
  };
31
31
  },
32
32
 
33
+ parseHTML() {
34
+ const getAttrs = (value) => ({ value: convertColor(value) });
35
+
36
+ return [
37
+ {
38
+ style: '--zw-font-color',
39
+ getAttrs
40
+ },
41
+ {
42
+ style: 'color',
43
+ getAttrs
44
+ }
45
+ ];
46
+ },
47
+
33
48
  renderHTML({ HTMLAttributes: attrs }) {
34
49
  return renderMark({ font_color: attrs.value });
35
50
  }
@@ -7,7 +7,8 @@ export const FontFamily = Mark.create({
7
7
  name: TextSettings.FONT_FAMILY,
8
8
 
9
9
  addOptions: () => ({
10
- fonts: []
10
+ fonts: [],
11
+ defaultFont: ''
11
12
  }),
12
13
 
13
14
  addAttributes: () => ({
@@ -56,7 +57,30 @@ export const FontFamily = Mark.create({
56
57
  };
57
58
  },
58
59
 
60
+ parseHTML() {
61
+ const getAttrs = (input) => {
62
+ const parsed = input.replace(/"/g, '');
63
+ const isExists = this.options.fonts.some((font) => font.name === parsed);
64
+ const value = isExists ? parsed : this.options.defaultFont;
65
+
66
+ return { value };
67
+ };
68
+
69
+ return [
70
+ {
71
+ style: '--zw-font-family',
72
+ getAttrs
73
+ },
74
+ {
75
+ style: 'font-family',
76
+ getAttrs
77
+ }
78
+ ];
79
+ },
80
+
59
81
  renderHTML({ HTMLAttributes: attrs }) {
60
- return renderMark({ font_family: attrs.value });
82
+ const font_family = attrs.value ? `"${attrs.value}"` : null;
83
+
84
+ return renderMark({ font_family });
61
85
  }
62
86
  });
@@ -62,13 +62,38 @@ export const FontSize = Mark.create({
62
62
  'Mod-Shift--': createKeyboardShortcut('decreaseFontSize')
63
63
  }),
64
64
 
65
+ parseHTML() {
66
+ const parseSize = (value) => value ? String(parseInt(value)) : null;
67
+
68
+ return [
69
+ {
70
+ tag: '[style*="--zw-font-size"]',
71
+
72
+ getAttrs: ({ style }) => ({
73
+ mobile: parseSize(style.getPropertyValue('--zw-font-size-mobile')),
74
+ tablet: parseSize(style.getPropertyValue('--zw-font-size-tablet')),
75
+ desktop: parseSize(style.getPropertyValue('--zw-font-size-desktop'))
76
+ })
77
+ },
78
+ {
79
+ style: 'font-size',
80
+
81
+ getAttrs: (input) => {
82
+ const value = parseSize(input);
83
+
84
+ return { desktop: value, tablet: value, mobile: value };
85
+ }
86
+ }
87
+ ];
88
+ },
89
+
65
90
  renderHTML({ HTMLAttributes: attrs }) {
66
91
  const addUnits = (value) => value ? `${value}px` : null;
67
92
 
68
93
  return renderMark({
69
- mobile_font_size: addUnits(attrs.mobile),
70
- tablet_font_size: addUnits(attrs.tablet),
71
- desktop_font_size: addUnits(attrs.desktop)
94
+ font_size_mobile: addUnits(attrs.mobile),
95
+ font_size_tablet: addUnits(attrs.tablet),
96
+ font_size_desktop: addUnits(attrs.desktop)
72
97
  });
73
98
  }
74
99
  });
@@ -56,7 +56,28 @@ export const FontStyle = Mark.create({
56
56
  'Mod-I': createKeyboardShortcut('toggleItalic')
57
57
  }),
58
58
 
59
- renderHTML() {
60
- return renderMark({ font_style: 'italic' });
59
+ parseHTML() {
60
+ const getAttrs = (value) => value.includes('italic') && { italic: true };
61
+
62
+ return [
63
+ {
64
+ style: '--zw-font-style',
65
+ getAttrs
66
+ },
67
+ {
68
+ style: 'font-style',
69
+ getAttrs
70
+ },
71
+ {
72
+ tag: 'i',
73
+ attrs: { italic: true }
74
+ }
75
+ ];
76
+ },
77
+
78
+ renderHTML({ HTMLAttributes: attrs }) {
79
+ const font_style = attrs.italic ? 'italic' : null;
80
+
81
+ return renderMark({ font_style });
61
82
  }
62
83
  });
@@ -33,8 +33,17 @@ export const FontWeight = Mark.create({
33
33
 
34
34
  getFontWeight: createCommand(({ editor, commands }) => {
35
35
  const defaultValue = commands.getDefaultFontWeight();
36
+ const font = commands.getFont();
36
37
 
37
- return computed(() => editor.getAttributes(this.name)?.value ?? defaultValue.value);
38
+ return computed(() => {
39
+ const weight = editor.getAttributes(this.name)?.value ?? defaultValue.value;
40
+
41
+ if (font.value.isWeightSupported(weight)) {
42
+ return weight;
43
+ }
44
+
45
+ return font.value.findClosestWeight(weight);
46
+ });
38
47
  }),
39
48
 
40
49
  getDefaultFontWeight: createCommand(({ commands }) => {
@@ -50,6 +59,29 @@ export const FontWeight = Mark.create({
50
59
  'Mod-B': createKeyboardShortcut('toggleBold')
51
60
  }),
52
61
 
62
+ parseHTML() {
63
+ const getAttrs = (value) => ({ value });
64
+
65
+ return [
66
+ {
67
+ style: '--zw-font-weight',
68
+ getAttrs
69
+ },
70
+ {
71
+ style: 'font-weight',
72
+ getAttrs
73
+ },
74
+ {
75
+ tag: 'b',
76
+ attrs: { value: '700' }
77
+ },
78
+ {
79
+ tag: 'strong',
80
+ attrs: { value: '700' }
81
+ }
82
+ ];
83
+ },
84
+
53
85
  renderHTML({ HTMLAttributes: attrs }) {
54
86
  return renderMark({ font_weight: attrs.value });
55
87
  }
@@ -2,6 +2,7 @@ import { Extension } from '@tiptap/vue-2';
2
2
  import { computed } from '@vue/composition-api';
3
3
  import { createCommand, renderInlineSetting } from '../utils';
4
4
  import { NodeTypes, TextSettings } from '../enums';
5
+ import { ContextWindow } from '../services';
5
6
 
6
7
  const DEFAULTS = {
7
8
  mobile: null,
@@ -19,13 +20,38 @@ export const LineHeight = Extension.create({
19
20
  [TextSettings.LINE_HEIGHT]: {
20
21
  isRequired: false,
21
22
 
23
+ parseHTML(element) {
24
+ if (element.matches('[style*="--zw-line-height"]')) {
25
+ const mobile = element.style.getPropertyValue('--zw-line-height-mobile') || null;
26
+ const tablet = element.style.getPropertyValue('--zw-line-height-tablet') || null;
27
+ const desktop = element.style.getPropertyValue('--zw-line-height-desktop') || null;
28
+
29
+ return { mobile, tablet, desktop };
30
+ }
31
+
32
+ const value = element.style.lineHeight;
33
+
34
+ if (!value) return null;
35
+
36
+ if (!value.includes('px')) {
37
+ return { desktop: value, tablet: value, mobile: value };
38
+ }
39
+
40
+ // element is not connected to window so getComputedStyle is not working on element
41
+ const childFontSize = element.firstElementChild.style.fontSize;
42
+ const fontSize = childFontSize || ContextWindow.getComputedStyle(ContextWindow.body).fontSize;
43
+ const relative = (parseFloat(value) / parseFloat(fontSize)).toFixed(2);
44
+
45
+ return { desktop: relative, tablet: relative, mobile: relative };
46
+ },
47
+
22
48
  renderHTML(attrs) {
23
49
  if (!attrs.line_height) return null;
24
50
 
25
51
  return renderInlineSetting({
26
- mobile_line_height: attrs.line_height.mobile,
27
- tablet_line_height: attrs.line_height.tablet,
28
- desktop_line_height: attrs.line_height.desktop
52
+ line_height_mobile: attrs.line_height.mobile,
53
+ line_height_tablet: attrs.line_height.tablet,
54
+ line_height_desktop: attrs.line_height.desktop
29
55
  });
30
56
  }
31
57
  }
@@ -0,0 +1,101 @@
1
+ import Base from '@tiptap/extension-link';
2
+ import { createCommand } from '../utils';
3
+ import { LinkDestinations, LinkTargets } from '../enums';
4
+
5
+ export const Link = Base.extend({
6
+ name: 'link',
7
+
8
+ addOptions() {
9
+ return {
10
+ ...this.parent?.(),
11
+ openOnClick: false,
12
+ HTMLAttributes: {
13
+ target: LinkTargets.SELF
14
+ },
15
+ preset: {},
16
+ basePresetClass: null,
17
+ pageBlocks: []
18
+ };
19
+ },
20
+
21
+ addAttributes() {
22
+ return {
23
+ href: {
24
+ default: null,
25
+ parseHTML: (element) => {
26
+ const href = element.getAttribute('href');
27
+
28
+ if (!href.startsWith('#')) return href;
29
+
30
+ return parseFloat(element.getAttribute('href').replace('#', ''));
31
+ }
32
+ },
33
+
34
+ target: {
35
+ default: this.options.target,
36
+ parseHTML: (element) => {
37
+ const target = element.getAttribute('target');
38
+
39
+ if (!target) return LinkTargets.SELF;
40
+
41
+ return target;
42
+ }
43
+ },
44
+
45
+ destination: {
46
+ default: LinkDestinations.URL,
47
+ parseHTML: (element) => {
48
+ const href = element.getAttribute('href');
49
+
50
+ if (!href.startsWith('#')) return LinkDestinations.URL;
51
+
52
+ const id = href.replace('#', '');
53
+ const block = this.options.pageBlocks.value.find((block) => block.id === parseInt(id));
54
+
55
+ return block ? LinkDestinations.BLOCK : LinkDestinations.URL;
56
+ }
57
+ }
58
+ };
59
+ },
60
+
61
+ addCommands() {
62
+ return {
63
+ ...this.parent?.(),
64
+ applyLink: createCommand(({ commands, chain }, attributes) => {
65
+ if (!commands.getSelectedText()) {
66
+ return commands.insertContent({
67
+ type: 'text',
68
+ marks: [
69
+ {
70
+ type: 'link',
71
+ attrs: { ...attributes }
72
+ }
73
+ ],
74
+ text: attributes.text
75
+ });
76
+ }
77
+
78
+ return chain()
79
+ .setMark(this.name, attributes)
80
+ .transformText(() => attributes.text)
81
+ .extendMarkRange('link')
82
+ .run();
83
+ })
84
+ };
85
+ },
86
+
87
+ renderHTML({ HTMLAttributes: attrs }) {
88
+ const href = attrs.destination === LinkDestinations.BLOCK ? `#${attrs.href}` : attrs.href;
89
+
90
+ const presetClass = this.options.basePresetClass + this.options.preset.id;
91
+ const classes = `${presetClass} zw-style`;
92
+
93
+ const linkAttrs = {
94
+ href,
95
+ target: attrs.target,
96
+ class: classes
97
+ };
98
+
99
+ return ['a', linkAttrs, 0];
100
+ }
101
+ });
@@ -3,6 +3,13 @@ import { computed, toRef } from '@vue/composition-api';
3
3
  import { Heading } from '@tiptap/extension-heading';
4
4
  import { createCommand } from '../utils';
5
5
  import { Devices, NodeTypes, TextSettings } from '../enums';
6
+ import { ContextWindow } from '../services';
7
+
8
+ function makePresetClass(base, preset) {
9
+ const baseClass = base.split(' ').map((part) => `.${part}`).join('');
10
+
11
+ return baseClass + preset.id;
12
+ }
6
13
 
7
14
  export const StylePreset = Extension.create({
8
15
  name: 'style_preset',
@@ -17,6 +24,7 @@ export const StylePreset = Extension.create({
17
24
  addOptions: () => ({
18
25
  presetsRef: null,
19
26
  defaultId: null,
27
+ baseClass: null,
20
28
  makeVariable: null
21
29
  }),
22
30
 
@@ -32,10 +40,31 @@ export const StylePreset = Extension.create({
32
40
  preset: {
33
41
  isRequired: false,
34
42
  default: { id: this.options.defaultId },
43
+
44
+ parseHTML: (element) => {
45
+ const presets = this.options.presetsRef.value;
46
+
47
+ if (element.parentElement.tagName === 'LI') return null;
48
+
49
+ for (const { id, node, fallbackClass } of presets) {
50
+ const isFallback = fallbackClass && element.classList.contains(fallbackClass);
51
+
52
+ if (isFallback) return { id };
53
+
54
+ const className = makePresetClass(this.options.baseClass, { id });
55
+
56
+ if (element.matches(className)) return { id };
57
+ if (element.tagName === `H${node?.level}`) return { id };
58
+ }
59
+
60
+ // Matches all paragraphs without classes to default preset
61
+ return element.tagName === 'P' ? { id: this.options.defaultId } : null;
62
+ },
63
+
35
64
  renderHTML: (attrs) => {
36
65
  if (!attrs.preset) return null;
37
66
 
38
- return { class: `zw-preset--${attrs.preset.id}` };
67
+ return { class: this.options.baseClass + attrs.preset.id };
39
68
  }
40
69
  }
41
70
  }
@@ -121,7 +150,7 @@ export const StylePreset = Extension.create({
121
150
  chain()
122
151
  .storeSelection()
123
152
  .expandSelectionToBlock()
124
- .unsetAllMarks()
153
+ .unsetMarks(TextSettings.marks)
125
154
  .resetAttributes(NodeTypes.PARAGRAPH, TextSettings.attributes)
126
155
  .resetAttributes(NodeTypes.HEADING, TextSettings.attributes)
127
156
  .restoreSelection()
@@ -136,18 +165,19 @@ export const StylePreset = Extension.create({
136
165
  },
137
166
 
138
167
  onCreate() {
139
- const existingStyleEl = document.querySelector('[data-zw-styles]');
168
+ const existingStyleEl = ContextWindow.document.querySelector('[data-zw-styles]');
140
169
 
141
170
  if (existingStyleEl) {
142
171
  this.storage.presetStyleEl = existingStyleEl;
143
172
  return;
144
173
  }
145
174
 
146
- this.storage.presetStyleEl = document.createElement('style');
175
+ this.storage.presetStyleEl = ContextWindow.document.createElement('style');
147
176
  this.storage.presetStyleEl.dataset.zwStyles = '';
148
177
 
149
178
  for (const preset of this.options.presetsRef.value) {
150
- const css = [` .zw-preset--${preset.id} {`];
179
+ const className = makePresetClass(this.options.baseClass, preset);
180
+ const css = [` ${className} {`];
151
181
 
152
182
  for (const device of Devices.values) {
153
183
  for (const setting of Object.keys(preset[device])) {
@@ -163,6 +193,6 @@ export const StylePreset = Extension.create({
163
193
  this.storage.presetStyleEl.innerHTML += css.join(' ');
164
194
  }
165
195
 
166
- document.head.append(this.storage.presetStyleEl);
196
+ ContextWindow.head.append(this.storage.presetStyleEl);
167
197
  }
168
198
  });
@@ -13,10 +13,10 @@ export const TextDecoration = Mark.create({
13
13
 
14
14
  addCommands() {
15
15
  return {
16
- isUnderline: createCommand(({ commands }) => {
16
+ isUnderline: createCommand(({ commands, editor }) => {
17
17
  const decoration = commands.getTextDecoration();
18
18
 
19
- return computed(() => decoration.value.underline);
19
+ return computed(() => editor.isActive('link') || decoration.value.underline);
20
20
  }),
21
21
 
22
22
  isStrikeThrough: createCommand(({ commands }) => {
@@ -51,7 +51,9 @@ export const TextDecoration = Mark.create({
51
51
  });
52
52
  }),
53
53
 
54
- toggleUnderline: createCommand(({ commands }) => {
54
+ toggleUnderline: createCommand(({ commands, editor }) => {
55
+ if (editor.isActive('link')) return;
56
+
55
57
  commands.toggleTextDecoration('underline');
56
58
  }),
57
59
 
@@ -86,6 +88,30 @@ export const TextDecoration = Mark.create({
86
88
  'Mod-U': createKeyboardShortcut('toggleUnderline')
87
89
  }),
88
90
 
91
+ parseHTML() {
92
+ const getAttrs = (value) => {
93
+ const underline = value.includes('underline');
94
+ const strike_through = value.includes('line-through');
95
+
96
+ if (!underline && !strike_through) {
97
+ return false;
98
+ }
99
+
100
+ return { underline, strike_through };
101
+ };
102
+
103
+ return [
104
+ {
105
+ style: '--zw-text-decoration',
106
+ getAttrs
107
+ },
108
+ {
109
+ style: 'text-decoration-line',
110
+ getAttrs
111
+ }
112
+ ];
113
+ },
114
+
89
115
  renderHTML({ HTMLAttributes: attrs }) {
90
116
  const decorations = [];
91
117