@zipify/wysiwyg 1.0.0-dev.5 → 1.0.0-dev.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +1 -1
- package/.eslintignore +1 -0
- package/.eslintrc.js +2 -2
- package/.github/dependabot.yaml +1 -0
- package/.lintstagedrc +2 -2
- package/.release-it.json +3 -1
- package/.stylelintignore +1 -0
- package/.stylelintrc +0 -4
- package/README.md +2 -2
- package/config/jest/setupTests.js +4 -0
- package/config/vite/example.config.js +25 -0
- package/config/vite/lib.config.js +30 -0
- package/config/{webpack → vite}/settings.js +0 -0
- package/dist/wysiwyg.css +1 -0
- package/dist/wysiwyg.mjs +18305 -0
- package/dist/wysiwyg.mjs.map +1 -0
- package/example/ExampleApp.vue +20 -1
- package/example/{example.html → index.html} +1 -0
- package/example/pageBlocks.js +31 -0
- package/example/presets.js +2 -2
- package/jest.config.js +3 -1
- package/lib/Wysiwyg.vue +50 -25
- package/lib/__tests__/utils/NodeFactory.js +13 -0
- package/lib/assets/icons/alignment-center.svg +3 -0
- package/lib/assets/icons/alignment-justify.svg +3 -0
- package/lib/assets/icons/alignment-left.svg +3 -0
- package/lib/assets/icons/alignment-right.svg +3 -0
- package/lib/assets/icons/arrow.svg +3 -0
- package/lib/assets/icons/background-color.svg +3 -0
- package/lib/assets/icons/case-style.svg +3 -0
- package/lib/assets/icons/font-color.svg +5 -0
- package/lib/assets/icons/italic.svg +3 -0
- package/lib/assets/icons/line-height.svg +3 -0
- package/lib/assets/icons/link.svg +3 -0
- package/lib/assets/icons/list-circle.svg +3 -0
- package/lib/assets/icons/list-decimal.svg +3 -0
- package/lib/assets/icons/list-disc.svg +3 -0
- package/lib/assets/icons/list-latin.svg +3 -0
- package/lib/assets/icons/list-roman.svg +3 -0
- package/lib/assets/icons/list-square.svg +3 -0
- package/lib/assets/icons/remove-format.svg +3 -0
- package/lib/assets/icons/reset-styles.svg +3 -0
- package/lib/assets/icons/strike-through.svg +3 -0
- package/lib/assets/icons/superscript.svg +3 -0
- package/lib/assets/icons/underline.svg +3 -0
- package/lib/assets/icons/unlink.svg +3 -0
- package/lib/components/base/Button.vue +21 -1
- package/lib/components/base/Checkbox.vue +89 -0
- package/lib/components/base/FieldLabel.vue +2 -1
- package/lib/components/base/Icon.vue +18 -10
- package/lib/components/base/Modal.vue +0 -1
- package/lib/components/base/NumberField.vue +1 -1
- package/lib/components/base/ScrollView.vue +0 -2
- package/lib/components/base/TextField.vue +106 -0
- package/lib/components/base/__tests__/Icon.test.js +6 -13
- package/lib/components/base/__tests__/Modal.test.js +6 -1
- package/lib/components/base/__tests__/TextField.test.js +57 -0
- package/lib/components/base/__tests__/__snapshots__/TextField.test.js.snap +9 -0
- package/lib/components/base/colorPicker/ColorPicker.vue +1 -1
- package/lib/components/base/colorPicker/composables/usePickerHotkeys.js +2 -1
- package/lib/components/base/composables/__tests__/useValidator.test.js +44 -0
- package/lib/components/base/composables/index.js +1 -0
- package/lib/components/base/composables/useValidator.js +23 -0
- package/lib/components/base/dropdown/Dropdown.vue +15 -3
- package/lib/components/base/dropdown/DropdownActivator.vue +19 -3
- package/lib/components/base/index.js +3 -1
- package/lib/components/toolbar/Toolbar.vue +49 -9
- package/lib/components/toolbar/ToolbarFull.vue +10 -2
- package/lib/components/toolbar/__tests__/Toolbar.test.js +6 -0
- package/lib/components/toolbar/controls/FontSizeControl.vue +7 -0
- package/lib/components/toolbar/controls/ListControl.vue +1 -5
- package/lib/components/toolbar/controls/StylePresetControl.vue +14 -1
- package/lib/components/toolbar/controls/UnderlineControl.vue +2 -2
- package/lib/components/toolbar/controls/__tests__/StylePresetControl.test.js +16 -0
- package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +4 -0
- package/lib/components/toolbar/controls/index.js +1 -0
- package/lib/components/toolbar/controls/link/LinkControl.vue +155 -0
- package/lib/components/toolbar/controls/link/LinkControlApply.vue +35 -0
- package/lib/components/toolbar/controls/link/LinkControlHeader.vue +67 -0
- package/lib/components/toolbar/controls/link/__tests__/LinkControl.test.js +79 -0
- package/lib/components/toolbar/controls/link/__tests__/LinkControlHeader.test.js +42 -0
- package/lib/components/toolbar/controls/link/composables/__tests__/__snapshots__/useLink.test.js.snap +8 -0
- package/lib/components/toolbar/controls/link/composables/__tests__/useLink.test.js +114 -0
- package/lib/components/toolbar/controls/link/composables/index.js +1 -0
- package/lib/components/toolbar/controls/link/composables/useLink.js +61 -0
- package/lib/components/toolbar/controls/link/destination/LinkControlDestination.vue +103 -0
- package/lib/components/toolbar/controls/link/destination/LinkControlPageBlock.vue +54 -0
- package/lib/components/toolbar/controls/link/destination/LinkControlUrl.vue +52 -0
- package/lib/components/toolbar/controls/link/destination/__tests__/LinkControlPageBlock.test.js +36 -0
- package/lib/components/toolbar/controls/link/destination/__tests__/LinkControlUrl.test.js +46 -0
- package/lib/components/toolbar/controls/link/destination/__tests__/__snapshots__/LinkControlPageBlock.test.js.snap +9 -0
- package/lib/components/toolbar/controls/link/destination/__tests__/__snapshots__/LinkControlUrl.test.js.snap +17 -0
- package/lib/components/toolbar/controls/link/destination/index.js +1 -0
- package/lib/components/toolbar/controls/link/index.js +1 -0
- package/lib/composables/__tests__/useEditor.test.js +2 -2
- package/lib/composables/useEditor.js +5 -7
- package/lib/composables/useToolbar.js +23 -28
- package/lib/directives/__tests__/outClick.test.js +6 -0
- package/lib/directives/outClick.js +19 -6
- package/lib/enums/Alignments.js +10 -1
- package/lib/enums/LinkDestinations.js +4 -0
- package/lib/enums/LinkTargets.js +4 -0
- package/lib/enums/TextSettings.js +3 -1
- package/lib/enums/index.js +2 -0
- package/lib/extensions/Alignment.js +21 -7
- package/lib/extensions/BackgroundColor.js +14 -6
- package/lib/extensions/DeviceManager.js +0 -4
- package/lib/extensions/FontColor.js +14 -6
- package/lib/extensions/FontFamily.js +25 -8
- package/lib/extensions/FontSize.js +31 -12
- package/lib/extensions/FontStyle.js +23 -13
- package/lib/extensions/FontWeight.js +24 -14
- package/lib/extensions/LineHeight.js +31 -28
- package/lib/extensions/Link.js +89 -0
- package/lib/extensions/StylePreset.js +16 -15
- package/lib/extensions/TextDecoration.js +45 -12
- package/lib/extensions/__tests__/Alignment.test.js +11 -5
- package/lib/extensions/__tests__/BackgroundColor.test.js +11 -5
- package/lib/extensions/__tests__/CaseStyle.test.js +3 -5
- package/lib/extensions/__tests__/FontColor.test.js +11 -5
- package/lib/extensions/__tests__/FontFamily.test.js +32 -7
- package/lib/extensions/__tests__/FontSize.test.js +13 -6
- package/lib/extensions/__tests__/FontStyle.test.js +11 -5
- package/lib/extensions/__tests__/FontWeight.test.js +19 -5
- package/lib/extensions/__tests__/LineHeight.test.js +23 -11
- package/lib/extensions/__tests__/Link.test.js +102 -0
- package/lib/extensions/__tests__/StylePreset.test.js +70 -6
- package/lib/extensions/__tests__/TextDecoration.test.js +51 -5
- package/lib/extensions/__tests__/__snapshots__/Alignment.test.js.snap +26 -2
- package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +30 -1
- package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +18 -1
- package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +88 -1
- package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +33 -2
- package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +25 -4
- package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +47 -1
- package/lib/extensions/__tests__/__snapshots__/LineHeight.test.js.snap +26 -2
- package/lib/extensions/__tests__/__snapshots__/Link.test.js.snap +225 -0
- package/lib/extensions/__tests__/__snapshots__/StylePreset.test.js.snap +6 -2
- package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +183 -3
- package/lib/extensions/core/CopyPasteProcessor.js +10 -0
- package/lib/extensions/core/TextProcessor.js +10 -0
- package/lib/extensions/core/__tests__/NodeProcessor.test.js +3 -5
- package/lib/extensions/core/__tests__/SelectionProcessor.test.js +3 -5
- package/lib/extensions/core/__tests__/TextProcessor.test.js +138 -12
- package/lib/extensions/core/__tests__/__snapshots__/TextProcessor.test.js.snap +26 -0
- package/lib/extensions/core/index.js +11 -2
- package/lib/extensions/core/plugins/PastePlugin.js +57 -0
- package/lib/extensions/core/plugins/ProseMirrorPlugin.js +20 -0
- package/lib/extensions/core/plugins/index.js +1 -0
- package/lib/extensions/index.js +46 -34
- package/lib/extensions/list/__tests__/List.test.js +3 -5
- package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +45 -15
- package/lib/injectionTokens.js +2 -1
- package/lib/services/ContentNormalizer.js +113 -29
- package/lib/services/ContextWidnow.js +23 -0
- package/lib/services/__tests__/ContentNormalizer.test.js +75 -7
- package/lib/services/index.js +1 -0
- package/lib/styles/content.css +102 -13
- package/lib/styles/helpers/offsets.css +16 -0
- package/lib/styles/variables.css +6 -0
- package/lib/utils/__tests__/__snapshots__/renderInlineSetting.test.js.snap +4 -4
- package/lib/utils/__tests__/convertAlignment.test.js +16 -0
- package/lib/utils/__tests__/convertFontSize.test.js +21 -0
- package/lib/utils/__tests__/convertLineHeight.test.js +21 -0
- package/lib/utils/convertAlignment.js +12 -0
- package/lib/utils/convertColor.js +1 -1
- package/lib/utils/convertFontSize.js +8 -0
- package/lib/utils/convertLineHeight.js +17 -0
- package/lib/utils/importIcon.js +13 -0
- package/lib/utils/index.js +4 -0
- package/lib/utils/renderInlineSetting.js +1 -1
- package/package.json +21 -25
- package/config/webpack/example.config.js +0 -86
- package/config/webpack/loaders/index.js +0 -6
- package/config/webpack/loaders/js-loader.js +0 -5
- package/config/webpack/loaders/style-loader.js +0 -7
- package/config/webpack/loaders/svg-loader.js +0 -4
- package/config/webpack/loaders/vue-loader.js +0 -4
- package/lib/assets/icons.svg +0 -69
- package/lib/composables/__tests__/useToolbar.test.js +0 -56
package/lib/extensions/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import History from '@tiptap/extension-history';
|
|
2
|
-
import { CORE_EXTENSIONS } from './core';
|
|
3
1
|
import { FontFamily } from './FontFamily';
|
|
4
2
|
import { StylePreset } from './StylePreset';
|
|
5
3
|
import { FontWeight } from './FontWeight';
|
|
@@ -13,38 +11,52 @@ import { CaseStyle } from './CaseStyle';
|
|
|
13
11
|
import { Alignment } from './Alignment';
|
|
14
12
|
import { LineHeight } from './LineHeight';
|
|
15
13
|
import { List } from './list';
|
|
14
|
+
import { Link } from './Link';
|
|
16
15
|
import { Superscript } from './Superscript';
|
|
16
|
+
import { buildCoreExtensions } from './core';
|
|
17
17
|
|
|
18
|
-
export
|
|
18
|
+
export function buildExtensions(options) {
|
|
19
|
+
const getPresetById = (id) => options.presetsRef.value.find((preset) => preset.id === id);
|
|
20
|
+
const defaultPreset = getPresetById(options.defaultPresetId);
|
|
21
|
+
const linkPreset = getPresetById('link');
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
23
|
+
return buildCoreExtensions(options).concat([
|
|
24
|
+
StylePreset.configure({
|
|
25
|
+
presetsRef: options.presetsRef,
|
|
26
|
+
defaultId: options.defaultPresetId,
|
|
27
|
+
baseClass: options.basePresetClass,
|
|
28
|
+
makeVariable: options.makePresetVariable
|
|
29
|
+
}),
|
|
30
|
+
List.configure({
|
|
31
|
+
baseClass: options.baseListClass
|
|
32
|
+
}),
|
|
33
|
+
DeviceManager.configure({
|
|
34
|
+
deviceRef: options.deviceRef
|
|
35
|
+
}),
|
|
36
|
+
FontFamily.configure({
|
|
37
|
+
fonts: options.fonts,
|
|
38
|
+
defaultFont: defaultPreset.common.font_family
|
|
39
|
+
}),
|
|
40
|
+
FontWeight,
|
|
41
|
+
FontSize.configure({
|
|
42
|
+
minSize: options.minFontSize,
|
|
43
|
+
maxSize: options.maxFontSize,
|
|
44
|
+
wrapperRef: options.wrapperRef
|
|
45
|
+
}),
|
|
46
|
+
FontColor,
|
|
47
|
+
BackgroundColor,
|
|
48
|
+
FontStyle,
|
|
49
|
+
TextDecoration,
|
|
50
|
+
CaseStyle,
|
|
51
|
+
Superscript,
|
|
52
|
+
Alignment,
|
|
53
|
+
LineHeight.configure({
|
|
54
|
+
wrapperRef: options.wrapperRef
|
|
55
|
+
}),
|
|
56
|
+
Link.configure({
|
|
57
|
+
preset: linkPreset,
|
|
58
|
+
basePresetClass: options.basePresetClass,
|
|
59
|
+
pageBlocksRef: options.pageBlocksRef
|
|
60
|
+
})
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
@@ -2,18 +2,16 @@ import { Editor } from '@tiptap/vue-2';
|
|
|
2
2
|
import { ref } from '@vue/composition-api';
|
|
3
3
|
import { NodeFactory } from '../../../__tests__/utils';
|
|
4
4
|
import { ListTypes } from '../../../enums';
|
|
5
|
-
import { CORE_EXTENSIONS } from '../../core';
|
|
6
5
|
import { StylePreset } from '../../StylePreset';
|
|
7
6
|
import { List } from '../List';
|
|
8
7
|
import { ContentNormalizer } from '../../../services';
|
|
8
|
+
import { buildCoreExtensions } from '../../core';
|
|
9
9
|
|
|
10
10
|
function createEditor({ content }) {
|
|
11
|
-
const normalizer = new ContentNormalizer();
|
|
12
|
-
|
|
13
11
|
return new Editor({
|
|
14
|
-
content:
|
|
12
|
+
content: ContentNormalizer.normalize(content),
|
|
15
13
|
element: document.createElement('div'),
|
|
16
|
-
extensions:
|
|
14
|
+
extensions: buildCoreExtensions().concat(
|
|
17
15
|
List.configure({
|
|
18
16
|
baseClass: 'zw-list--'
|
|
19
17
|
}),
|
|
@@ -92,7 +92,9 @@ Object {
|
|
|
92
92
|
"content": Array [
|
|
93
93
|
Object {
|
|
94
94
|
"attrs": Object {
|
|
95
|
-
"preset":
|
|
95
|
+
"preset": Object {
|
|
96
|
+
"id": "regular-1",
|
|
97
|
+
},
|
|
96
98
|
},
|
|
97
99
|
"content": Array [
|
|
98
100
|
Object {
|
|
@@ -104,7 +106,9 @@ Object {
|
|
|
104
106
|
},
|
|
105
107
|
Object {
|
|
106
108
|
"attrs": Object {
|
|
107
|
-
"preset":
|
|
109
|
+
"preset": Object {
|
|
110
|
+
"id": "regular-1",
|
|
111
|
+
},
|
|
108
112
|
},
|
|
109
113
|
"content": Array [
|
|
110
114
|
Object {
|
|
@@ -221,7 +225,9 @@ Object {
|
|
|
221
225
|
"content": Array [
|
|
222
226
|
Object {
|
|
223
227
|
"attrs": Object {
|
|
224
|
-
"preset":
|
|
228
|
+
"preset": Object {
|
|
229
|
+
"id": "regular-1",
|
|
230
|
+
},
|
|
225
231
|
},
|
|
226
232
|
"content": Array [
|
|
227
233
|
Object {
|
|
@@ -256,7 +262,9 @@ Object {
|
|
|
256
262
|
"content": Array [
|
|
257
263
|
Object {
|
|
258
264
|
"attrs": Object {
|
|
259
|
-
"preset":
|
|
265
|
+
"preset": Object {
|
|
266
|
+
"id": "regular-1",
|
|
267
|
+
},
|
|
260
268
|
},
|
|
261
269
|
"content": Array [
|
|
262
270
|
Object {
|
|
@@ -291,7 +299,9 @@ Object {
|
|
|
291
299
|
"content": Array [
|
|
292
300
|
Object {
|
|
293
301
|
"attrs": Object {
|
|
294
|
-
"preset":
|
|
302
|
+
"preset": Object {
|
|
303
|
+
"id": "regular-1",
|
|
304
|
+
},
|
|
295
305
|
},
|
|
296
306
|
"content": Array [
|
|
297
307
|
Object {
|
|
@@ -326,7 +336,9 @@ Object {
|
|
|
326
336
|
"content": Array [
|
|
327
337
|
Object {
|
|
328
338
|
"attrs": Object {
|
|
329
|
-
"preset":
|
|
339
|
+
"preset": Object {
|
|
340
|
+
"id": "regular-1",
|
|
341
|
+
},
|
|
330
342
|
},
|
|
331
343
|
"content": Array [
|
|
332
344
|
Object {
|
|
@@ -361,7 +373,9 @@ Object {
|
|
|
361
373
|
"content": Array [
|
|
362
374
|
Object {
|
|
363
375
|
"attrs": Object {
|
|
364
|
-
"preset":
|
|
376
|
+
"preset": Object {
|
|
377
|
+
"id": "regular-1",
|
|
378
|
+
},
|
|
365
379
|
},
|
|
366
380
|
"content": Array [
|
|
367
381
|
Object {
|
|
@@ -396,7 +410,9 @@ Object {
|
|
|
396
410
|
"content": Array [
|
|
397
411
|
Object {
|
|
398
412
|
"attrs": Object {
|
|
399
|
-
"preset":
|
|
413
|
+
"preset": Object {
|
|
414
|
+
"id": "regular-1",
|
|
415
|
+
},
|
|
400
416
|
},
|
|
401
417
|
"content": Array [
|
|
402
418
|
Object {
|
|
@@ -431,7 +447,9 @@ Object {
|
|
|
431
447
|
"content": Array [
|
|
432
448
|
Object {
|
|
433
449
|
"attrs": Object {
|
|
434
|
-
"preset":
|
|
450
|
+
"preset": Object {
|
|
451
|
+
"id": "regular-1",
|
|
452
|
+
},
|
|
435
453
|
},
|
|
436
454
|
"content": Array [
|
|
437
455
|
Object {
|
|
@@ -466,7 +484,9 @@ Object {
|
|
|
466
484
|
"content": Array [
|
|
467
485
|
Object {
|
|
468
486
|
"attrs": Object {
|
|
469
|
-
"preset":
|
|
487
|
+
"preset": Object {
|
|
488
|
+
"id": "regular-1",
|
|
489
|
+
},
|
|
470
490
|
},
|
|
471
491
|
"content": Array [
|
|
472
492
|
Object {
|
|
@@ -501,7 +521,9 @@ Object {
|
|
|
501
521
|
"content": Array [
|
|
502
522
|
Object {
|
|
503
523
|
"attrs": Object {
|
|
504
|
-
"preset":
|
|
524
|
+
"preset": Object {
|
|
525
|
+
"id": "regular-1",
|
|
526
|
+
},
|
|
505
527
|
},
|
|
506
528
|
"content": Array [
|
|
507
529
|
Object {
|
|
@@ -536,7 +558,9 @@ Object {
|
|
|
536
558
|
"content": Array [
|
|
537
559
|
Object {
|
|
538
560
|
"attrs": Object {
|
|
539
|
-
"preset":
|
|
561
|
+
"preset": Object {
|
|
562
|
+
"id": "regular-1",
|
|
563
|
+
},
|
|
540
564
|
},
|
|
541
565
|
"content": Array [
|
|
542
566
|
Object {
|
|
@@ -571,7 +595,9 @@ Object {
|
|
|
571
595
|
"content": Array [
|
|
572
596
|
Object {
|
|
573
597
|
"attrs": Object {
|
|
574
|
-
"preset":
|
|
598
|
+
"preset": Object {
|
|
599
|
+
"id": "regular-1",
|
|
600
|
+
},
|
|
575
601
|
},
|
|
576
602
|
"content": Array [
|
|
577
603
|
Object {
|
|
@@ -606,7 +632,9 @@ Object {
|
|
|
606
632
|
"content": Array [
|
|
607
633
|
Object {
|
|
608
634
|
"attrs": Object {
|
|
609
|
-
"preset":
|
|
635
|
+
"preset": Object {
|
|
636
|
+
"id": "regular-1",
|
|
637
|
+
},
|
|
610
638
|
},
|
|
611
639
|
"content": Array [
|
|
612
640
|
Object {
|
|
@@ -641,7 +669,9 @@ Object {
|
|
|
641
669
|
"content": Array [
|
|
642
670
|
Object {
|
|
643
671
|
"attrs": Object {
|
|
644
|
-
"preset":
|
|
672
|
+
"preset": Object {
|
|
673
|
+
"id": "regular-1",
|
|
674
|
+
},
|
|
645
675
|
},
|
|
646
676
|
"content": Array [
|
|
647
677
|
Object {
|
package/lib/injectionTokens.js
CHANGED
|
@@ -3,5 +3,6 @@ export const InjectionTokens = Object.freeze({
|
|
|
3
3
|
FONT_SIZES: Symbol('fontSizes'),
|
|
4
4
|
EDITOR: Symbol('editor'),
|
|
5
5
|
LOCAL_STORAGE: Symbol('localStorage'),
|
|
6
|
-
FAVORITE_COLORS: Symbol('favoriteColors')
|
|
6
|
+
FAVORITE_COLORS: Symbol('favoriteColors'),
|
|
7
|
+
PAGE_BLOCKS: Symbol('pageBlocks')
|
|
7
8
|
});
|
|
@@ -1,42 +1,103 @@
|
|
|
1
|
+
import { ContextWindow } from './ContextWidnow';
|
|
2
|
+
|
|
1
3
|
export class ContentNormalizer {
|
|
2
|
-
static
|
|
4
|
+
static BLOCK_STYLES = ['text-align', 'line-height'];
|
|
5
|
+
static BLOCK_NODE_NAMES = ['P', 'H1', 'H2', 'H3', 'H4'];
|
|
6
|
+
|
|
7
|
+
static ASSIGN_STYLE_RULES = [
|
|
8
|
+
{
|
|
9
|
+
tag: /^(b|strong)$/,
|
|
10
|
+
ignore: /font-weight/
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
tag: /^i$/,
|
|
14
|
+
ignore: /font-style/
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
tag: /^s$/,
|
|
18
|
+
ignore: /text-decoration(.+)?/
|
|
19
|
+
}
|
|
20
|
+
];
|
|
3
21
|
|
|
4
|
-
normalize(content) {
|
|
5
|
-
|
|
22
|
+
static normalize(content) {
|
|
23
|
+
return new ContentNormalizer(content).normalize();
|
|
24
|
+
}
|
|
6
25
|
|
|
7
|
-
|
|
26
|
+
constructor(content) {
|
|
27
|
+
this._content = content;
|
|
28
|
+
this._dom = null;
|
|
8
29
|
}
|
|
9
30
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
31
|
+
normalize() {
|
|
32
|
+
if (typeof this._content !== 'string') {
|
|
33
|
+
return this._content;
|
|
34
|
+
}
|
|
35
|
+
return this._normalizeTextContent();
|
|
36
|
+
}
|
|
13
37
|
|
|
14
|
-
|
|
15
|
-
this.
|
|
38
|
+
_normalizeTextContent() {
|
|
39
|
+
this._dom = new DOMParser().parseFromString(this._content, 'text/html');
|
|
16
40
|
|
|
17
|
-
|
|
41
|
+
this._iterateNodes(this._removeEmptyNodes, this._isBlockNode);
|
|
42
|
+
this._iterateNodes(this._normalizeListItems, (node) => node.tagName === 'LI');
|
|
43
|
+
this._iterateNodes(this._normalizeSettingsStructure, (node) => node.tagName === 'SPAN');
|
|
44
|
+
this._iterateNodes(this._normalizeStyles, (node) => node.tagName !== 'SPAN');
|
|
45
|
+
|
|
46
|
+
return this._dom.body.innerHTML;
|
|
18
47
|
}
|
|
19
48
|
|
|
20
|
-
_iterateNodes(
|
|
21
|
-
const iterator =
|
|
22
|
-
acceptNode: (node) => condition(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
|
|
49
|
+
_iterateNodes(handler, condition = () => true) {
|
|
50
|
+
const iterator = this._dom.createNodeIterator(this._dom.body, NodeFilter.SHOW_ELEMENT, {
|
|
51
|
+
acceptNode: (node) => condition.call(this, node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
|
|
23
52
|
});
|
|
24
53
|
let currentNode = iterator.nextNode();
|
|
25
54
|
|
|
26
55
|
while (currentNode) {
|
|
27
|
-
handler(currentNode);
|
|
56
|
+
handler.call(this, currentNode);
|
|
28
57
|
currentNode = iterator.nextNode();
|
|
29
58
|
}
|
|
30
59
|
}
|
|
31
60
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
.
|
|
61
|
+
_removeEmptyNodes(node) {
|
|
62
|
+
if (node.childNodes.length === 0) {
|
|
63
|
+
node.remove();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_normalizeListItems(itemEl) {
|
|
68
|
+
const fragment = new DocumentFragment();
|
|
69
|
+
const children = Array.from(itemEl.childNodes);
|
|
70
|
+
let capturingParagraph;
|
|
35
71
|
|
|
36
|
-
|
|
72
|
+
const append = (node) => {
|
|
73
|
+
this._assignElementProperties(node, itemEl, ContentNormalizer.BLOCK_STYLES);
|
|
74
|
+
fragment.append(node);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
for (const node of children) {
|
|
78
|
+
if (node.tagName === 'P') {
|
|
79
|
+
append(node);
|
|
80
|
+
capturingParagraph = null;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!capturingParagraph) {
|
|
85
|
+
capturingParagraph = document.createElement('p');
|
|
86
|
+
append(capturingParagraph);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
capturingParagraph.append(node);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
itemEl.append(fragment);
|
|
93
|
+
this._removeStyleProperties(itemEl, ContentNormalizer.BLOCK_STYLES);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_normalizeSettingsStructure(element) {
|
|
97
|
+
if (this._isOnlyTextContent(element)) return;
|
|
37
98
|
|
|
38
99
|
const cloned = element.cloneNode(true);
|
|
39
|
-
const migratingStyles = this._getMigratingStyles(element);
|
|
100
|
+
const migratingStyles = this._getMigratingStyles(element, { customProperties: true });
|
|
40
101
|
const content = [];
|
|
41
102
|
|
|
42
103
|
for (const node of cloned.childNodes) {
|
|
@@ -54,8 +115,12 @@ export class ContentNormalizer {
|
|
|
54
115
|
}
|
|
55
116
|
|
|
56
117
|
_normalizeStyles(element) {
|
|
118
|
+
if (!this._isBlockNode(element) && this._isOnlyTextContent(element)) return;
|
|
119
|
+
|
|
57
120
|
const properties = this._getMigratingStyles(element);
|
|
58
121
|
|
|
122
|
+
if (!properties.length) return;
|
|
123
|
+
|
|
59
124
|
for (const node of element.childNodes) {
|
|
60
125
|
const child = this._wrapTextNode(element, node);
|
|
61
126
|
|
|
@@ -65,16 +130,25 @@ export class ContentNormalizer {
|
|
|
65
130
|
this._removeStyleProperties(element, properties);
|
|
66
131
|
}
|
|
67
132
|
|
|
68
|
-
|
|
69
|
-
|
|
133
|
+
_isOnlyTextContent(node) {
|
|
134
|
+
return Array.from(node.childNodes).every((node) => node.nodeType === Node.TEXT_NODE);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
_isBlockNode(node) {
|
|
138
|
+
return ContentNormalizer.BLOCK_NODE_NAMES.includes(node.tagName);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
_getMigratingStyles(element, { customProperties } = {}) {
|
|
142
|
+
const blacklist = ContentNormalizer.BLOCK_STYLES;
|
|
70
143
|
const properties = [];
|
|
71
144
|
|
|
72
145
|
for (let index = 0; index < element.style.length; index++) {
|
|
73
146
|
const property = element.style.item(index);
|
|
74
147
|
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
148
|
+
if (blacklist.includes(property)) continue;
|
|
149
|
+
if (!customProperties && property.startsWith('--')) continue;
|
|
150
|
+
|
|
151
|
+
properties.push(property);
|
|
78
152
|
}
|
|
79
153
|
|
|
80
154
|
return properties;
|
|
@@ -83,7 +157,7 @@ export class ContentNormalizer {
|
|
|
83
157
|
_wrapTextNode(parent, node) {
|
|
84
158
|
if (node.nodeType !== Node.TEXT_NODE) return node;
|
|
85
159
|
|
|
86
|
-
const span = document.createElement('span');
|
|
160
|
+
const span = ContextWindow.document.createElement('span');
|
|
87
161
|
|
|
88
162
|
span.append(node.cloneNode());
|
|
89
163
|
parent.replaceChild(span, node);
|
|
@@ -93,14 +167,24 @@ export class ContentNormalizer {
|
|
|
93
167
|
|
|
94
168
|
_assignElementProperties(target, source, properties) {
|
|
95
169
|
for (const property of properties) {
|
|
96
|
-
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
170
|
+
const value = source.style.getPropertyValue(property);
|
|
99
171
|
|
|
100
|
-
|
|
172
|
+
if (value && this._canAssignElementProperty(target, source, property)) {
|
|
173
|
+
target.style.setProperty(property, value);
|
|
174
|
+
}
|
|
101
175
|
}
|
|
102
176
|
}
|
|
103
177
|
|
|
178
|
+
_canAssignElementProperty(target, source, property) {
|
|
179
|
+
if (target.style.getPropertyValue(property)) return false;
|
|
180
|
+
|
|
181
|
+
return ContentNormalizer.ASSIGN_STYLE_RULES.every((rule) => {
|
|
182
|
+
if (!rule.tag.test(target.tagName.toLowerCase())) return true;
|
|
183
|
+
|
|
184
|
+
return !rule.ignore.test(property);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
104
188
|
_removeStyleProperties(element, properties) {
|
|
105
189
|
for (const property of properties) {
|
|
106
190
|
element.style.removeProperty(property);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export class ContextWindow {
|
|
2
|
+
static window = window;
|
|
3
|
+
|
|
4
|
+
static use(window) {
|
|
5
|
+
this.window = window;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static get document() {
|
|
9
|
+
return this.window.document;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static get body() {
|
|
13
|
+
return this.document.body;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static get head() {
|
|
17
|
+
return this.document.head;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getComputedStyle(element) {
|
|
21
|
+
return this.window.getComputedStyle(element);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { ContentNormalizer } from '../ContentNormalizer';
|
|
2
2
|
import { NodeFactory } from '../../__tests__/utils';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
describe('normalize text settings', () => {
|
|
4
|
+
describe('normalize text content', () => {
|
|
7
5
|
test('should ignore json content', () => {
|
|
8
6
|
const content = NodeFactory.doc([NodeFactory.paragraph('Test')]);
|
|
9
7
|
|
|
10
|
-
expect(normalize(content)).toBe(content);
|
|
8
|
+
expect(ContentNormalizer.normalize(content)).toBe(content);
|
|
11
9
|
});
|
|
12
10
|
|
|
13
11
|
test('should flat structure', () => {
|
|
@@ -18,14 +16,14 @@ describe('normalize text settings', () => {
|
|
|
18
16
|
'<span style="background-color: rgb(255, 0, 0); color: rgb(255, 255, 255);">sum</span>' +
|
|
19
17
|
'</p>';
|
|
20
18
|
|
|
21
|
-
expect(normalize(input)).toBe(output);
|
|
19
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
22
20
|
});
|
|
23
21
|
|
|
24
22
|
test('should move styles from paragraph to text', () => {
|
|
25
23
|
const input = '<p style="background-color: red;">lorem ipsum</p>';
|
|
26
24
|
const output = '<p><span style="background-color: red;">lorem ipsum</span></p>';
|
|
27
25
|
|
|
28
|
-
expect(normalize(input)).toBe(output);
|
|
26
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
29
27
|
});
|
|
30
28
|
|
|
31
29
|
test('should move styles from paragraph to unstyled text', () => {
|
|
@@ -36,6 +34,76 @@ describe('normalize text settings', () => {
|
|
|
36
34
|
'<span style="color: white; background-color: red;">one</span' +
|
|
37
35
|
'></p>';
|
|
38
36
|
|
|
39
|
-
expect(normalize(input)).toBe(output);
|
|
37
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should wrap list content in paragraph', () => {
|
|
41
|
+
const input = '<ul><li style="line-height: 2;">lorem impsum</li></ul>';
|
|
42
|
+
const output = '<ul><li><p style="line-height: 2;">lorem impsum</p></li></ul>';
|
|
43
|
+
|
|
44
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should keep order in list nodes', () => {
|
|
48
|
+
const input = '<ul><li style="line-height: 2;"><span style="font-weight: 700">lorem</span> impsum</li></ul>';
|
|
49
|
+
const output = '<ul>' +
|
|
50
|
+
'<li>' +
|
|
51
|
+
'<p style="line-height: 2;"><span style="font-weight: 700">lorem</span> impsum</p>' +
|
|
52
|
+
'</li>' +
|
|
53
|
+
'</ul>';
|
|
54
|
+
|
|
55
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('should wrap non paragraph list content', () => {
|
|
59
|
+
const input = '<ul>' +
|
|
60
|
+
'<li style="line-height: 2;">' +
|
|
61
|
+
'<span style="font-weight: 700">lorem</span> impsum' +
|
|
62
|
+
'<p>paragraph text</p>' +
|
|
63
|
+
'</li>' +
|
|
64
|
+
'</ul>';
|
|
65
|
+
const output = '<ul>' +
|
|
66
|
+
'<li>' +
|
|
67
|
+
'<p style="line-height: 2;"><span style="font-weight: 700">lorem</span> impsum</p>' +
|
|
68
|
+
'<p style="line-height: 2;">paragraph text</p>' +
|
|
69
|
+
'</li>' +
|
|
70
|
+
'</ul>';
|
|
71
|
+
|
|
72
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('should ignore empty nodes', () => {
|
|
76
|
+
const input = '<p>lorem ipsum 1</p><p></p><p>lorem ipsum 2</p>';
|
|
77
|
+
const output = '<p>lorem ipsum 1</p><p>lorem ipsum 2</p>';
|
|
78
|
+
|
|
79
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('should not ignore setting', () => {
|
|
83
|
+
const input = '<p style="text-decoration-line: underline;">lorem ipsum</p>';
|
|
84
|
+
const output = '<p><span style="text-decoration-line: underline;">lorem ipsum</span></p>';
|
|
85
|
+
|
|
86
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('should not assign font-weight to b tag', () => {
|
|
90
|
+
const input = '<p style="font-weight: 400;"><b>lorem ipsum</b></p>';
|
|
91
|
+
const output = '<p><b>lorem ipsum</b></p>';
|
|
92
|
+
|
|
93
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('should not assign font-style to i tag', () => {
|
|
97
|
+
const input = '<p style="font-style: normal;"><i>lorem ipsum</i></p>';
|
|
98
|
+
const output = '<p><i>lorem ipsum</i></p>';
|
|
99
|
+
|
|
100
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should not assign text-decoration to s tag', () => {
|
|
104
|
+
const input = '<p style="text-decoration-line: initial;"><s>lorem ipsum</s></p>';
|
|
105
|
+
const output = '<p><s>lorem ipsum</s></p>';
|
|
106
|
+
|
|
107
|
+
expect(ContentNormalizer.normalize(input)).toBe(output);
|
|
40
108
|
});
|
|
41
109
|
});
|
package/lib/services/index.js
CHANGED