@zipify/wysiwyg 2.0.0-1 → 2.0.0-11

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 (97) hide show
  1. package/config/build/cli.config.js +8 -2
  2. package/dist/cli.js +3 -3
  3. package/dist/wysiwyg.css +41 -31
  4. package/dist/wysiwyg.mjs +2015 -1359
  5. package/example/ExampleApp.vue +10 -1
  6. package/lib/Wysiwyg.vue +3 -2
  7. package/lib/__tests__/utils/buildTestExtensions.js +2 -1
  8. package/lib/assets/icons/indicator.svg +4 -0
  9. package/lib/cli/commands/Command.js +39 -0
  10. package/lib/cli/commands/ToJsonCommand.js +55 -0
  11. package/lib/cli/commands/VersionCommand.js +11 -0
  12. package/lib/cli/commands/index.js +2 -0
  13. package/lib/cli/index.js +1 -0
  14. package/lib/components/base/Button.vue +6 -0
  15. package/lib/components/base/dropdown/Dropdown.vue +7 -1
  16. package/lib/components/base/dropdown/DropdownActivator.vue +25 -4
  17. package/lib/components/base/dropdown/__tests__/DropdownActivator.test.js +23 -1
  18. package/lib/components/toolbar/controls/AlignmentControl.vue +12 -1
  19. package/lib/components/toolbar/controls/FontColorControl.vue +14 -0
  20. package/lib/components/toolbar/controls/FontFamilyControl.vue +4 -0
  21. package/lib/components/toolbar/controls/FontSizeControl.vue +6 -1
  22. package/lib/components/toolbar/controls/FontWeightControl.vue +12 -0
  23. package/lib/components/toolbar/controls/ItalicControl.vue +14 -0
  24. package/lib/components/toolbar/controls/LineHeightControl.vue +15 -0
  25. package/lib/components/toolbar/controls/UnderlineControl.vue +13 -0
  26. package/lib/components/toolbar/controls/__tests__/AlignmentControl.test.js +72 -5
  27. package/lib/components/toolbar/controls/__tests__/FontColorControl.test.js +22 -1
  28. package/lib/components/toolbar/controls/__tests__/FontFamilyControl.test.js +1 -0
  29. package/lib/components/toolbar/controls/__tests__/FontSizeControl.test.js +1 -0
  30. package/lib/components/toolbar/controls/__tests__/FontWeightControl.test.js +1 -0
  31. package/lib/components/toolbar/controls/__tests__/ItalicControl.test.js +23 -1
  32. package/lib/components/toolbar/controls/__tests__/LineHeightControl.test.js +23 -1
  33. package/lib/components/toolbar/controls/__tests__/StylePresetControl.test.js +4 -4
  34. package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +25 -1
  35. package/lib/composables/__tests__/useEditor.test.js +1 -1
  36. package/lib/composables/useEditor.js +9 -8
  37. package/lib/directives/__tests__/tooltip.test.js +22 -4
  38. package/lib/directives/tooltip.js +4 -1
  39. package/lib/entry-cli.js +7 -20
  40. package/lib/entry-lib.js +1 -1
  41. package/lib/enums/MarkGroups.js +4 -0
  42. package/lib/enums/TextSettings.js +1 -1
  43. package/lib/enums/index.js +1 -0
  44. package/lib/extensions/BackgroundColor.js +0 -1
  45. package/lib/extensions/FontColor.js +2 -2
  46. package/lib/extensions/FontFamily.js +3 -3
  47. package/lib/extensions/FontSize.js +2 -2
  48. package/lib/extensions/FontStyle.js +2 -2
  49. package/lib/extensions/FontWeight.js +2 -2
  50. package/lib/extensions/StylePreset.js +9 -2
  51. package/lib/extensions/Superscript.js +5 -2
  52. package/lib/extensions/TextDecoration.js +7 -0
  53. package/lib/extensions/__tests__/Alignment.test.js +2 -2
  54. package/lib/extensions/__tests__/BackgroundColor.test.js +4 -3
  55. package/lib/extensions/__tests__/FontColor.test.js +4 -3
  56. package/lib/extensions/__tests__/FontFamily.test.js +6 -6
  57. package/lib/extensions/__tests__/FontSize.test.js +9 -8
  58. package/lib/extensions/__tests__/FontStyle.test.js +6 -5
  59. package/lib/extensions/__tests__/LineHeight.test.js +2 -1
  60. package/lib/extensions/__tests__/StylePreset.test.js +51 -0
  61. package/lib/extensions/__tests__/Superscript.test.js +102 -0
  62. package/lib/extensions/__tests__/TextDecoration.test.js +20 -0
  63. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +25 -25
  64. package/lib/extensions/__tests__/__snapshots__/Superscript.test.js.snap +107 -0
  65. package/lib/extensions/core/Document.js +2 -1
  66. package/lib/extensions/core/Heading.js +2 -1
  67. package/lib/extensions/core/NodeProcessor.js +42 -21
  68. package/lib/extensions/core/Paragraph.js +2 -1
  69. package/lib/extensions/core/__tests__/NodeProcessor.test.js +309 -11
  70. package/lib/extensions/core/__tests__/TextProcessor.test.js +1 -1
  71. package/lib/extensions/core/__tests__/__snapshots__/NodeProcessor.test.js.snap +249 -0
  72. package/lib/extensions/core/steps/AddNodeMarkStep.js +6 -0
  73. package/lib/extensions/core/steps/AttrStep.js +6 -0
  74. package/lib/extensions/core/steps/RemoveNodeMarkStep.js +6 -0
  75. package/lib/extensions/list/List.js +70 -9
  76. package/lib/extensions/list/ListItem.js +27 -5
  77. package/lib/extensions/list/__tests__/List.test.js +26 -17
  78. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +36 -36
  79. package/lib/services/NodeFactory.js +69 -3
  80. package/lib/services/__tests__/NodeFactory.test.js +124 -0
  81. package/lib/services/__tests__/__snapshots__/NodeFactory.test.js.snap +326 -0
  82. package/lib/services/normalizer/HtmlNormalizer.js +54 -2
  83. package/lib/services/normalizer/JsonNormalizer.js +6 -5
  84. package/lib/services/normalizer/__tests__/HtmlNormalizer.test.js +14 -0
  85. package/lib/services/normalizer/__tests__/JsonNormalizer.test.js +20 -3
  86. package/lib/services/normalizer/__tests__/__snapshots__/JsonNormalizer.test.js.snap +37 -0
  87. package/lib/utils/__tests__/findMarkByType.test.js +17 -0
  88. package/lib/utils/__tests__/isMarkAppliedToParent.test.js +53 -0
  89. package/lib/utils/__tests__/isNodeFullySelected.test.js +44 -0
  90. package/lib/utils/__tests__/resolveTextPosition.test.js +39 -0
  91. package/lib/utils/copyMark.js +5 -0
  92. package/lib/utils/index.js +1 -1
  93. package/lib/utils/isMarkAppliedToParent.js +1 -1
  94. package/lib/utils/isNodeFullySelected.js +4 -7
  95. package/lib/utils/resolveTextPosition.js +4 -6
  96. package/package.json +37 -27
  97. package/lib/utils/resolveNodePosition.js +0 -6
@@ -1,10 +1,32 @@
1
1
  import { NodeTypes } from '../enums';
2
2
 
3
+ /**
4
+ * @typedef {Object} NodeJson
5
+ * @property {String} type
6
+ * @property {Array<MarkJson>} [marks]
7
+ * @property {AttrsJson} [attrs]
8
+ *
9
+ * @typedef {Object} MarkJson
10
+ * @property {String} type
11
+ * @property {AttrsJson} attrs
12
+ *
13
+ * @typedef {Object} AttrsJson
14
+ */
15
+
3
16
  export class NodeFactory {
17
+ /**
18
+ * @param {Array<NodeJson>} content
19
+ * @returns {NodeJson}
20
+ */
4
21
  static doc(content) {
5
22
  return { type: NodeTypes.DOCUMENT, content };
6
23
  }
7
24
 
25
+ /**
26
+ * @param {String} type
27
+ * @param {Array<String | NodeJson>} items
28
+ * @returns {NodeJson}
29
+ */
8
30
  static list(type, items) {
9
31
  return {
10
32
  type: NodeTypes.LIST,
@@ -16,10 +38,33 @@ export class NodeFactory {
16
38
  };
17
39
  }
18
40
 
41
+ /**
42
+ * @type {{
43
+ * ((content: String | NodeJson) => NodeJson);
44
+ * ((attrs: AttrsJson, content: String | NodeJson) => NodeJson);
45
+ * ((attrs: AttrsJson, marks: Array<MarkJson>, content: String | NodeJson) => NodeJson);
46
+ * }}
47
+ */
19
48
  static listItem(...args) {
20
- return { type: NodeTypes.LIST_ITEM, ...this.#textBlock(args, this.paragraph) };
49
+ const { attrs, content: children, marks } = this.#normalizeTextBlockArgs(args);
50
+
51
+ return {
52
+ type: NodeTypes.LIST_ITEM,
53
+ ...(attrs ? { attrs } : {}),
54
+ ...(marks ? { marks } : {}),
55
+ content: [].concat(children).map((content) => {
56
+ return typeof content === 'string' ? this.paragraph(content) : content;
57
+ })
58
+ };
21
59
  }
22
60
 
61
+ /**
62
+ * @type {{
63
+ * ((level: Number, content: String | NodeJson) => NodeJson);
64
+ * ((level: Number, attrs: AttrsJson, content: String | NodeJson) => NodeJson);
65
+ * ((level: Number, attrs: AttrsJson, marks: Array<MarkJson>, content: String | NodeJson) => NodeJson);
66
+ * }}
67
+ */
23
68
  static heading(level, ...args) {
24
69
  const config = this.#textBlock(args, this.text);
25
70
 
@@ -29,6 +74,13 @@ export class NodeFactory {
29
74
  return { type: NodeTypes.HEADING, ...config };
30
75
  }
31
76
 
77
+ /**
78
+ * @type {{
79
+ * ((content: String | NodeJson) => NodeJson);
80
+ * ((attrs: AttrsJson, content: String | NodeJson) => NodeJson);
81
+ * ((attrs: AttrsJson, marks: Array<MarkJson>, content: String | NodeJson) => NodeJson);
82
+ * }}
83
+ */
32
84
  static paragraph(...args) {
33
85
  return {
34
86
  type: NodeTypes.PARAGRAPH,
@@ -36,9 +88,9 @@ export class NodeFactory {
36
88
  };
37
89
  }
38
90
 
39
- static #textBlock(args, createChildNode) {
91
+ static #textBlock(args) {
40
92
  const { attrs, content, marks } = this.#normalizeTextBlockArgs(args);
41
- const children = typeof content === 'string' ? [createChildNode.call(this, content)] : content;
93
+ const children = typeof content === 'string' ? [this.text(content)] : content;
42
94
 
43
95
  return {
44
96
  content: children,
@@ -57,6 +109,11 @@ export class NodeFactory {
57
109
  return { attrs: args[0], marks: args[1], content: args[2] };
58
110
  }
59
111
 
112
+ /**
113
+ * @param {String} text
114
+ * @param {Array<MarkJson>} marks
115
+ * @returns {NodeJson}
116
+ */
60
117
  static text(text, marks) {
61
118
  return {
62
119
  type: NodeTypes.TEXT,
@@ -65,10 +122,19 @@ export class NodeFactory {
65
122
  };
66
123
  }
67
124
 
125
+ /**
126
+ * @param {String} type
127
+ * @param {AttrsJson} attrs
128
+ * @returns {MarkJson}
129
+ */
68
130
  static mark(type, attrs) {
69
131
  return { type, attrs };
70
132
  }
71
133
 
134
+ /**
135
+ * @param {*} value
136
+ * @returns {{tablet, desktop, mobile}}
137
+ */
72
138
  static populateAllDevices(value) {
73
139
  // return { mobile: value, tablet: value, desktop: value };
74
140
 
@@ -0,0 +1,124 @@
1
+ import { NodeFactory } from '../NodeFactory';
2
+ import { ListTypes, TextSettings } from '../../enums';
3
+
4
+ describe('build node', () => {
5
+ test('should build doc node', () => {
6
+ expect(NodeFactory.doc([])).toMatchSnapshot();
7
+ });
8
+
9
+ test('should build list node with text items', () => {
10
+ const node = NodeFactory.list(ListTypes.DISC, [
11
+ 'lorem ipsum 1',
12
+ 'lorem ipsum 2'
13
+ ]);
14
+
15
+ expect(node).toMatchSnapshot();
16
+ });
17
+
18
+ test('should build list node with node items', () => {
19
+ const node = NodeFactory.list(ListTypes.DISC, [
20
+ NodeFactory.paragraph('lorem ipsum 1'),
21
+ NodeFactory.paragraph('lorem ipsum 2')
22
+ ]);
23
+
24
+ expect(node).toMatchSnapshot();
25
+ });
26
+
27
+ test('should build list node with custom list item nodes', () => {
28
+ const node = NodeFactory.list(ListTypes.DISC, [
29
+ NodeFactory.listItem(null, [
30
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
31
+ ], 'lorem ipsum 1'),
32
+
33
+ NodeFactory.listItem(null, [
34
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
35
+ ], 'lorem ipsum 2')
36
+ ]);
37
+
38
+ expect(node).toMatchSnapshot();
39
+ });
40
+
41
+ test('should build heading node', () => {
42
+ const node = NodeFactory.heading(1, 'lorem ipsum');
43
+
44
+ expect(node).toMatchSnapshot();
45
+ });
46
+
47
+ test('should build heading node with attrs', () => {
48
+ const node = NodeFactory.heading(1, { preset: { id: 'h1' } }, 'lorem ipsum');
49
+
50
+ expect(node).toMatchSnapshot();
51
+ });
52
+
53
+ test('should build heading node with marks', () => {
54
+ const node = NodeFactory.heading(1, null, [
55
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
56
+ ], 'lorem ipsum');
57
+
58
+ expect(node).toMatchSnapshot();
59
+ });
60
+
61
+ test('should build heading with custom text node', () => {
62
+ const node = NodeFactory.heading(1, [
63
+ NodeFactory.text('lorem ipsum', [
64
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
65
+ ])
66
+ ]);
67
+
68
+ expect(node).toMatchSnapshot();
69
+ });
70
+
71
+ test('should build paragraph node', () => {
72
+ const node = NodeFactory.paragraph('lorem ipsum');
73
+
74
+ expect(node).toMatchSnapshot();
75
+ });
76
+
77
+ test('should build paragraph node with attrs', () => {
78
+ const node = NodeFactory.paragraph({ preset: { id: 'regular-1' } }, 'lorem ipsum');
79
+
80
+ expect(node).toMatchSnapshot();
81
+ });
82
+
83
+ test('should build paragraph node with marks', () => {
84
+ const node = NodeFactory.paragraph(null, [
85
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
86
+ ], 'lorem ipsum');
87
+
88
+ expect(node).toMatchSnapshot();
89
+ });
90
+
91
+ test('should build paragraph with custom text node', () => {
92
+ const node = NodeFactory.paragraph([
93
+ NodeFactory.text('lorem ipsum', [
94
+ NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' })
95
+ ])
96
+ ]);
97
+
98
+ expect(node).toMatchSnapshot();
99
+ });
100
+
101
+ test('should build text node', () => {
102
+ const node = NodeFactory.text('lorem ipsum');
103
+
104
+ expect(node).toMatchSnapshot();
105
+ });
106
+
107
+ test('should build text node with marks', () => {
108
+ const node = NodeFactory.text('lorem ipsum');
109
+
110
+ expect(node).toMatchSnapshot();
111
+ });
112
+
113
+ test('should build mark', () => {
114
+ const mark = NodeFactory.mark(TextSettings.FONT_WEIGHT, { value: '700' });
115
+
116
+ expect(mark).toMatchSnapshot();
117
+ });
118
+
119
+ test('should populate value to all devices', () => {
120
+ const value = NodeFactory.populateAllDevices('18');
121
+
122
+ expect(value).toMatchSnapshot();
123
+ });
124
+ });
@@ -0,0 +1,326 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`build node should build doc node 1`] = `
4
+ Object {
5
+ "content": Array [],
6
+ "type": "doc",
7
+ }
8
+ `;
9
+
10
+ exports[`build node should build heading node 1`] = `
11
+ Object {
12
+ "attrs": Object {
13
+ "level": 1,
14
+ },
15
+ "content": Array [
16
+ Object {
17
+ "text": "lorem ipsum",
18
+ "type": "text",
19
+ },
20
+ ],
21
+ "type": "heading",
22
+ }
23
+ `;
24
+
25
+ exports[`build node should build heading node with attrs 1`] = `
26
+ Object {
27
+ "attrs": Object {
28
+ "level": 1,
29
+ "preset": Object {
30
+ "id": "h1",
31
+ },
32
+ },
33
+ "content": Array [
34
+ Object {
35
+ "text": "lorem ipsum",
36
+ "type": "text",
37
+ },
38
+ ],
39
+ "type": "heading",
40
+ }
41
+ `;
42
+
43
+ exports[`build node should build heading node with marks 1`] = `
44
+ Object {
45
+ "attrs": Object {
46
+ "level": 1,
47
+ },
48
+ "content": Array [
49
+ Object {
50
+ "text": "lorem ipsum",
51
+ "type": "text",
52
+ },
53
+ ],
54
+ "marks": Array [
55
+ Object {
56
+ "attrs": Object {
57
+ "value": "700",
58
+ },
59
+ "type": "font_weight",
60
+ },
61
+ ],
62
+ "type": "heading",
63
+ }
64
+ `;
65
+
66
+ exports[`build node should build heading with custom text node 1`] = `
67
+ Object {
68
+ "attrs": Object {
69
+ "level": 1,
70
+ },
71
+ "content": Array [
72
+ Object {
73
+ "marks": Array [
74
+ Object {
75
+ "attrs": Object {
76
+ "value": "700",
77
+ },
78
+ "type": "font_weight",
79
+ },
80
+ ],
81
+ "text": "lorem ipsum",
82
+ "type": "text",
83
+ },
84
+ ],
85
+ "type": "heading",
86
+ }
87
+ `;
88
+
89
+ exports[`build node should build list node with custom list item nodes 1`] = `
90
+ Object {
91
+ "attrs": Object {
92
+ "bullet": Object {
93
+ "type": "disc",
94
+ },
95
+ },
96
+ "content": Array [
97
+ Object {
98
+ "content": Array [
99
+ Object {
100
+ "content": Array [
101
+ Object {
102
+ "text": "lorem ipsum 1",
103
+ "type": "text",
104
+ },
105
+ ],
106
+ "type": "paragraph",
107
+ },
108
+ ],
109
+ "marks": Array [
110
+ Object {
111
+ "attrs": Object {
112
+ "value": "700",
113
+ },
114
+ "type": "font_weight",
115
+ },
116
+ ],
117
+ "type": "listItem",
118
+ },
119
+ Object {
120
+ "content": Array [
121
+ Object {
122
+ "content": Array [
123
+ Object {
124
+ "text": "lorem ipsum 2",
125
+ "type": "text",
126
+ },
127
+ ],
128
+ "type": "paragraph",
129
+ },
130
+ ],
131
+ "marks": Array [
132
+ Object {
133
+ "attrs": Object {
134
+ "value": "700",
135
+ },
136
+ "type": "font_weight",
137
+ },
138
+ ],
139
+ "type": "listItem",
140
+ },
141
+ ],
142
+ "type": "list",
143
+ }
144
+ `;
145
+
146
+ exports[`build node should build list node with node items 1`] = `
147
+ Object {
148
+ "attrs": Object {
149
+ "bullet": Object {
150
+ "type": "disc",
151
+ },
152
+ },
153
+ "content": Array [
154
+ Object {
155
+ "content": Array [
156
+ Object {
157
+ "content": Array [
158
+ Object {
159
+ "text": "lorem ipsum 1",
160
+ "type": "text",
161
+ },
162
+ ],
163
+ "type": "paragraph",
164
+ },
165
+ ],
166
+ "type": "listItem",
167
+ },
168
+ Object {
169
+ "content": Array [
170
+ Object {
171
+ "content": Array [
172
+ Object {
173
+ "text": "lorem ipsum 2",
174
+ "type": "text",
175
+ },
176
+ ],
177
+ "type": "paragraph",
178
+ },
179
+ ],
180
+ "type": "listItem",
181
+ },
182
+ ],
183
+ "type": "list",
184
+ }
185
+ `;
186
+
187
+ exports[`build node should build list node with text items 1`] = `
188
+ Object {
189
+ "attrs": Object {
190
+ "bullet": Object {
191
+ "type": "disc",
192
+ },
193
+ },
194
+ "content": Array [
195
+ Object {
196
+ "content": Array [
197
+ Object {
198
+ "content": Array [
199
+ Object {
200
+ "text": "lorem ipsum 1",
201
+ "type": "text",
202
+ },
203
+ ],
204
+ "type": "paragraph",
205
+ },
206
+ ],
207
+ "type": "listItem",
208
+ },
209
+ Object {
210
+ "content": Array [
211
+ Object {
212
+ "content": Array [
213
+ Object {
214
+ "text": "lorem ipsum 2",
215
+ "type": "text",
216
+ },
217
+ ],
218
+ "type": "paragraph",
219
+ },
220
+ ],
221
+ "type": "listItem",
222
+ },
223
+ ],
224
+ "type": "list",
225
+ }
226
+ `;
227
+
228
+ exports[`build node should build mark 1`] = `
229
+ Object {
230
+ "attrs": Object {
231
+ "value": "700",
232
+ },
233
+ "type": "font_weight",
234
+ }
235
+ `;
236
+
237
+ exports[`build node should build paragraph node 1`] = `
238
+ Object {
239
+ "content": Array [
240
+ Object {
241
+ "text": "lorem ipsum",
242
+ "type": "text",
243
+ },
244
+ ],
245
+ "type": "paragraph",
246
+ }
247
+ `;
248
+
249
+ exports[`build node should build paragraph node with attrs 1`] = `
250
+ Object {
251
+ "attrs": Object {
252
+ "preset": Object {
253
+ "id": "regular-1",
254
+ },
255
+ },
256
+ "content": Array [
257
+ Object {
258
+ "text": "lorem ipsum",
259
+ "type": "text",
260
+ },
261
+ ],
262
+ "type": "paragraph",
263
+ }
264
+ `;
265
+
266
+ exports[`build node should build paragraph node with marks 1`] = `
267
+ Object {
268
+ "content": Array [
269
+ Object {
270
+ "text": "lorem ipsum",
271
+ "type": "text",
272
+ },
273
+ ],
274
+ "marks": Array [
275
+ Object {
276
+ "attrs": Object {
277
+ "value": "700",
278
+ },
279
+ "type": "font_weight",
280
+ },
281
+ ],
282
+ "type": "paragraph",
283
+ }
284
+ `;
285
+
286
+ exports[`build node should build paragraph with custom text node 1`] = `
287
+ Object {
288
+ "content": Array [
289
+ Object {
290
+ "marks": Array [
291
+ Object {
292
+ "attrs": Object {
293
+ "value": "700",
294
+ },
295
+ "type": "font_weight",
296
+ },
297
+ ],
298
+ "text": "lorem ipsum",
299
+ "type": "text",
300
+ },
301
+ ],
302
+ "type": "paragraph",
303
+ }
304
+ `;
305
+
306
+ exports[`build node should build text node 1`] = `
307
+ Object {
308
+ "text": "lorem ipsum",
309
+ "type": "text",
310
+ }
311
+ `;
312
+
313
+ exports[`build node should build text node with marks 1`] = `
314
+ Object {
315
+ "text": "lorem ipsum",
316
+ "type": "text",
317
+ }
318
+ `;
319
+
320
+ exports[`build node should populate value to all devices 1`] = `
321
+ Object {
322
+ "desktop": "18",
323
+ "mobile": null,
324
+ "tablet": "18",
325
+ }
326
+ `;
@@ -2,6 +2,7 @@ import { BaseNormalizer } from './BaseNormalizer';
2
2
 
3
3
  export class HtmlNormalizer extends BaseNormalizer {
4
4
  static BLOCK_NODE_NAMES = ['P', 'H1', 'H2', 'H3', 'H4'];
5
+ static ROOT_NODE_NAMES = HtmlNormalizer.BLOCK_NODE_NAMES.concat('UL', 'OL');
5
6
 
6
7
  static BLOCK_STYLES = [
7
8
  'text-align',
@@ -30,10 +31,12 @@ export class HtmlNormalizer extends BaseNormalizer {
30
31
  this.dom = this.#parser.parse(this.content.replace(/(\r)?\n/g, ''));
31
32
 
32
33
  this.#removeComments();
34
+ this.#normalizeRootTags();
33
35
  this.#iterateNodes(this.#normalizeBreakLines, (node) => node.tagName === 'BR');
34
36
  this.#iterateNodes(this.#removeEmptyNodes, this.#isBlockNode);
35
37
  this.#iterateNodes(this.#normalizeListItems, (node) => node.tagName === 'LI');
36
38
  this.#normalizeBlockTextDecoration();
39
+ this.#normalizeBlockBackgroundColor();
37
40
  }
38
41
 
39
42
  get normalizedHTML() {
@@ -54,6 +57,30 @@ export class HtmlNormalizer extends BaseNormalizer {
54
57
  this.#runIterator(iterator, (node) => node.remove());
55
58
  }
56
59
 
60
+ #normalizeRootTags() {
61
+ const children = Array.from(this.dom.body.childNodes);
62
+ const fragment = this.dom.createDocumentFragment();
63
+ let capturingParagraph;
64
+
65
+ for (const node of children) {
66
+ if (this.#isRootNode(node)) {
67
+ fragment.append(node);
68
+ capturingParagraph = null;
69
+ continue;
70
+ }
71
+
72
+ if (!capturingParagraph) {
73
+ capturingParagraph = this.dom.createElement('p');
74
+ fragment.append(capturingParagraph);
75
+ }
76
+
77
+ capturingParagraph.append(node);
78
+ }
79
+
80
+ this.dom.body.innerHTML = '';
81
+ this.dom.body.append(fragment);
82
+ }
83
+
57
84
  #createNodeIterator(whatToShow, filter) {
58
85
  return this.dom.createNodeIterator(this.dom.body, whatToShow, filter);
59
86
  }
@@ -84,8 +111,7 @@ export class HtmlNormalizer extends BaseNormalizer {
84
111
  #normalizeListItems(itemEl) {
85
112
  const fragment = this.dom.createDocumentFragment();
86
113
  const children = Array.from(itemEl.childNodes);
87
- let capturingParagraph;
88
- let previousNode;
114
+ let capturingParagraph, previousNode;
89
115
 
90
116
  const append = (node) => {
91
117
  this.#assignElementProperties(node, itemEl, HtmlNormalizer.BLOCK_STYLES);
@@ -135,6 +161,10 @@ export class HtmlNormalizer extends BaseNormalizer {
135
161
  return HtmlNormalizer.BLOCK_NODE_NAMES.includes(node.tagName);
136
162
  }
137
163
 
164
+ #isRootNode(node) {
165
+ return HtmlNormalizer.ROOT_NODE_NAMES.includes(node.tagName);
166
+ }
167
+
138
168
  #assignElementProperties(target, source, properties) {
139
169
  for (const property of properties) {
140
170
  const value = source.style.getPropertyValue(property);
@@ -232,6 +262,28 @@ export class HtmlNormalizer extends BaseNormalizer {
232
262
  };
233
263
  }
234
264
 
265
+ #normalizeBlockBackgroundColor() {
266
+ const blockEls = this.dom.querySelectorAll('[style*="background-color"]:where(p, h1, h2, h3, h4, li)');
267
+
268
+ for (const blockEl of blockEls) {
269
+ this.#moveBackgroundColorToChildren(blockEl);
270
+ }
271
+ }
272
+
273
+ #moveBackgroundColorToChildren(blockEl) {
274
+ const blockColor = blockEl.style.backgroundColor;
275
+
276
+ blockEl.style.removeProperty('background-color');
277
+ if (!blockEl.style.cssText) blockEl.removeAttribute('style');
278
+
279
+ for (const childNode of blockEl.childNodes) {
280
+ const textEl = this.#wrapTextNode(blockEl, childNode);
281
+ const color = textEl.style.backgroundColor || blockColor;
282
+
283
+ textEl.style.backgroundColor = color;
284
+ }
285
+ }
286
+
235
287
  #wrapTextNode(parent, node) {
236
288
  if (node.nodeType !== this.#Node.TEXT_NODE) return node;
237
289
 
@@ -14,8 +14,8 @@ export class JsonNormalizer extends BaseNormalizer {
14
14
 
15
15
  #iterateChildNodes(node, handler) {
16
16
  for (const child of node.content) {
17
- handler.call(this, child);
18
17
  child.content && this.#iterateChildNodes(child, handler);
18
+ handler.call(this, child);
19
19
  }
20
20
  }
21
21
 
@@ -27,14 +27,14 @@ export class JsonNormalizer extends BaseNormalizer {
27
27
  if (!child.marks) continue;
28
28
 
29
29
  for (const childMark of child.marks.slice()) {
30
- if (this.#canBubbleMark(node, childMark)) {
30
+ if (this.#includesMark(node, childMark)) {
31
31
  this.#removeMark(child, childMark);
32
- this.#addMark(node, childMark);
33
32
  continue;
34
33
  }
35
34
 
36
- if (this.#includesMark(node, childMark)) {
35
+ if (this.#canBubbleMark(node, childMark)) {
37
36
  this.#removeMark(child, childMark);
37
+ this.#addMark(node, childMark);
38
38
  }
39
39
  }
40
40
  }
@@ -45,8 +45,9 @@ export class JsonNormalizer extends BaseNormalizer {
45
45
  if (this.#includesMarkType(node, childMark.type)) return false;
46
46
 
47
47
  for (const child of node.content) {
48
+ if (!child.content && node.type === NodeTypes.LIST_ITEM) continue;
48
49
  if (!child.marks) return false;
49
- if (!this.#includesMarkType(child, childMark.type)) return false;
50
+ if (!this.#includesMark(child, childMark)) return false;
50
51
  }
51
52
 
52
53
  return true;