@zipify/wysiwyg 1.0.0-dev.2 → 1.0.0-dev.5

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 (60) hide show
  1. package/README.md +3 -1
  2. package/config/jest/setupTests.js +3 -1
  3. package/example/ExampleApp.vue +39 -31
  4. package/example/example.js +26 -0
  5. package/example/presets.js +2 -0
  6. package/lib/Wysiwyg.vue +6 -0
  7. package/lib/composables/__tests__/useEditor.test.js +12 -3
  8. package/lib/composables/useEditor.js +12 -5
  9. package/lib/extensions/Alignment.js +6 -0
  10. package/lib/extensions/BackgroundColor.js +8 -1
  11. package/lib/extensions/FontColor.js +8 -1
  12. package/lib/extensions/FontFamily.js +7 -0
  13. package/lib/extensions/FontSize.js +12 -0
  14. package/lib/extensions/FontStyle.js +11 -0
  15. package/lib/extensions/FontWeight.js +25 -1
  16. package/lib/extensions/LineHeight.js +17 -0
  17. package/lib/extensions/StylePreset.js +30 -3
  18. package/lib/extensions/TextDecoration.js +11 -0
  19. package/lib/extensions/__tests__/Alignment.test.js +22 -1
  20. package/lib/extensions/__tests__/BackgroundColor.test.js +30 -1
  21. package/lib/extensions/__tests__/CaseStyle.test.js +4 -1
  22. package/lib/extensions/__tests__/FontColor.test.js +30 -1
  23. package/lib/extensions/__tests__/FontFamily.test.js +30 -1
  24. package/lib/extensions/__tests__/FontSize.test.js +30 -1
  25. package/lib/extensions/__tests__/FontStyle.test.js +38 -1
  26. package/lib/extensions/__tests__/FontWeight.test.js +58 -1
  27. package/lib/extensions/__tests__/LineHeight.test.js +41 -1
  28. package/lib/extensions/__tests__/StylePreset.test.js +76 -1
  29. package/lib/extensions/__tests__/TextDecoration.test.js +63 -1
  30. package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +44 -0
  31. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +91 -0
  32. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +91 -0
  33. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +91 -0
  34. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +99 -0
  35. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +120 -0
  36. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +149 -0
  37. package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +92 -0
  38. package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +167 -2
  39. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +207 -0
  40. package/lib/extensions/core/__tests__/NodeProcessor.test.js +4 -1
  41. package/lib/extensions/core/__tests__/SelectionProcessor.test.js +9 -4
  42. package/lib/extensions/core/__tests__/TextProcessor.test.js +4 -1
  43. package/lib/extensions/index.js +1 -0
  44. package/lib/extensions/list/List.js +34 -0
  45. package/lib/extensions/list/__tests__/List.test.js +115 -3
  46. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +457 -6
  47. package/lib/services/ContentNormalizer.js +113 -0
  48. package/lib/services/Storage.js +1 -13
  49. package/lib/services/__tests__/ContentNormalizer.test.js +41 -0
  50. package/lib/services/__tests__/FavoriteColors.test.js +20 -0
  51. package/lib/services/__tests__/JsonSerializer.test.js +23 -0
  52. package/lib/services/__tests__/Storage.test.js +79 -0
  53. package/lib/services/index.js +1 -0
  54. package/lib/utils/__tests__/convertColor.test.js +19 -0
  55. package/lib/utils/__tests__/createKeyboardShortcut.test.js +25 -0
  56. package/lib/utils/__tests__/renderInlineSetting.test.js +26 -0
  57. package/lib/utils/convertColor.js +7 -0
  58. package/lib/utils/index.js +1 -0
  59. package/lib/utils/renderInlineSetting.js +1 -1
  60. package/package.json +4 -4
@@ -92,9 +92,7 @@ Object {
92
92
  "content": Array [
93
93
  Object {
94
94
  "attrs": Object {
95
- "preset": Object {
96
- "id": "regular-1",
97
- },
95
+ "preset": null,
98
96
  },
99
97
  "content": Array [
100
98
  Object {
@@ -106,9 +104,7 @@ Object {
106
104
  },
107
105
  Object {
108
106
  "attrs": Object {
109
- "preset": Object {
110
- "id": "regular-1",
111
- },
107
+ "preset": null,
112
108
  },
113
109
  "content": Array [
114
110
  Object {
@@ -210,3 +206,458 @@ Object {
210
206
  "type": "doc",
211
207
  }
212
208
  `;
209
+
210
+ exports[`parsing html should parse circle type by class 1`] = `
211
+ Object {
212
+ "content": Array [
213
+ Object {
214
+ "attrs": Object {
215
+ "bullet": Object {
216
+ "type": "circle",
217
+ },
218
+ },
219
+ "content": Array [
220
+ Object {
221
+ "content": Array [
222
+ Object {
223
+ "attrs": Object {
224
+ "preset": null,
225
+ },
226
+ "content": Array [
227
+ Object {
228
+ "text": "lorem ipsum",
229
+ "type": "text",
230
+ },
231
+ ],
232
+ "type": "paragraph",
233
+ },
234
+ ],
235
+ "type": "listItem",
236
+ },
237
+ ],
238
+ "type": "list",
239
+ },
240
+ ],
241
+ "type": "doc",
242
+ }
243
+ `;
244
+
245
+ exports[`parsing html should parse decimal as default 1`] = `
246
+ Object {
247
+ "content": Array [
248
+ Object {
249
+ "attrs": Object {
250
+ "bullet": Object {
251
+ "type": "decimal",
252
+ },
253
+ },
254
+ "content": Array [
255
+ Object {
256
+ "content": Array [
257
+ Object {
258
+ "attrs": Object {
259
+ "preset": null,
260
+ },
261
+ "content": Array [
262
+ Object {
263
+ "text": "lorem ipsum",
264
+ "type": "text",
265
+ },
266
+ ],
267
+ "type": "paragraph",
268
+ },
269
+ ],
270
+ "type": "listItem",
271
+ },
272
+ ],
273
+ "type": "list",
274
+ },
275
+ ],
276
+ "type": "doc",
277
+ }
278
+ `;
279
+
280
+ exports[`parsing html should parse decimal type by attribute 1`] = `
281
+ Object {
282
+ "content": Array [
283
+ Object {
284
+ "attrs": Object {
285
+ "bullet": Object {
286
+ "type": "decimal",
287
+ },
288
+ },
289
+ "content": Array [
290
+ Object {
291
+ "content": Array [
292
+ Object {
293
+ "attrs": Object {
294
+ "preset": null,
295
+ },
296
+ "content": Array [
297
+ Object {
298
+ "text": "lorem ipsum",
299
+ "type": "text",
300
+ },
301
+ ],
302
+ "type": "paragraph",
303
+ },
304
+ ],
305
+ "type": "listItem",
306
+ },
307
+ ],
308
+ "type": "list",
309
+ },
310
+ ],
311
+ "type": "doc",
312
+ }
313
+ `;
314
+
315
+ exports[`parsing html should parse decimal type by class 1`] = `
316
+ Object {
317
+ "content": Array [
318
+ Object {
319
+ "attrs": Object {
320
+ "bullet": Object {
321
+ "type": "decimal",
322
+ },
323
+ },
324
+ "content": Array [
325
+ Object {
326
+ "content": Array [
327
+ Object {
328
+ "attrs": Object {
329
+ "preset": null,
330
+ },
331
+ "content": Array [
332
+ Object {
333
+ "text": "lorem ipsum",
334
+ "type": "text",
335
+ },
336
+ ],
337
+ "type": "paragraph",
338
+ },
339
+ ],
340
+ "type": "listItem",
341
+ },
342
+ ],
343
+ "type": "list",
344
+ },
345
+ ],
346
+ "type": "doc",
347
+ }
348
+ `;
349
+
350
+ exports[`parsing html should parse disc as default 1`] = `
351
+ Object {
352
+ "content": Array [
353
+ Object {
354
+ "attrs": Object {
355
+ "bullet": Object {
356
+ "type": "disc",
357
+ },
358
+ },
359
+ "content": Array [
360
+ Object {
361
+ "content": Array [
362
+ Object {
363
+ "attrs": Object {
364
+ "preset": null,
365
+ },
366
+ "content": Array [
367
+ Object {
368
+ "text": "lorem ipsum",
369
+ "type": "text",
370
+ },
371
+ ],
372
+ "type": "paragraph",
373
+ },
374
+ ],
375
+ "type": "listItem",
376
+ },
377
+ ],
378
+ "type": "list",
379
+ },
380
+ ],
381
+ "type": "doc",
382
+ }
383
+ `;
384
+
385
+ exports[`parsing html should parse disc type by class 1`] = `
386
+ Object {
387
+ "content": Array [
388
+ Object {
389
+ "attrs": Object {
390
+ "bullet": Object {
391
+ "type": "disc",
392
+ },
393
+ },
394
+ "content": Array [
395
+ Object {
396
+ "content": Array [
397
+ Object {
398
+ "attrs": Object {
399
+ "preset": null,
400
+ },
401
+ "content": Array [
402
+ Object {
403
+ "text": "lorem ipsum",
404
+ "type": "text",
405
+ },
406
+ ],
407
+ "type": "paragraph",
408
+ },
409
+ ],
410
+ "type": "listItem",
411
+ },
412
+ ],
413
+ "type": "list",
414
+ },
415
+ ],
416
+ "type": "doc",
417
+ }
418
+ `;
419
+
420
+ exports[`parsing html should parse latin type by class 1`] = `
421
+ Object {
422
+ "content": Array [
423
+ Object {
424
+ "attrs": Object {
425
+ "bullet": Object {
426
+ "type": "latin",
427
+ },
428
+ },
429
+ "content": Array [
430
+ Object {
431
+ "content": Array [
432
+ Object {
433
+ "attrs": Object {
434
+ "preset": null,
435
+ },
436
+ "content": Array [
437
+ Object {
438
+ "text": "lorem ipsum",
439
+ "type": "text",
440
+ },
441
+ ],
442
+ "type": "paragraph",
443
+ },
444
+ ],
445
+ "type": "listItem",
446
+ },
447
+ ],
448
+ "type": "list",
449
+ },
450
+ ],
451
+ "type": "doc",
452
+ }
453
+ `;
454
+
455
+ exports[`parsing html should parse latin type by lower attribute 1`] = `
456
+ Object {
457
+ "content": Array [
458
+ Object {
459
+ "attrs": Object {
460
+ "bullet": Object {
461
+ "type": "latin",
462
+ },
463
+ },
464
+ "content": Array [
465
+ Object {
466
+ "content": Array [
467
+ Object {
468
+ "attrs": Object {
469
+ "preset": null,
470
+ },
471
+ "content": Array [
472
+ Object {
473
+ "text": "lorem ipsum",
474
+ "type": "text",
475
+ },
476
+ ],
477
+ "type": "paragraph",
478
+ },
479
+ ],
480
+ "type": "listItem",
481
+ },
482
+ ],
483
+ "type": "list",
484
+ },
485
+ ],
486
+ "type": "doc",
487
+ }
488
+ `;
489
+
490
+ exports[`parsing html should parse latin type by upper attribute 1`] = `
491
+ Object {
492
+ "content": Array [
493
+ Object {
494
+ "attrs": Object {
495
+ "bullet": Object {
496
+ "type": "latin",
497
+ },
498
+ },
499
+ "content": Array [
500
+ Object {
501
+ "content": Array [
502
+ Object {
503
+ "attrs": Object {
504
+ "preset": null,
505
+ },
506
+ "content": Array [
507
+ Object {
508
+ "text": "lorem ipsum",
509
+ "type": "text",
510
+ },
511
+ ],
512
+ "type": "paragraph",
513
+ },
514
+ ],
515
+ "type": "listItem",
516
+ },
517
+ ],
518
+ "type": "list",
519
+ },
520
+ ],
521
+ "type": "doc",
522
+ }
523
+ `;
524
+
525
+ exports[`parsing html should parse roman type by class 1`] = `
526
+ Object {
527
+ "content": Array [
528
+ Object {
529
+ "attrs": Object {
530
+ "bullet": Object {
531
+ "type": "roman",
532
+ },
533
+ },
534
+ "content": Array [
535
+ Object {
536
+ "content": Array [
537
+ Object {
538
+ "attrs": Object {
539
+ "preset": null,
540
+ },
541
+ "content": Array [
542
+ Object {
543
+ "text": "lorem ipsum",
544
+ "type": "text",
545
+ },
546
+ ],
547
+ "type": "paragraph",
548
+ },
549
+ ],
550
+ "type": "listItem",
551
+ },
552
+ ],
553
+ "type": "list",
554
+ },
555
+ ],
556
+ "type": "doc",
557
+ }
558
+ `;
559
+
560
+ exports[`parsing html should parse roman type by lower attribute 1`] = `
561
+ Object {
562
+ "content": Array [
563
+ Object {
564
+ "attrs": Object {
565
+ "bullet": Object {
566
+ "type": "roman",
567
+ },
568
+ },
569
+ "content": Array [
570
+ Object {
571
+ "content": Array [
572
+ Object {
573
+ "attrs": Object {
574
+ "preset": null,
575
+ },
576
+ "content": Array [
577
+ Object {
578
+ "text": "lorem ipsum",
579
+ "type": "text",
580
+ },
581
+ ],
582
+ "type": "paragraph",
583
+ },
584
+ ],
585
+ "type": "listItem",
586
+ },
587
+ ],
588
+ "type": "list",
589
+ },
590
+ ],
591
+ "type": "doc",
592
+ }
593
+ `;
594
+
595
+ exports[`parsing html should parse roman type by upper attribute 1`] = `
596
+ Object {
597
+ "content": Array [
598
+ Object {
599
+ "attrs": Object {
600
+ "bullet": Object {
601
+ "type": "roman",
602
+ },
603
+ },
604
+ "content": Array [
605
+ Object {
606
+ "content": Array [
607
+ Object {
608
+ "attrs": Object {
609
+ "preset": null,
610
+ },
611
+ "content": Array [
612
+ Object {
613
+ "text": "lorem ipsum",
614
+ "type": "text",
615
+ },
616
+ ],
617
+ "type": "paragraph",
618
+ },
619
+ ],
620
+ "type": "listItem",
621
+ },
622
+ ],
623
+ "type": "list",
624
+ },
625
+ ],
626
+ "type": "doc",
627
+ }
628
+ `;
629
+
630
+ exports[`parsing html should parse square type by class 1`] = `
631
+ Object {
632
+ "content": Array [
633
+ Object {
634
+ "attrs": Object {
635
+ "bullet": Object {
636
+ "type": "square",
637
+ },
638
+ },
639
+ "content": Array [
640
+ Object {
641
+ "content": Array [
642
+ Object {
643
+ "attrs": Object {
644
+ "preset": null,
645
+ },
646
+ "content": Array [
647
+ Object {
648
+ "text": "lorem ipsum",
649
+ "type": "text",
650
+ },
651
+ ],
652
+ "type": "paragraph",
653
+ },
654
+ ],
655
+ "type": "listItem",
656
+ },
657
+ ],
658
+ "type": "list",
659
+ },
660
+ ],
661
+ "type": "doc",
662
+ }
663
+ `;
@@ -0,0 +1,113 @@
1
+ export class ContentNormalizer {
2
+ static NORMALIZING_STYLE_BLACKLIST = ['text-align', 'line-height'];
3
+
4
+ normalize(content) {
5
+ if (typeof content !== 'string') return content;
6
+
7
+ return this._normalizeTextContent(content);
8
+ }
9
+
10
+ _normalizeTextContent(content) {
11
+ const parser = new DOMParser();
12
+ const dom = parser.parseFromString(content, 'text/html');
13
+
14
+ this._iterateNodes(dom, this._normalizeStructure.bind(this), (node) => node.tagName === 'SPAN');
15
+ this._iterateNodes(dom, this._normalizeStyles.bind(this), (node) => node.tagName !== 'SPAN');
16
+
17
+ return dom.body.innerHTML;
18
+ }
19
+
20
+ _iterateNodes(dom, handler, condition) {
21
+ const iterator = dom.createNodeIterator(dom.body, NodeFilter.SHOW_ELEMENT, {
22
+ acceptNode: (node) => condition(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
23
+ });
24
+ let currentNode = iterator.nextNode();
25
+
26
+ while (currentNode) {
27
+ handler(currentNode);
28
+ currentNode = iterator.nextNode();
29
+ }
30
+ }
31
+
32
+ _normalizeStructure(element) {
33
+ const isTextChildren = Array.from(element.childNodes)
34
+ .every((node) => node.nodeType === Node.TEXT_NODE);
35
+
36
+ if (isTextChildren) return;
37
+
38
+ const cloned = element.cloneNode(true);
39
+ const migratingStyles = this._getMigratingStyles(element);
40
+ const content = [];
41
+
42
+ for (const node of cloned.childNodes) {
43
+ let child = node;
44
+
45
+ if (migratingStyles.length) {
46
+ child = this._wrapTextNode(cloned, node);
47
+ this._assignElementProperties(child, cloned, migratingStyles);
48
+ }
49
+
50
+ content.push(child);
51
+ }
52
+
53
+ element.replaceWith(...content);
54
+ }
55
+
56
+ _normalizeStyles(element) {
57
+ const properties = this._getMigratingStyles(element);
58
+
59
+ for (const node of element.childNodes) {
60
+ const child = this._wrapTextNode(element, node);
61
+
62
+ this._assignElementProperties(child, element, properties);
63
+ }
64
+
65
+ this._removeStyleProperties(element, properties);
66
+ }
67
+
68
+ _getMigratingStyles(element) {
69
+ const blacklist = ContentNormalizer.NORMALIZING_STYLE_BLACKLIST;
70
+ const properties = [];
71
+
72
+ for (let index = 0; index < element.style.length; index++) {
73
+ const property = element.style.item(index);
74
+
75
+ if (!blacklist.includes(property)) {
76
+ properties.push(property);
77
+ }
78
+ }
79
+
80
+ return properties;
81
+ }
82
+
83
+ _wrapTextNode(parent, node) {
84
+ if (node.nodeType !== Node.TEXT_NODE) return node;
85
+
86
+ const span = document.createElement('span');
87
+
88
+ span.append(node.cloneNode());
89
+ parent.replaceChild(span, node);
90
+
91
+ return span;
92
+ }
93
+
94
+ _assignElementProperties(target, source, properties) {
95
+ for (const property of properties) {
96
+ if (target.style.getPropertyValue(property)) {
97
+ continue;
98
+ }
99
+
100
+ target.style.setProperty(property, source.style.getPropertyValue(property));
101
+ }
102
+ }
103
+
104
+ _removeStyleProperties(element, properties) {
105
+ for (const property of properties) {
106
+ element.style.removeProperty(property);
107
+ }
108
+
109
+ if (element.style.length === 0) {
110
+ element.removeAttribute('style');
111
+ }
112
+ }
113
+ }
@@ -17,19 +17,11 @@ export class Storage {
17
17
  }
18
18
 
19
19
  getItem(key) {
20
- const json = this._getItemFromStorage(key);
20
+ const json = this.accessToStorage((storage) => storage.getItem(this.makeKey(key)));
21
21
 
22
22
  return JsonSerializer.decode(json);
23
23
  }
24
24
 
25
- removeItem(key) {
26
- this.accessToStorage((storage) => storage.removeItem(this.makeKey(key)));
27
- }
28
-
29
- hasItem(key) {
30
- return !!this._getItemFromStorage(key);
31
- }
32
-
33
25
  accessToStorage(action) {
34
26
  try {
35
27
  return action(this.nativeStorage);
@@ -38,10 +30,6 @@ export class Storage {
38
30
  }
39
31
  }
40
32
 
41
- _getItemFromStorage(key) {
42
- return this.accessToStorage((storage) => storage.getItem(this.makeKey(key)));
43
- }
44
-
45
33
  onPrivateModeEnabled(error) {
46
34
  // eslint-disable-next-line no-console
47
35
  console.error(error);
@@ -0,0 +1,41 @@
1
+ import { ContentNormalizer } from '../ContentNormalizer';
2
+ import { NodeFactory } from '../../__tests__/utils';
3
+
4
+ const normalize = (content) => new ContentNormalizer().normalize(content);
5
+
6
+ describe('normalize text settings', () => {
7
+ test('should ignore json content', () => {
8
+ const content = NodeFactory.doc([NodeFactory.paragraph('Test')]);
9
+
10
+ expect(normalize(content)).toBe(content);
11
+ });
12
+
13
+ test('should flat structure', () => {
14
+ const input = '<p style="text-align: center; color: rgb(255, 255, 255);"><span style="background-color: rgb(255, 0, 0);">lore<span style="color: rgb(0, 0, 0);">m ip</span>sum</span></p>';
15
+ const output = '<p style="text-align: center;">' +
16
+ '<span style="background-color: rgb(255, 0, 0); color: rgb(255, 255, 255);">lore</span>' +
17
+ '<span style="color: rgb(0, 0, 0); background-color: rgb(255, 0, 0);">m ip</span>' +
18
+ '<span style="background-color: rgb(255, 0, 0); color: rgb(255, 255, 255);">sum</span>' +
19
+ '</p>';
20
+
21
+ expect(normalize(input)).toBe(output);
22
+ });
23
+
24
+ test('should move styles from paragraph to text', () => {
25
+ const input = '<p style="background-color: red;">lorem ipsum</p>';
26
+ const output = '<p><span style="background-color: red;">lorem ipsum</span></p>';
27
+
28
+ expect(normalize(input)).toBe(output);
29
+ });
30
+
31
+ test('should move styles from paragraph to unstyled text', () => {
32
+ const input = '<p style="background-color: red;"><span style="background-color: #000;">lorem</span> ipsum <span style="color: white;">one</span></p>';
33
+ const output = '<p>' +
34
+ '<span style="background-color: #000;">lorem</span>' +
35
+ '<span style="background-color: red;"> ipsum </span>' +
36
+ '<span style="color: white; background-color: red;">one</span' +
37
+ '></p>';
38
+
39
+ expect(normalize(input)).toBe(output);
40
+ });
41
+ });
@@ -0,0 +1,20 @@
1
+ import { ref } from '@vue/composition-api';
2
+ import { FavoriteColors } from '../FavoriteColors';
3
+
4
+ function createService({ colors, onUpdate }) {
5
+ return new FavoriteColors({
6
+ listRef: ref(colors ?? []),
7
+ triggerUpdate: onUpdate
8
+ });
9
+ }
10
+
11
+ describe('trigger update', () => {
12
+ test('should trigger colors update', () => {
13
+ const onUpdate = jest.fn();
14
+ const service = createService({ onUpdate });
15
+
16
+ service.triggerUpdate(['red']);
17
+
18
+ expect(onUpdate).toHaveBeenCalledWith(['red']);
19
+ });
20
+ });
@@ -0,0 +1,23 @@
1
+ import { JsonSerializer } from '../JsonSerializer';
2
+
3
+ describe('decode', () => {
4
+ test('should decode json', () => {
5
+ const json = JsonSerializer.decode('{ "test": 1 }');
6
+
7
+ expect(json).toEqual({ test: 1 });
8
+ });
9
+
10
+ test('should return null on error', () => {
11
+ const json = JsonSerializer.decode('{ "tes');
12
+
13
+ expect(json).toBe(null);
14
+ });
15
+ });
16
+
17
+ describe('encode', () => {
18
+ test('should encode object to json', () => {
19
+ const json = JsonSerializer.encode({ test: 1 });
20
+
21
+ expect(json).toBe('{"test":1}');
22
+ });
23
+ });