@zipify/wysiwyg 1.0.0-dev.41 → 1.0.0-dev.44

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 (51) hide show
  1. package/.lintstagedrc +1 -1
  2. package/config/vite/example.config.js +25 -0
  3. package/dist/wysiwyg.css +1 -1
  4. package/dist/wysiwyg.mjs +3272 -3219
  5. package/example/ExampleApp.vue +5 -0
  6. package/example/{example.html → index.html} +1 -0
  7. package/lib/Wysiwyg.vue +1 -4
  8. package/lib/__tests__/utils/NodeFactory.js +13 -0
  9. package/lib/components/base/__tests__/Modal.test.js +2 -3
  10. package/lib/components/base/composables/__tests__/useValidator.test.js +44 -0
  11. package/lib/components/base/composables/useValidator.js +7 -3
  12. package/lib/components/toolbar/__tests__/Toolbar.test.js +2 -3
  13. package/lib/components/toolbar/controls/StylePresetControl.vue +14 -1
  14. package/lib/components/toolbar/controls/__tests__/StylePresetControl.test.js +16 -0
  15. package/lib/components/toolbar/controls/link/LinkControl.vue +28 -25
  16. package/lib/components/toolbar/controls/link/__tests__/LinkControl.test.js +79 -0
  17. package/lib/components/toolbar/controls/link/__tests__/LinkControlHeader.test.js +42 -0
  18. package/lib/components/toolbar/controls/link/composables/__tests__/__snapshots__/useLink.test.js.snap +8 -0
  19. package/lib/components/toolbar/controls/link/composables/__tests__/useLink.test.js +114 -0
  20. package/lib/components/toolbar/controls/link/destination/LinkControlDestination.vue +2 -2
  21. package/lib/components/toolbar/controls/link/destination/LinkControlUrl.vue +2 -2
  22. package/lib/components/toolbar/controls/link/destination/__tests__/LinkControlPageBlock.test.js +36 -0
  23. package/lib/components/toolbar/controls/link/destination/__tests__/LinkControlUrl.test.js +46 -0
  24. package/lib/components/toolbar/controls/link/destination/__tests__/__snapshots__/LinkControlPageBlock.test.js.snap +9 -0
  25. package/lib/components/toolbar/controls/link/destination/__tests__/__snapshots__/LinkControlUrl.test.js.snap +17 -0
  26. package/lib/composables/useToolbar.js +12 -0
  27. package/lib/directives/__tests__/outClick.test.js +6 -0
  28. package/lib/directives/outClick.js +12 -15
  29. package/lib/extensions/FontWeight.js +3 -1
  30. package/lib/extensions/LineHeight.js +3 -2
  31. package/lib/extensions/Link.js +3 -15
  32. package/lib/extensions/TextDecoration.js +18 -0
  33. package/lib/extensions/__tests__/FontWeight.test.js +8 -0
  34. package/lib/extensions/__tests__/Link.test.js +102 -0
  35. package/lib/extensions/__tests__/TextDecoration.test.js +24 -0
  36. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +17 -0
  37. package/lib/extensions/__tests__/__snapshots__/Link.test.js.snap +225 -0
  38. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +90 -0
  39. package/lib/extensions/core/plugins/PastePlugin.js +23 -14
  40. package/lib/extensions/index.js +5 -3
  41. package/lib/services/ContentNormalizer.js +55 -15
  42. package/lib/services/__tests__/ContentNormalizer.test.js +39 -4
  43. package/package.json +4 -14
  44. package/config/webpack/example.config.js +0 -88
  45. package/config/webpack/lib.config.js +0 -43
  46. package/config/webpack/loaders/index.js +0 -6
  47. package/config/webpack/loaders/js-loader.js +0 -5
  48. package/config/webpack/loaders/style-loader.js +0 -9
  49. package/config/webpack/loaders/svg-loader.js +0 -4
  50. package/config/webpack/loaders/vue-loader.js +0 -4
  51. package/config/webpack/settings.js +0 -9
@@ -0,0 +1,17 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`update link should update link target 1`] = `
4
+ Array [
5
+ Array [
6
+ true,
7
+ ],
8
+ ]
9
+ `;
10
+
11
+ exports[`update link should update link url 1`] = `
12
+ Array [
13
+ Array [
14
+ "Some text",
15
+ ],
16
+ ]
17
+ `;
@@ -12,6 +12,18 @@ export function useToolbar({ wrapperRef, offsets, isActiveRef }) {
12
12
  {
13
13
  name: 'offset',
14
14
  options: { offset: offsets }
15
+ },
16
+ {
17
+ name: 'preventOverflow',
18
+ options: {
19
+ altAxis: true,
20
+ padding: 2
21
+ }
22
+ },
23
+
24
+ {
25
+ name: 'flip',
26
+ enabled: false
15
27
  }
16
28
  ]
17
29
  });
@@ -10,6 +10,7 @@ describe('out click', () => {
10
10
  await waitAsyncOperation();
11
11
 
12
12
  document.dispatchEvent(new Event('click'));
13
+ await waitAsyncOperation();
13
14
 
14
15
  expect(onOutClick).toHaveBeenCalled();
15
16
  });
@@ -22,6 +23,7 @@ describe('out click', () => {
22
23
  await waitAsyncOperation();
23
24
 
24
25
  el.dispatchEvent(new Event('click'));
26
+ await waitAsyncOperation();
25
27
 
26
28
  expect(onOutClick).not.toHaveBeenCalled();
27
29
  });
@@ -36,6 +38,7 @@ describe('out click', () => {
36
38
  await waitAsyncOperation();
37
39
 
38
40
  child.dispatchEvent(new Event('click'));
41
+ await waitAsyncOperation();
39
42
 
40
43
  expect(onOutClick).not.toHaveBeenCalled();
41
44
  });
@@ -50,6 +53,7 @@ describe('out click', () => {
50
53
  outClick.unbind(el);
51
54
 
52
55
  document.dispatchEvent(new Event('click'));
56
+ await waitAsyncOperation();
53
57
 
54
58
  expect(onOutClick).not.toHaveBeenCalled();
55
59
  });
@@ -65,6 +69,7 @@ describe('out click', () => {
65
69
  await waitAsyncOperation();
66
70
 
67
71
  document.dispatchEvent(new Event('click'));
72
+ await waitAsyncOperation();
68
73
 
69
74
  expect(onOutClick).toHaveBeenCalled();
70
75
  });
@@ -80,6 +85,7 @@ describe('out click', () => {
80
85
  await waitAsyncOperation();
81
86
 
82
87
  document.dispatchEvent(new Event('click'));
88
+ await waitAsyncOperation();
83
89
 
84
90
  expect(onOutClick).not.toHaveBeenCalled();
85
91
  });
@@ -2,18 +2,15 @@ import { ContextWindow } from '../services';
2
2
 
3
3
  const dataStorage = new WeakMap();
4
4
 
5
- function attachListener(onClick) {
6
- if (ContextWindow.window !== window) {
7
- window.document.addEventListener('click', onClick);
8
- }
9
- ContextWindow.document.addEventListener('click', onClick);
10
- }
5
+ function toggleListener(toEnable, onClick) {
6
+ const args = ['click', onClick, { capture: true }];
7
+ const action = toEnable ? 'addEventListener' : 'removeEventListener';
11
8
 
12
- function removeListener(onClick) {
13
9
  if (ContextWindow.window !== window) {
14
- window.document.removeEventListener('click', onClick);
10
+ window.document[action](...args);
15
11
  }
16
- ContextWindow.document.removeEventListener('click', onClick);
12
+
13
+ ContextWindow.document[action](...args);
17
14
  }
18
15
 
19
16
  export const outClick = {
@@ -24,13 +21,15 @@ export const outClick = {
24
21
  function onClick(event) {
25
22
  const isInside = event.target === el || el.contains(event.target);
26
23
 
27
- if (event.target.isConnected && !isInside) onOutClick(event);
24
+ if (event.target.isConnected && !isInside) {
25
+ setTimeout(() => onOutClick(event));
26
+ }
28
27
  }
29
28
 
30
29
  dataStorage.set(el, { callback: onClick, isEnabled });
31
30
 
32
31
  if (isEnabled) {
33
- setTimeout(() => attachListener(onClick));
32
+ setTimeout(() => toggleListener(true, onClick));
34
33
  }
35
34
  },
36
35
 
@@ -40,14 +39,12 @@ export const outClick = {
40
39
 
41
40
  if (isEnabled === data.isEnabled) return;
42
41
 
43
- isEnabled
44
- ? attachListener(data.callback)
45
- : removeListener(data.callback);
42
+ toggleListener(isEnabled, data.callback);
46
43
 
47
44
  dataStorage.set(el, { callback: data.callback, isEnabled });
48
45
  },
49
46
 
50
47
  unbind(el) {
51
- removeListener(dataStorage.get(el).callback);
48
+ toggleListener(false, dataStorage.get(el).callback);
52
49
  }
53
50
  };
@@ -60,7 +60,9 @@ export const FontWeight = Mark.create({
60
60
  }),
61
61
 
62
62
  parseHTML() {
63
- const getAttrs = (value) => ({ value });
63
+ const getAttrs = (value) => {
64
+ return Number(value) ? { value } : false;
65
+ };
64
66
 
65
67
  return [
66
68
  {
@@ -38,8 +38,9 @@ export const LineHeight = Extension.create({
38
38
  }
39
39
 
40
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;
41
+ const source = element.firstElementChild || element;
42
+ const sourceSize = source.style.fontSize;
43
+ const fontSize = sourceSize || ContextWindow.getComputedStyle(ContextWindow.body).fontSize;
43
44
  const relative = (parseFloat(value) / parseFloat(fontSize)).toFixed(2);
44
45
 
45
46
  return { desktop: relative, tablet: relative, mobile: relative };
@@ -8,13 +8,7 @@ export const Link = Base.extend({
8
8
  addOptions() {
9
9
  return {
10
10
  ...this.parent?.(),
11
- openOnClick: false,
12
- HTMLAttributes: {
13
- target: LinkTargets.SELF
14
- },
15
- preset: {},
16
- basePresetClass: null,
17
- pageBlocks: []
11
+ openOnClick: false
18
12
  };
19
13
  },
20
14
 
@@ -32,14 +26,8 @@ export const Link = Base.extend({
32
26
  },
33
27
 
34
28
  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
- }
29
+ default: LinkTargets.SELF,
30
+ parseHTML: (element) => element.getAttribute('target') || LinkTargets.SELF
43
31
  },
44
32
 
45
33
  destination: {
@@ -108,6 +108,24 @@ export const TextDecoration = Mark.create({
108
108
  {
109
109
  style: 'text-decoration-line',
110
110
  getAttrs
111
+ },
112
+ {
113
+ style: 'text-decoration',
114
+ getAttrs
115
+ },
116
+ {
117
+ tag: 's',
118
+ attrs: {
119
+ underline: false,
120
+ strike_through: true
121
+ }
122
+ },
123
+ {
124
+ tag: 'u',
125
+ attrs: {
126
+ underline: true,
127
+ strike_through: false
128
+ }
111
129
  }
112
130
  ];
113
131
  },
@@ -211,4 +211,12 @@ describe('parsing html', () => {
211
211
 
212
212
  expect(editor.getJSON()).toMatchSnapshot();
213
213
  });
214
+
215
+ test('should ignore invalid value', () => {
216
+ const editor = createEditor({
217
+ content: '<p><span style="font-weight: var(--font-weight)">lorem</span> ipsum</p>'
218
+ });
219
+
220
+ expect(editor.getJSON()).toMatchSnapshot();
221
+ });
214
222
  });
@@ -0,0 +1,102 @@
1
+ import { ref } from 'vue';
2
+ import { Editor } from '@tiptap/vue-2';
3
+ import { ContentNormalizer } from '../../services';
4
+ import { Link } from '../Link';
5
+ import { NodeFactory } from '../../__tests__/utils';
6
+ import { buildCoreExtensions } from '../core';
7
+
8
+ function createEditor({ content }) {
9
+ return new Editor({
10
+ content: ContentNormalizer.normalize(content),
11
+ extensions: buildCoreExtensions().concat(
12
+ Link.configure({
13
+ preset: 'link',
14
+ baseClass: 'zw ts-',
15
+ pageBlocks: ref([{ id: 987654 }])
16
+ })
17
+ )
18
+ });
19
+ }
20
+
21
+ const createContent = (text) => NodeFactory.doc([
22
+ NodeFactory.paragraph([text])
23
+ ]);
24
+
25
+ const linkAttributes = (href) => ({
26
+ href: href ?? '/test',
27
+ text: 'Hello world link',
28
+ target: '_self',
29
+ destination: 'url'
30
+ });
31
+
32
+ describe('apply link', () => {
33
+ test('should apply link', () => {
34
+ const editor = createEditor({
35
+ content: createContent(NodeFactory.text('hello world'))
36
+ });
37
+
38
+ editor.chain().selectAll().applyLink(linkAttributes()).run();
39
+
40
+ expect(editor.getJSON()).toMatchSnapshot();
41
+ });
42
+
43
+ test('should apply link when no selected text', () => {
44
+ const editor = createEditor({
45
+ content: createContent(NodeFactory.text('Some text'))
46
+ });
47
+
48
+ editor.chain().setTextSelection(6).applyLink(linkAttributes()).run();
49
+
50
+ expect(editor.getJSON()).toMatchSnapshot();
51
+ });
52
+ });
53
+
54
+ describe('parse html', () => {
55
+ test('should parse url links from html', () => {
56
+ const editor = createEditor({
57
+ content: '<a href="https://hello.world" target="_blank">Hello</a>'
58
+ });
59
+
60
+ expect(editor.getJSON()).toMatchSnapshot();
61
+ });
62
+
63
+ test('should parse block links from html', () => {
64
+ const editor = createEditor({
65
+ content: '<a href="#987654" target="_blank">Hello</a>'
66
+ });
67
+
68
+ expect(editor.getJSON()).toMatchSnapshot();
69
+ });
70
+
71
+ test('should parse link target', () => {
72
+ const editor = createEditor({
73
+ content: '<a href="https://hello.world" target="_blank">Hello</a>'
74
+ });
75
+
76
+ expect(editor.getJSON()).toMatchSnapshot();
77
+ });
78
+
79
+ test('should add default self target for links from html without target', () => {
80
+ const editor = createEditor({
81
+ content: '<a href="https://hello.world">Hello</a>'
82
+ });
83
+
84
+ expect(editor.getJSON()).toMatchSnapshot();
85
+ });
86
+
87
+ test('should parse value for relative url', () => {
88
+ const editor = createEditor({
89
+ content: '<a href="/hello-world">Hello</a>'
90
+ });
91
+
92
+ expect(editor.getJSON()).toMatchSnapshot();
93
+ });
94
+
95
+ test('should parse not valid value', () => {
96
+ const editor = createEditor({
97
+ content: '<a href="934238">Hello</a>'
98
+ });
99
+
100
+ expect(editor.getJSON()).toMatchSnapshot();
101
+ });
102
+ });
@@ -324,6 +324,14 @@ describe('parsing html', () => {
324
324
  expect(editor.getJSON()).toMatchSnapshot();
325
325
  });
326
326
 
327
+ test('should get both from text with property shorthand', () => {
328
+ const editor = createEditor({
329
+ content: '<p><span style="text-decoration: underline line-through">lorem</span> ipsum</p>'
330
+ });
331
+
332
+ expect(editor.getJSON()).toMatchSnapshot();
333
+ });
334
+
327
335
  test('should get both from rendered view', () => {
328
336
  const editor = createEditor({
329
337
  content: '<p><span style="--zw-text-decoration: underline line-through">lorem</span> ipsum</p>'
@@ -339,4 +347,20 @@ describe('parsing html', () => {
339
347
 
340
348
  expect(editor.getJSON()).toMatchSnapshot();
341
349
  });
350
+
351
+ test('should parse underline from tag', () => {
352
+ const editor = createEditor({
353
+ content: '<p><u>lorem</u> ipsum</p>'
354
+ });
355
+
356
+ expect(editor.getJSON()).toMatchSnapshot();
357
+ });
358
+
359
+ test('should parse strike through from tag', () => {
360
+ const editor = createEditor({
361
+ content: '<p><s>lorem</s> ipsum</p>'
362
+ });
363
+
364
+ expect(editor.getJSON()).toMatchSnapshot();
365
+ });
342
366
  });
@@ -278,6 +278,23 @@ Object {
278
278
  }
279
279
  `;
280
280
 
281
+ exports[`parsing html should ignore invalid value 1`] = `
282
+ Object {
283
+ "content": Array [
284
+ Object {
285
+ "content": Array [
286
+ Object {
287
+ "text": "lorem ipsum",
288
+ "type": "text",
289
+ },
290
+ ],
291
+ "type": "paragraph",
292
+ },
293
+ ],
294
+ "type": "doc",
295
+ }
296
+ `;
297
+
281
298
  exports[`parsing html should merge paragraph and text settings 1`] = `
282
299
  Object {
283
300
  "content": Array [
@@ -0,0 +1,225 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`apply link should apply link 1`] = `
4
+ Object {
5
+ "content": Array [
6
+ Object {
7
+ "content": Array [
8
+ Object {
9
+ "marks": Array [
10
+ Object {
11
+ "attrs": Object {
12
+ "destination": "url",
13
+ "href": "/test",
14
+ "target": "_self",
15
+ },
16
+ "type": "link",
17
+ },
18
+ ],
19
+ "text": "Hello world link",
20
+ "type": "text",
21
+ },
22
+ ],
23
+ "type": "paragraph",
24
+ },
25
+ ],
26
+ "type": "doc",
27
+ }
28
+ `;
29
+
30
+ exports[`apply link should apply link when no selected text 1`] = `
31
+ Object {
32
+ "content": Array [
33
+ Object {
34
+ "content": Array [
35
+ Object {
36
+ "text": "Some ",
37
+ "type": "text",
38
+ },
39
+ Object {
40
+ "marks": Array [
41
+ Object {
42
+ "attrs": Object {
43
+ "destination": "url",
44
+ "href": "/test",
45
+ "target": "_self",
46
+ },
47
+ "type": "link",
48
+ },
49
+ ],
50
+ "text": "Hello world link",
51
+ "type": "text",
52
+ },
53
+ Object {
54
+ "text": " text",
55
+ "type": "text",
56
+ },
57
+ ],
58
+ "type": "paragraph",
59
+ },
60
+ ],
61
+ "type": "doc",
62
+ }
63
+ `;
64
+
65
+ exports[`parse html should add default self target for links from html without target 1`] = `
66
+ Object {
67
+ "content": Array [
68
+ Object {
69
+ "content": Array [
70
+ Object {
71
+ "marks": Array [
72
+ Object {
73
+ "attrs": Object {
74
+ "destination": "url",
75
+ "href": "https://hello.world",
76
+ "target": "_self",
77
+ },
78
+ "type": "link",
79
+ },
80
+ ],
81
+ "text": "Hello",
82
+ "type": "text",
83
+ },
84
+ ],
85
+ "type": "paragraph",
86
+ },
87
+ ],
88
+ "type": "doc",
89
+ }
90
+ `;
91
+
92
+ exports[`parse html should parse block links from html 1`] = `
93
+ Object {
94
+ "content": Array [
95
+ Object {
96
+ "content": Array [
97
+ Object {
98
+ "marks": Array [
99
+ Object {
100
+ "attrs": Object {
101
+ "destination": "block",
102
+ "href": 987654,
103
+ "target": "_blank",
104
+ },
105
+ "type": "link",
106
+ },
107
+ ],
108
+ "text": "Hello",
109
+ "type": "text",
110
+ },
111
+ ],
112
+ "type": "paragraph",
113
+ },
114
+ ],
115
+ "type": "doc",
116
+ }
117
+ `;
118
+
119
+ exports[`parse html should parse link target 1`] = `
120
+ Object {
121
+ "content": Array [
122
+ Object {
123
+ "content": Array [
124
+ Object {
125
+ "marks": Array [
126
+ Object {
127
+ "attrs": Object {
128
+ "destination": "url",
129
+ "href": "https://hello.world",
130
+ "target": "_blank",
131
+ },
132
+ "type": "link",
133
+ },
134
+ ],
135
+ "text": "Hello",
136
+ "type": "text",
137
+ },
138
+ ],
139
+ "type": "paragraph",
140
+ },
141
+ ],
142
+ "type": "doc",
143
+ }
144
+ `;
145
+
146
+ exports[`parse html should parse not valid value 1`] = `
147
+ Object {
148
+ "content": Array [
149
+ Object {
150
+ "content": Array [
151
+ Object {
152
+ "marks": Array [
153
+ Object {
154
+ "attrs": Object {
155
+ "destination": "url",
156
+ "href": "934238",
157
+ "target": "_self",
158
+ },
159
+ "type": "link",
160
+ },
161
+ ],
162
+ "text": "Hello",
163
+ "type": "text",
164
+ },
165
+ ],
166
+ "type": "paragraph",
167
+ },
168
+ ],
169
+ "type": "doc",
170
+ }
171
+ `;
172
+
173
+ exports[`parse html should parse url links from html 1`] = `
174
+ Object {
175
+ "content": Array [
176
+ Object {
177
+ "content": Array [
178
+ Object {
179
+ "marks": Array [
180
+ Object {
181
+ "attrs": Object {
182
+ "destination": "url",
183
+ "href": "https://hello.world",
184
+ "target": "_blank",
185
+ },
186
+ "type": "link",
187
+ },
188
+ ],
189
+ "text": "Hello",
190
+ "type": "text",
191
+ },
192
+ ],
193
+ "type": "paragraph",
194
+ },
195
+ ],
196
+ "type": "doc",
197
+ }
198
+ `;
199
+
200
+ exports[`parse html should parse value for relative url 1`] = `
201
+ Object {
202
+ "content": Array [
203
+ Object {
204
+ "content": Array [
205
+ Object {
206
+ "marks": Array [
207
+ Object {
208
+ "attrs": Object {
209
+ "destination": "url",
210
+ "href": "/hello-world",
211
+ "target": "_self",
212
+ },
213
+ "type": "link",
214
+ },
215
+ ],
216
+ "text": "Hello",
217
+ "type": "text",
218
+ },
219
+ ],
220
+ "type": "paragraph",
221
+ },
222
+ ],
223
+ "type": "doc",
224
+ }
225
+ `;