@zipify/wysiwyg 1.0.0-dev.6 → 1.0.0-dev.62

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 (217) hide show
  1. package/.editorconfig +1 -1
  2. package/.eslintrc.js +2 -2
  3. package/.github/dependabot.yaml +1 -0
  4. package/.lintstagedrc +2 -2
  5. package/.release-it.json +3 -1
  6. package/.stylelintrc +0 -4
  7. package/README.md +2 -2
  8. package/config/jest/setupTests.js +0 -3
  9. package/config/vite/example.config.js +25 -0
  10. package/config/vite/lib.config.js +28 -0
  11. package/config/{webpack → vite}/settings.js +0 -0
  12. package/dist/wysiwyg.css +1 -837
  13. package/dist/wysiwyg.mjs +18353 -0
  14. package/example/ExampleApp.vue +41 -3
  15. package/example/example.js +0 -3
  16. package/example/{example.html → index.html} +1 -0
  17. package/example/pageBlocks.js +31 -0
  18. package/example/presets.js +2 -2
  19. package/example/tooltip/Tooltip.js +1 -1
  20. package/jest.config.js +3 -1
  21. package/lib/Wysiwyg.vue +65 -26
  22. package/lib/__tests__/utils/NodeFactory.js +13 -0
  23. package/lib/__tests__/utils/withComponentContext.js +1 -1
  24. package/lib/assets/icons/link.svg +3 -0
  25. package/lib/assets/icons/unlink.svg +3 -0
  26. package/lib/components/base/Button.vue +22 -2
  27. package/lib/components/base/Checkbox.vue +89 -0
  28. package/lib/components/base/FieldLabel.vue +2 -1
  29. package/lib/components/base/Icon.vue +3 -3
  30. package/lib/components/base/Modal.vue +1 -2
  31. package/lib/components/base/NumberField.vue +2 -2
  32. package/lib/components/base/Range.vue +1 -1
  33. package/lib/components/base/ScrollView.vue +1 -3
  34. package/lib/components/base/TextField.vue +106 -0
  35. package/lib/components/base/__tests__/Modal.test.js +7 -2
  36. package/lib/components/base/__tests__/TextField.test.js +57 -0
  37. package/lib/components/base/__tests__/__snapshots__/TextField.test.js.snap +9 -0
  38. package/lib/components/base/colorPicker/ColorPicker.vue +2 -2
  39. package/lib/components/base/colorPicker/composables/__tests__/usePickerApi.test.js +1 -1
  40. package/lib/components/base/colorPicker/composables/usePickerApi.js +3 -3
  41. package/lib/components/base/colorPicker/composables/usePickerHotkeys.js +3 -2
  42. package/lib/components/base/composables/__tests__/useActivatedListener.test.js +1 -1
  43. package/lib/components/base/composables/__tests__/useDeselectionLock.test.js +1 -1
  44. package/lib/components/base/composables/__tests__/useElementRef.test.js +1 -1
  45. package/lib/components/base/composables/__tests__/useModalToggler.test.js +1 -1
  46. package/lib/components/base/composables/__tests__/useNumberValue.test.js +1 -1
  47. package/lib/components/base/composables/__tests__/useScrollView.test.js +1 -1
  48. package/lib/components/base/composables/__tests__/useTempValue.test.js +1 -1
  49. package/lib/components/base/composables/__tests__/useValidator.test.js +44 -0
  50. package/lib/components/base/composables/index.js +1 -0
  51. package/lib/components/base/composables/useActivatedListener.js +1 -1
  52. package/lib/components/base/composables/useDeselectionLock.js +1 -1
  53. package/lib/components/base/composables/useElementRef.js +1 -1
  54. package/lib/components/base/composables/useModalToggler.js +1 -1
  55. package/lib/components/base/composables/useScrollView.js +1 -1
  56. package/lib/components/base/composables/useTempValue.js +1 -1
  57. package/lib/components/base/composables/useValidator.js +23 -0
  58. package/lib/components/base/dropdown/Dropdown.vue +16 -4
  59. package/lib/components/base/dropdown/DropdownActivator.vue +19 -3
  60. package/lib/components/base/dropdown/DropdownGroup.vue +1 -1
  61. package/lib/components/base/dropdown/DropdownMenu.vue +1 -1
  62. package/lib/components/base/dropdown/DropdownOption.vue +1 -1
  63. package/lib/components/base/dropdown/__tests__/DropdownActivator.test.js +1 -1
  64. package/lib/components/base/dropdown/__tests__/DropdownMenu.test.js +1 -1
  65. package/lib/components/base/dropdown/__tests__/DropdownOption.test.js +1 -1
  66. package/lib/components/base/dropdown/composables/__tests__/useActiveOptionManager.test.js +1 -1
  67. package/lib/components/base/dropdown/composables/__tests__/useDropdownEntityTitle.test.js +1 -1
  68. package/lib/components/base/dropdown/composables/useActiveOptionManager.js +1 -1
  69. package/lib/components/base/dropdown/composables/useDropdownEntityTitle.js +1 -1
  70. package/lib/components/base/index.js +3 -1
  71. package/lib/components/toolbar/Toolbar.vue +49 -9
  72. package/lib/components/toolbar/ToolbarDivider.vue +1 -1
  73. package/lib/components/toolbar/ToolbarFull.vue +10 -2
  74. package/lib/components/toolbar/ToolbarRow.vue +1 -0
  75. package/lib/components/toolbar/__tests__/Toolbar.test.js +6 -0
  76. package/lib/components/toolbar/controls/AlignmentControl.vue +1 -1
  77. package/lib/components/toolbar/controls/AlignmentDeviceControl.vue +1 -1
  78. package/lib/components/toolbar/controls/BackgroundColorControl.vue +1 -1
  79. package/lib/components/toolbar/controls/CaseStyleControl.vue +1 -1
  80. package/lib/components/toolbar/controls/FontColorControl.vue +1 -1
  81. package/lib/components/toolbar/controls/FontFamilyControl.vue +1 -1
  82. package/lib/components/toolbar/controls/FontSizeControl.vue +8 -1
  83. package/lib/components/toolbar/controls/FontWeightControl.vue +1 -1
  84. package/lib/components/toolbar/controls/ItalicControl.vue +1 -1
  85. package/lib/components/toolbar/controls/LineHeightControl.vue +1 -1
  86. package/lib/components/toolbar/controls/ListControl.vue +68 -34
  87. package/lib/components/toolbar/controls/RemoveFormatControl.vue +1 -1
  88. package/lib/components/toolbar/controls/StrikeThroughControl.vue +1 -1
  89. package/lib/components/toolbar/controls/StylePresetControl.vue +15 -2
  90. package/lib/components/toolbar/controls/SuperscriptControl.vue +1 -1
  91. package/lib/components/toolbar/controls/UnderlineControl.vue +2 -2
  92. package/lib/components/toolbar/controls/__tests__/AlignmentControl.test.js +1 -1
  93. package/lib/components/toolbar/controls/__tests__/AlignmentDeviceControl.test.js +1 -1
  94. package/lib/components/toolbar/controls/__tests__/BackgroundColorControl.test.js +1 -1
  95. package/lib/components/toolbar/controls/__tests__/CaseStyleControl.test.js +1 -1
  96. package/lib/components/toolbar/controls/__tests__/FontColorControl.test.js +1 -1
  97. package/lib/components/toolbar/controls/__tests__/FontFamilyControl.test.js +1 -1
  98. package/lib/components/toolbar/controls/__tests__/FontSizeControl.test.js +1 -1
  99. package/lib/components/toolbar/controls/__tests__/FontWeightControl.test.js +1 -1
  100. package/lib/components/toolbar/controls/__tests__/ItalicControl.test.js +1 -1
  101. package/lib/components/toolbar/controls/__tests__/LineHeightControl.test.js +1 -1
  102. package/lib/components/toolbar/controls/__tests__/ListControl.test.js +18 -3
  103. package/lib/components/toolbar/controls/__tests__/StrikeThroughControl.test.js +1 -1
  104. package/lib/components/toolbar/controls/__tests__/StylePresetControl.test.js +17 -1
  105. package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +5 -1
  106. package/lib/components/toolbar/controls/composables/useRecentFonts.js +1 -1
  107. package/lib/components/toolbar/controls/index.js +1 -0
  108. package/lib/components/toolbar/controls/link/LinkControl.vue +155 -0
  109. package/lib/components/toolbar/controls/link/LinkControlApply.vue +35 -0
  110. package/lib/components/toolbar/controls/link/LinkControlHeader.vue +67 -0
  111. package/lib/components/toolbar/controls/link/__tests__/LinkControl.test.js +79 -0
  112. package/lib/components/toolbar/controls/link/__tests__/LinkControlHeader.test.js +42 -0
  113. package/lib/components/toolbar/controls/link/composables/__tests__/__snapshots__/useLink.test.js.snap +8 -0
  114. package/lib/components/toolbar/controls/link/composables/__tests__/useLink.test.js +114 -0
  115. package/lib/components/toolbar/controls/link/composables/index.js +1 -0
  116. package/lib/components/toolbar/controls/link/composables/useLink.js +61 -0
  117. package/lib/components/toolbar/controls/link/destination/LinkControlDestination.vue +103 -0
  118. package/lib/components/toolbar/controls/link/destination/LinkControlPageBlock.vue +54 -0
  119. package/lib/components/toolbar/controls/link/destination/LinkControlUrl.vue +52 -0
  120. package/lib/components/toolbar/controls/link/destination/__tests__/LinkControlPageBlock.test.js +36 -0
  121. package/lib/components/toolbar/controls/link/destination/__tests__/LinkControlUrl.test.js +46 -0
  122. package/lib/components/toolbar/controls/link/destination/__tests__/__snapshots__/LinkControlPageBlock.test.js.snap +9 -0
  123. package/lib/components/toolbar/controls/link/destination/__tests__/__snapshots__/LinkControlUrl.test.js.snap +17 -0
  124. package/lib/components/toolbar/controls/link/destination/index.js +1 -0
  125. package/lib/components/toolbar/controls/link/index.js +1 -0
  126. package/lib/composables/__tests__/useEditor.test.js +4 -3
  127. package/lib/composables/useEditor.js +8 -8
  128. package/lib/composables/useToolbar.js +23 -28
  129. package/lib/directives/__tests__/outClick.test.js +6 -0
  130. package/lib/directives/outClick.js +19 -6
  131. package/lib/enums/Alignments.js +10 -1
  132. package/lib/enums/LinkDestinations.js +4 -0
  133. package/lib/enums/LinkTargets.js +4 -0
  134. package/lib/enums/TextSettings.js +3 -1
  135. package/lib/enums/index.js +2 -0
  136. package/lib/extensions/Alignment.js +22 -8
  137. package/lib/extensions/BackgroundColor.js +15 -7
  138. package/lib/extensions/DeviceManager.js +2 -5
  139. package/lib/extensions/FontColor.js +15 -7
  140. package/lib/extensions/FontFamily.js +26 -9
  141. package/lib/extensions/FontSize.js +32 -13
  142. package/lib/extensions/FontStyle.js +24 -14
  143. package/lib/extensions/FontWeight.js +25 -15
  144. package/lib/extensions/LineHeight.js +32 -29
  145. package/lib/extensions/Link.js +90 -0
  146. package/lib/extensions/StylePreset.js +20 -19
  147. package/lib/extensions/Superscript.js +5 -1
  148. package/lib/extensions/TextDecoration.js +46 -13
  149. package/lib/extensions/__tests__/Alignment.test.js +13 -7
  150. package/lib/extensions/__tests__/BackgroundColor.test.js +12 -6
  151. package/lib/extensions/__tests__/CaseStyle.test.js +3 -5
  152. package/lib/extensions/__tests__/FontColor.test.js +12 -6
  153. package/lib/extensions/__tests__/FontFamily.test.js +33 -8
  154. package/lib/extensions/__tests__/FontSize.test.js +15 -8
  155. package/lib/extensions/__tests__/FontStyle.test.js +12 -6
  156. package/lib/extensions/__tests__/FontWeight.test.js +20 -6
  157. package/lib/extensions/__tests__/LineHeight.test.js +24 -12
  158. package/lib/extensions/__tests__/Link.test.js +102 -0
  159. package/lib/extensions/__tests__/StylePreset.test.js +71 -8
  160. package/lib/extensions/__tests__/TextDecoration.test.js +52 -6
  161. package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +26 -2
  162. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +30 -1
  163. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +18 -1
  164. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +88 -1
  165. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +33 -2
  166. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +25 -4
  167. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +47 -1
  168. package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +26 -2
  169. package/lib/extensions/__tests__/__snapshots__/Link.test.js.snap +225 -0
  170. package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +6 -2
  171. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +183 -3
  172. package/lib/extensions/core/CopyPasteProcessor.js +10 -0
  173. package/lib/extensions/core/NodeProcessor.js +1 -1
  174. package/lib/extensions/core/TextProcessor.js +10 -0
  175. package/lib/extensions/core/__tests__/NodeProcessor.test.js +3 -5
  176. package/lib/extensions/core/__tests__/SelectionProcessor.test.js +3 -5
  177. package/lib/extensions/core/__tests__/TextProcessor.test.js +138 -12
  178. package/lib/extensions/core/__tests__/__snapshots__/TextProcessor.test.js.snap +26 -0
  179. package/lib/extensions/core/index.js +11 -2
  180. package/lib/extensions/core/plugins/PastePlugin.js +57 -0
  181. package/lib/extensions/core/plugins/ProseMirrorPlugin.js +20 -0
  182. package/lib/extensions/core/plugins/index.js +1 -0
  183. package/lib/extensions/index.js +46 -34
  184. package/lib/extensions/list/List.js +2 -3
  185. package/lib/extensions/list/__tests__/List.test.js +5 -8
  186. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +60 -20
  187. package/lib/injectionTokens.js +2 -1
  188. package/lib/services/ContentNormalizer.js +122 -29
  189. package/lib/services/ContextWidnow.js +23 -0
  190. package/lib/services/__tests__/ContentNormalizer.test.js +96 -7
  191. package/lib/services/__tests__/FavoriteColors.test.js +1 -1
  192. package/lib/services/index.js +1 -0
  193. package/lib/styles/content.css +110 -13
  194. package/lib/styles/helpers/offsets.css +16 -0
  195. package/lib/styles/variables.css +6 -0
  196. package/lib/utils/__tests__/__snapshots__/renderInlineSetting.test.js.snap +4 -4
  197. package/lib/utils/__tests__/convertAlignment.test.js +16 -0
  198. package/lib/utils/__tests__/convertFontSize.test.js +21 -0
  199. package/lib/utils/__tests__/convertLineHeight.test.js +21 -0
  200. package/lib/utils/convertAlignment.js +12 -0
  201. package/lib/utils/convertColor.js +1 -1
  202. package/lib/utils/convertFontSize.js +8 -0
  203. package/lib/utils/convertLineHeight.js +17 -0
  204. package/lib/utils/importIcon.js +7 -6
  205. package/lib/utils/index.js +3 -0
  206. package/lib/utils/renderInlineSetting.js +1 -1
  207. package/package.json +26 -31
  208. package/config/webpack/example.config.js +0 -88
  209. package/config/webpack/lib.config.js +0 -40
  210. package/config/webpack/loaders/index.js +0 -6
  211. package/config/webpack/loaders/js-loader.js +0 -5
  212. package/config/webpack/loaders/style-loader.js +0 -9
  213. package/config/webpack/loaders/svg-loader.js +0 -4
  214. package/config/webpack/loaders/vue-loader.js +0 -4
  215. package/dist/wysiwyg.js +0 -2
  216. package/dist/wysiwyg.js.LICENSE.txt +0 -1
  217. package/lib/composables/__tests__/useToolbar.test.js +0 -56
@@ -1,8 +1,9 @@
1
1
  import { shallowMount } from '@vue/test-utils';
2
- import { ref } from '@vue/composition-api';
2
+ import { ref } from 'vue';
3
3
  import { InjectionTokens } from '../../../../injectionTokens';
4
4
  import { Button, Dropdown } from '../../../base';
5
5
  import StylePresetControl from '../StylePresetControl';
6
+ import { TextSettings } from '../../../../enums';
6
7
 
7
8
  const createEditor = ({ presets, preset, customization } = {}) => ({
8
9
  commands: {
@@ -12,6 +13,10 @@ const createEditor = ({ presets, preset, customization } = {}) => ({
12
13
  getPresetCustomization: () => ref(customization || { attributes: [], marks: [] }),
13
14
  applyPreset: jest.fn().mockReturnThis(),
14
15
  removePresetCustomization: jest.fn().mockReturnThis(),
16
+ storeSelection: jest.fn().mockReturnThis(),
17
+ restoreSelection: jest.fn().mockReturnThis(),
18
+ expandSelectionToBlock: jest.fn().mockReturnThis(),
19
+ unsetMarks: jest.fn().mockReturnThis(),
15
20
  run: jest.fn()
16
21
  },
17
22
 
@@ -114,6 +119,17 @@ describe('apply preset', () => {
114
119
 
115
120
  expect(editor.commands.applyPreset).toHaveBeenCalledWith('regular-1');
116
121
  });
122
+
123
+ test('should remove settings on preset change', () => {
124
+ const editor = createEditor();
125
+ const wrapper = createComponent({ editor });
126
+ const dropdownWrapper = wrapper.findComponent(Dropdown);
127
+
128
+ dropdownWrapper.vm.$emit('change', 'regular-1');
129
+
130
+ expect(editor.commands.expandSelectionToBlock).toHaveBeenCalled();
131
+ expect(editor.commands.unsetMarks).toHaveBeenCalledWith([TextSettings.FONT_SIZE, TextSettings.FONT_WEIGHT]);
132
+ });
117
133
  });
118
134
 
119
135
  describe('remove customization', () => {
@@ -1,5 +1,5 @@
1
1
  import { shallowMount } from '@vue/test-utils';
2
- import { ref } from '@vue/composition-api';
2
+ import { ref } from 'vue';
3
3
  import { InjectionTokens } from '../../../../injectionTokens';
4
4
  import { Button } from '../../../base';
5
5
  import UnderlineControl from '../UnderlineControl';
@@ -12,6 +12,10 @@ const createEditor = ({ isUnderline } = {}) => ({
12
12
  run: jest.fn()
13
13
  },
14
14
 
15
+ isActive() {
16
+ return false;
17
+ },
18
+
15
19
  chain() {
16
20
  return this.commands;
17
21
  }
@@ -1,4 +1,4 @@
1
- import { computed, inject, ref } from '@vue/composition-api';
1
+ import { computed, inject, ref } from 'vue';
2
2
  import { InjectionTokens } from '../../../../injectionTokens';
3
3
 
4
4
  const STORAGE_KEY = 'wswg.recently_used_fonts';
@@ -14,3 +14,4 @@ export { default as AlignmentDeviceControl } from './AlignmentDeviceControl';
14
14
  export { default as LineHeightControl } from './LineHeightControl';
15
15
  export { default as ListControl } from './ListControl';
16
16
  export { default as RemoveFormatControl } from './RemoveFormatControl';
17
+ export { LinkControl } from './link';
@@ -0,0 +1,155 @@
1
+ <template>
2
+ <div class="zw-position--relative" ref="wrapperRef">
3
+ <Button icon skin="toolbar" :active="isActive" @click="toggler.open" v-tooltip="'Link'">
4
+ <Icon name="link" size="28px" auto-color />
5
+ </Button>
6
+
7
+ <Modal class="zw-link-modal" :toggler="toggler" ref="modalRef">
8
+ <LinkControlHeader @remove-link="removeLink" />
9
+
10
+ <div class="zw-link-modal__body">
11
+ <TextField
12
+ class="zw-margin-bottom--sm"
13
+ :value="link.linkData.value.text"
14
+ label="Text to display"
15
+ placeholder="Type Text"
16
+ :error="nameValidator.error.value"
17
+ @input="updateLinkText"
18
+ />
19
+
20
+ <LinkControlDestination
21
+ class="zw-margin-bottom--md"
22
+ :link="link"
23
+ :validator="urlValidator"
24
+ @reset-errors="resetErrors"
25
+ />
26
+
27
+ <LinkControlApply @cancel="toggler.close" @apply="applyLink" />
28
+ </div>
29
+ </Modal>
30
+ </div>
31
+ </template>
32
+
33
+ <script>
34
+ import { computed, ref, inject } from 'vue';
35
+ import { InjectionTokens } from '../../../../injectionTokens';
36
+ import { tooltip } from '../../../../directives';
37
+ import { useValidator } from '../../../base/composables';
38
+ import { Button, Icon, Modal, TextField, useModalToggler } from '../../../base';
39
+ import LinkControlHeader from './LinkControlHeader';
40
+ import LinkControlApply from './LinkControlApply';
41
+ import { useLink } from './composables';
42
+ import { LinkControlDestination } from './destination';
43
+
44
+ export default {
45
+ name: 'LinkControl',
46
+
47
+ components: {
48
+ LinkControlDestination,
49
+ LinkControlApply,
50
+ LinkControlHeader,
51
+ TextField,
52
+ Modal,
53
+ Icon,
54
+ Button
55
+ },
56
+
57
+ directives: {
58
+ tooltip
59
+ },
60
+
61
+ setup() {
62
+ const wrapperRef = ref(null);
63
+ const modalRef = ref(null);
64
+
65
+ const editor = inject(InjectionTokens.EDITOR);
66
+
67
+ const link = useLink();
68
+ const urlRegExp = /(^(https?:\/\/|\/)(?:www\.|(?!www))?[^\s])/;
69
+
70
+ const isEmpty = () => {
71
+ return link.linkData.value.text ? false : 'Can\'t be empty';
72
+ };
73
+ const isUrl = () => {
74
+ if (link.currentDestination.value.id !== 'url') return false;
75
+
76
+ return urlRegExp.test(link.destinationHrefs.value.url) ? false : 'Please enter a valid URL';
77
+ };
78
+
79
+ const nameValidator = useValidator({
80
+ validations: [isEmpty]
81
+ });
82
+
83
+ const urlValidator = useValidator({
84
+ validations: [isUrl]
85
+ });
86
+
87
+ const resetErrors = () => {
88
+ nameValidator.reset();
89
+ urlValidator.reset();
90
+ };
91
+
92
+ const onBeforeOpened = () => {
93
+ editor.commands.extendMarkRange('link');
94
+ resetErrors();
95
+ link.prepareInitialFields();
96
+ };
97
+
98
+ const toggler = useModalToggler({
99
+ onBeforeOpened: () => onBeforeOpened(),
100
+ wrapperRef,
101
+ modalRef
102
+ });
103
+
104
+ const isActive = computed(() => toggler.isOpened.value || editor.isActive('link'));
105
+
106
+ const updateLinkText = (value) => {
107
+ resetErrors();
108
+ link.updateText(value);
109
+ };
110
+
111
+ const applyLink = () => {
112
+ urlValidator.validate();
113
+ nameValidator.validate();
114
+
115
+ if (urlValidator.error.value || nameValidator.error.value) return;
116
+
117
+ link.apply();
118
+ toggler.close();
119
+ };
120
+
121
+ const removeLink = () => {
122
+ link.removeLink();
123
+ toggler.close();
124
+ };
125
+
126
+ return {
127
+ wrapperRef,
128
+ modalRef,
129
+ link,
130
+ toggler,
131
+ isActive,
132
+ nameValidator,
133
+ urlValidator,
134
+ updateLinkText,
135
+ resetErrors,
136
+ applyLink,
137
+ removeLink
138
+ };
139
+ }
140
+ };
141
+ </script>
142
+
143
+ <style scoped>
144
+ .zw-link-modal {
145
+ width: 266px;
146
+ }
147
+
148
+ .zw-link-modal__body {
149
+ padding: var(--zw-offset-sm);
150
+ }
151
+
152
+ ::v-deep .zw-link-modal-dropdown__option {
153
+ width: 234px;
154
+ }
155
+ </style>
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <div class="zw-link-modal__apply">
3
+ <Button class="zw-margin-right--xs" skin="secondary" @click="cancel">
4
+ Cancel
5
+ </Button>
6
+
7
+ <Button @click="apply" skin="primary">
8
+ Save
9
+ </Button>
10
+ </div>
11
+ </template>
12
+
13
+ <script>
14
+ import { Button } from '../../../base';
15
+
16
+ export default {
17
+ name: 'LinkControlApply',
18
+
19
+ components: { Button },
20
+
21
+ setup(_, { emit }) {
22
+ const cancel = () => emit('cancel');
23
+ const apply = () => emit('apply');
24
+
25
+ return { cancel, apply };
26
+ }
27
+ };
28
+ </script>
29
+
30
+ <style scoped>
31
+ .zw-link-modal__apply {
32
+ display: flex;
33
+ justify-content: flex-end;
34
+ }
35
+ </style>
@@ -0,0 +1,67 @@
1
+ <template>
2
+ <div class="zw-link-modal-header">
3
+ <span class="zw-link-modal-header__title">Link</span>
4
+
5
+ <Button class="zw-link-modal-header__unlink-button" :disabled="!isLink" @click="removeLink">
6
+ <Icon class="zw-link-modal-header__unlink-icon" name="unlink" size="14px" auto-color />
7
+ Remove
8
+ </Button>
9
+ </div>
10
+ </template>
11
+
12
+ <script>
13
+ import { computed, inject } from 'vue';
14
+ import { Icon, Button } from '../../../base';
15
+ import { InjectionTokens } from '../../../../injectionTokens';
16
+
17
+ export default {
18
+ name: 'LinkControlHeader',
19
+
20
+ components: { Icon, Button },
21
+
22
+ setup(_, { emit }) {
23
+ const editor = inject(InjectionTokens.EDITOR);
24
+ const isLink = computed(() => editor.isActive('link'));
25
+
26
+ const removeLink = () => emit('remove-link');
27
+
28
+ return { isLink, removeLink };
29
+ }
30
+ };
31
+ </script>
32
+
33
+ <style scoped>
34
+ .zw-link-modal-header {
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: space-between;
38
+ padding: var(--zw-offset-sm);
39
+ border-bottom: 2px solid rgb(var(--zw-color-n5));
40
+ }
41
+
42
+ .zw-link-modal-header__title {
43
+ text-transform: uppercase;
44
+ font-weight: var(--zw-font-weight-semibold);
45
+ font-size: var(--zw-font-size-xxs);
46
+ color: rgb(var(--zw-color-white));
47
+ }
48
+
49
+ .zw-link-modal-header__unlink-icon {
50
+ margin-right: var(--zw-offset-xxs);
51
+ }
52
+
53
+ .zw-link-modal-header__unlink-button {
54
+ color: rgb(var(--zw-color-n80));
55
+ font-size: var(--zw-font-size-xxs);
56
+ transition: 0.1s opacity ease-out;
57
+ will-change: opacity;
58
+ }
59
+
60
+ .zw-link-modal-header__unlink-button:disabled {
61
+ opacity: 0.35;
62
+ }
63
+
64
+ .zw-link-modal-header__unlink-button:hover {
65
+ color: rgb(var(--zw-color-white));
66
+ }
67
+ </style>
@@ -0,0 +1,79 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { ref, nextTick } from 'vue';
3
+ import LinkControl from '../LinkControl';
4
+ import { InjectionTokens } from '../../../../../injectionTokens';
5
+ import { Button, TextField } from '../../../../base';
6
+ import LinkControlApply from '../LinkControlApply';
7
+ import { LinkControlDestination } from '../destination';
8
+
9
+ const createEditor = (isActive) => ({
10
+ isActive: () => isActive ?? false
11
+ });
12
+
13
+ function createComponent({ editor }) {
14
+ return shallowMount(LinkControl, {
15
+ provide: {
16
+ [InjectionTokens.EDITOR]: editor,
17
+ [InjectionTokens.PAGE_BLOCKS]: ref([{ id: 1 }])
18
+ }
19
+ });
20
+ }
21
+
22
+ describe('selection value', () => {
23
+ test('should render link status', () => {
24
+ const editor = createEditor(true );
25
+ const wrapper = createComponent({ editor });
26
+ const buttonWrapper = wrapper.findComponent(Button);
27
+
28
+ expect(buttonWrapper.props('active')).toBe(true);
29
+ });
30
+ });
31
+
32
+ describe('validation', () => {
33
+ test('should show error on apply', async () => {
34
+ const editor = createEditor(true );
35
+ const wrapper = createComponent({ editor });
36
+ const textFieldWrapper = wrapper.findComponent(TextField);
37
+ const linkControlDestinationWrapper = wrapper.findComponent(LinkControlDestination);
38
+ const applyWrapper = wrapper.findComponent(LinkControlApply);
39
+
40
+ applyWrapper.vm.$emit('apply');
41
+ await nextTick();
42
+
43
+ expect(textFieldWrapper.props('error')).toBe('Can\'t be empty');
44
+ expect(linkControlDestinationWrapper.props('validator').error.value).toBe('Please enter a valid URL');
45
+ });
46
+
47
+ test('should reset errors on input', async () => {
48
+ const editor = createEditor(true );
49
+ const wrapper = createComponent({ editor });
50
+ const textFieldWrapper = wrapper.findComponent(TextField);
51
+ const linkControlDestinationWrapper = wrapper.findComponent(LinkControlDestination);
52
+ const applyWrapper = wrapper.findComponent(LinkControlApply);
53
+
54
+ applyWrapper.vm.$emit('apply');
55
+ textFieldWrapper.vm.$emit('input', 'hello');
56
+
57
+ await nextTick();
58
+
59
+ expect(textFieldWrapper.props('error')).toBeNull();
60
+ expect(linkControlDestinationWrapper.props('validator').error.value).toBeNull();
61
+ });
62
+
63
+ test('should reset errors on change destination', async () => {
64
+ const editor = createEditor(true );
65
+ const wrapper = createComponent({ editor });
66
+ const textFieldWrapper = wrapper.findComponent(TextField);
67
+ const linkControlDestinationWrapper = wrapper.findComponent(LinkControlDestination);
68
+ const applyWrapper = wrapper.findComponent(LinkControlApply);
69
+
70
+ applyWrapper.vm.$emit('apply');
71
+ linkControlDestinationWrapper.vm.$emit('reset-errors');
72
+
73
+ await nextTick();
74
+
75
+ expect(textFieldWrapper.props('error')).toBeNull();
76
+ expect(linkControlDestinationWrapper.props('validator').error.value).toBeNull();
77
+ });
78
+ });
79
+
@@ -0,0 +1,42 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { InjectionTokens } from '../../../../../injectionTokens';
3
+ import { Button } from '../../../../base';
4
+ import LinkControlHeader from '../LinkControlHeader';
5
+
6
+ const createEditor = (isActive) => ({
7
+ isActive: () => isActive ?? false
8
+ });
9
+
10
+ function createComponent({ editor }) {
11
+ return shallowMount(LinkControlHeader, {
12
+ provide: { [InjectionTokens.EDITOR]: editor }
13
+ });
14
+ }
15
+
16
+ describe('rendering unlink button', () => {
17
+ test('should button be disabled when no link', () => {
18
+ const editor = createEditor(true );
19
+ const wrapper = createComponent({ editor });
20
+ const buttonWrapper = wrapper.findComponent(Button);
21
+
22
+ expect(buttonWrapper.props('disabled')).toBe(false);
23
+ });
24
+
25
+ test('should button be enabled when link selected', () => {
26
+ const editor = createEditor(false );
27
+ const wrapper = createComponent({ editor });
28
+ const buttonWrapper = wrapper.findComponent(Button);
29
+
30
+ expect(buttonWrapper.props('disabled')).toBe(true);
31
+ });
32
+
33
+ test('should send unlink event', () => {
34
+ const editor = createEditor(true );
35
+ const wrapper = createComponent({ editor });
36
+ const buttonWrapper = wrapper.findComponent(Button);
37
+
38
+ buttonWrapper.vm.$emit('click');
39
+
40
+ expect(wrapper.emitted('remove-link')).toBeTruthy();
41
+ });
42
+ });
@@ -0,0 +1,8 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`init link should prepare initial fields 1`] = `
4
+ Object {
5
+ "block": 1,
6
+ "url": "",
7
+ }
8
+ `;
@@ -0,0 +1,114 @@
1
+ import { ref } from 'vue';
2
+ import { withComponentContext } from '../../../../../../__tests__/utils';
3
+ import { InjectionTokens } from '../../../../../../injectionTokens';
4
+ import { LinkDestinations, LinkTargets } from '../../../../../../enums';
5
+ import { useLink } from '../useLink';
6
+
7
+ const createEditor = (text, destination) => ({
8
+ commands: {
9
+ storeSelection: jest.fn(),
10
+ restoreSelection: jest.fn(),
11
+ getSelectedText: jest.fn(() => text ?? ''),
12
+ focus: jest.fn().mockReturnThis(),
13
+ unsetLink: jest.fn().mockReturnThis(),
14
+ applyLink: jest.fn().mockReturnThis(),
15
+ run: jest.fn()
16
+ },
17
+
18
+ chain() {
19
+ return this.commands;
20
+ },
21
+
22
+ getAttributes: jest.fn(() => ({
23
+ destination: destination ?? null
24
+ }))
25
+ });
26
+
27
+ function useComposable(text, destination) {
28
+ return withComponentContext(() => useLink(), {
29
+ provide: {
30
+ [InjectionTokens.EDITOR]: createEditor(text, destination),
31
+ [InjectionTokens.PAGE_BLOCKS]: ref([{ id: 1 }, { id: 2 }])
32
+
33
+ }
34
+ });
35
+ }
36
+
37
+ describe('init link', () => {
38
+ test('should prepare initial fields', () => {
39
+ const link = useComposable();
40
+
41
+ link.prepareInitialFields();
42
+
43
+ expect(link.currentDestination.value.id).toBe(LinkDestinations.URL);
44
+ expect(link.linkData.value.target).toBe(LinkTargets.SELF);
45
+ expect(link.destinationHrefs.value).toMatchSnapshot();
46
+ });
47
+
48
+ test('should take text from selection', () => {
49
+ const link = useComposable('hello world');
50
+
51
+ link.prepareInitialFields();
52
+
53
+ expect(link.linkData.value.text).toBe('hello world');
54
+ });
55
+ });
56
+
57
+ describe('actions with link', () => {
58
+ test('should update url', () => {
59
+ const link = useComposable('hello world');
60
+
61
+ link.updateLink('https://hello.world');
62
+
63
+ expect(link.destinationHrefs.value.url).toBe('https://hello.world');
64
+ });
65
+
66
+ test('should update block destination', () => {
67
+ const link = useComposable('hello world', LinkDestinations.BLOCK);
68
+
69
+ link.prepareInitialFields();
70
+ link.updateLink('3456');
71
+
72
+ expect(link.destinationHrefs.value.block).toBe('3456');
73
+ });
74
+
75
+ test('should remove link', () => {
76
+ const link = useComposable();
77
+
78
+ link.removeLink();
79
+
80
+ expect(link.editor.commands.unsetLink).toHaveBeenCalled();
81
+ });
82
+
83
+ test('should apply link', () => {
84
+ const link = useComposable();
85
+
86
+ link.updateText('hello');
87
+ link.updateLink('/world');
88
+
89
+ link.apply();
90
+
91
+ expect(link.editor.commands.applyLink).toHaveBeenCalledWith({
92
+ destination: 'url',
93
+ href: '/world',
94
+ target: LinkTargets.SELF,
95
+ text: 'hello'
96
+ });
97
+ });
98
+
99
+ test('should apply link target', () => {
100
+ const link = useComposable();
101
+
102
+ link.updateTarget(false);
103
+
104
+ expect(link.linkData.value.target).toBe(LinkTargets.SELF);
105
+ });
106
+
107
+ test('should apply link target', () => {
108
+ const link = useComposable();
109
+
110
+ link.updateTarget(true);
111
+
112
+ expect(link.linkData.value.target).toBe(LinkTargets.BLANK);
113
+ });
114
+ });
@@ -0,0 +1 @@
1
+ export { useLink } from './useLink';
@@ -0,0 +1,61 @@
1
+ import { ref, inject } from 'vue';
2
+ import { LinkTargets, LinkDestinations } from '../../../../../enums';
3
+ import { InjectionTokens } from '../../../../../injectionTokens';
4
+
5
+ export function useLink() {
6
+ const editor = inject(InjectionTokens.EDITOR);
7
+ const pageBlocks = inject(InjectionTokens.PAGE_BLOCKS);
8
+
9
+ const linkData = ref({ text: '', target: LinkTargets.SELF, destination: LinkDestinations.URL });
10
+ const destinationHrefs = ref({ block: pageBlocks.value[0].id, url: '' });
11
+ const currentDestination = ref({ id: LinkDestinations.URL });
12
+
13
+ function updateTarget(isChecked) {
14
+ linkData.value.target = isChecked ? LinkTargets.BLANK : LinkTargets.SELF;
15
+ }
16
+
17
+ function prepareInitialFields() {
18
+ linkData.value.text = editor.commands.getSelectedText();
19
+ currentDestination.value.id = editor.getAttributes('link').destination || LinkDestinations.URL;
20
+ destinationHrefs.value[currentDestination.value.id] = editor.getAttributes('link').href || '';
21
+ linkData.value.target = editor.getAttributes('link').target || LinkTargets.SELF;
22
+ }
23
+
24
+ function apply() {
25
+ editor
26
+ .chain()
27
+ .focus()
28
+ .applyLink({
29
+ href: destinationHrefs.value[currentDestination.value.id],
30
+ text: linkData.value.text,
31
+ target: linkData.value.target,
32
+ destination: currentDestination.value.id
33
+ })
34
+ .run();
35
+ }
36
+
37
+ function removeLink() {
38
+ editor.chain().focus().unsetLink().run();
39
+ }
40
+
41
+ function updateLink(value) {
42
+ destinationHrefs.value[currentDestination.value.id] = value;
43
+ }
44
+
45
+ function updateText(text) {
46
+ linkData.value.text = text;
47
+ }
48
+
49
+ return {
50
+ editor,
51
+ linkData,
52
+ destinationHrefs,
53
+ currentDestination,
54
+ prepareInitialFields,
55
+ updateTarget,
56
+ updateLink,
57
+ apply,
58
+ removeLink,
59
+ updateText
60
+ };
61
+ }