@zipify/wysiwyg 2.0.0-0 → 2.0.0-10

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 (118) hide show
  1. package/.eslintrc.js +1 -1
  2. package/config/build/cli.config.js +8 -2
  3. package/config/build/lib.config.js +4 -2
  4. package/dist/cli.js +10 -2
  5. package/dist/wysiwyg.css +53 -37
  6. package/dist/wysiwyg.mjs +2131 -405
  7. package/example/ExampleApp.vue +13 -2
  8. package/lib/Wysiwyg.vue +3 -2
  9. package/lib/__tests__/utils/buildTestExtensions.js +2 -1
  10. package/lib/assets/icons/indicator.svg +4 -0
  11. package/lib/cli/commands/Command.js +39 -0
  12. package/lib/cli/commands/ToJsonCommand.js +46 -0
  13. package/lib/cli/commands/VersionCommand.js +11 -0
  14. package/lib/cli/commands/index.js +2 -0
  15. package/lib/cli/index.js +1 -0
  16. package/lib/components/base/Button.vue +6 -0
  17. package/lib/components/base/dropdown/Dropdown.vue +7 -1
  18. package/lib/components/base/dropdown/DropdownActivator.vue +25 -4
  19. package/lib/components/base/dropdown/__tests__/DropdownActivator.test.js +23 -1
  20. package/lib/components/toolbar/controls/AlignmentControl.vue +12 -1
  21. package/lib/components/toolbar/controls/FontColorControl.vue +14 -0
  22. package/lib/components/toolbar/controls/FontFamilyControl.vue +4 -0
  23. package/lib/components/toolbar/controls/FontSizeControl.vue +6 -1
  24. package/lib/components/toolbar/controls/FontWeightControl.vue +12 -0
  25. package/lib/components/toolbar/controls/ItalicControl.vue +14 -0
  26. package/lib/components/toolbar/controls/LineHeightControl.vue +15 -0
  27. package/lib/components/toolbar/controls/StylePresetControl.vue +1 -1
  28. package/lib/components/toolbar/controls/UnderlineControl.vue +13 -0
  29. package/lib/components/toolbar/controls/__tests__/AlignmentControl.test.js +72 -5
  30. package/lib/components/toolbar/controls/__tests__/FontColorControl.test.js +22 -1
  31. package/lib/components/toolbar/controls/__tests__/FontFamilyControl.test.js +1 -0
  32. package/lib/components/toolbar/controls/__tests__/FontSizeControl.test.js +1 -0
  33. package/lib/components/toolbar/controls/__tests__/FontWeightControl.test.js +1 -0
  34. package/lib/components/toolbar/controls/__tests__/ItalicControl.test.js +23 -1
  35. package/lib/components/toolbar/controls/__tests__/LineHeightControl.test.js +23 -1
  36. package/lib/components/toolbar/controls/__tests__/StylePresetControl.test.js +6 -6
  37. package/lib/components/toolbar/controls/__tests__/UnderlineControl.test.js +25 -1
  38. package/lib/composables/__tests__/useEditor.test.js +1 -1
  39. package/lib/composables/useEditor.js +9 -8
  40. package/lib/directives/__tests__/tooltip.test.js +22 -4
  41. package/lib/directives/tooltip.js +4 -1
  42. package/lib/entry-cli.js +7 -20
  43. package/lib/entry-lib.js +1 -1
  44. package/lib/enums/MarkGroups.js +4 -0
  45. package/lib/enums/TextSettings.js +5 -5
  46. package/lib/enums/index.js +1 -0
  47. package/lib/extensions/BackgroundColor.js +0 -1
  48. package/lib/extensions/FontColor.js +2 -2
  49. package/lib/extensions/FontFamily.js +3 -3
  50. package/lib/extensions/FontSize.js +2 -2
  51. package/lib/extensions/FontStyle.js +2 -2
  52. package/lib/extensions/FontWeight.js +2 -2
  53. package/lib/extensions/Link.js +1 -1
  54. package/lib/extensions/StylePreset.js +9 -3
  55. package/lib/extensions/Superscript.js +5 -2
  56. package/lib/extensions/TextDecoration.js +19 -29
  57. package/lib/extensions/__tests__/Alignment.test.js +2 -2
  58. package/lib/extensions/__tests__/BackgroundColor.test.js +4 -3
  59. package/lib/extensions/__tests__/FontColor.test.js +4 -3
  60. package/lib/extensions/__tests__/FontFamily.test.js +6 -6
  61. package/lib/extensions/__tests__/FontSize.test.js +9 -8
  62. package/lib/extensions/__tests__/FontStyle.test.js +6 -5
  63. package/lib/extensions/__tests__/FontWeight.test.js +2 -2
  64. package/lib/extensions/__tests__/LineHeight.test.js +2 -1
  65. package/lib/extensions/__tests__/StylePreset.test.js +51 -0
  66. package/lib/extensions/__tests__/Superscript.test.js +102 -0
  67. package/lib/extensions/__tests__/TextDecoration.test.js +40 -24
  68. package/lib/extensions/__tests__/__snapshots__/BackgroundColor.test.js.snap +24 -24
  69. package/lib/extensions/__tests__/__snapshots__/FontColor.test.js.snap +1 -1
  70. package/lib/extensions/__tests__/__snapshots__/FontFamily.test.js.snap +19 -23
  71. package/lib/extensions/__tests__/__snapshots__/FontSize.test.js.snap +2 -2
  72. package/lib/extensions/__tests__/__snapshots__/FontStyle.test.js.snap +1 -1
  73. package/lib/extensions/__tests__/__snapshots__/FontWeight.test.js.snap +13 -17
  74. package/lib/extensions/__tests__/__snapshots__/Superscript.test.js.snap +107 -0
  75. package/lib/extensions/__tests__/__snapshots__/TextDecoration.test.js.snap +102 -102
  76. package/lib/extensions/core/Document.js +2 -1
  77. package/lib/extensions/core/Heading.js +2 -1
  78. package/lib/extensions/core/NodeProcessor.js +63 -20
  79. package/lib/extensions/core/Paragraph.js +2 -1
  80. package/lib/extensions/core/TextProcessor.js +0 -5
  81. package/lib/extensions/core/__tests__/NodeProcessor.test.js +364 -11
  82. package/lib/extensions/core/__tests__/TextProcessor.test.js +1 -22
  83. package/lib/extensions/core/__tests__/__snapshots__/NodeProcessor.test.js.snap +309 -0
  84. package/lib/extensions/core/__tests__/__snapshots__/TextProcessor.test.js.snap +7 -27
  85. package/lib/extensions/core/steps/AddNodeMarkStep.js +6 -0
  86. package/lib/extensions/core/steps/AttrStep.js +60 -0
  87. package/lib/extensions/core/steps/RemoveNodeMarkStep.js +6 -0
  88. package/lib/extensions/core/steps/index.js +1 -0
  89. package/lib/extensions/list/List.js +70 -9
  90. package/lib/extensions/list/ListItem.js +2 -2
  91. package/lib/extensions/list/__tests__/List.test.js +26 -17
  92. package/lib/extensions/list/__tests__/__snapshots__/List.test.js.snap +36 -36
  93. package/lib/services/NodeFactory.js +73 -13
  94. package/lib/services/__tests__/NodeFactory.test.js +124 -0
  95. package/lib/services/__tests__/__snapshots__/NodeFactory.test.js.snap +326 -0
  96. package/lib/services/index.js +1 -1
  97. package/lib/services/normalizer/BaseNormalizer.js +11 -0
  98. package/lib/services/{BrowserDomParser.js → normalizer/BrowserDomParser.js} +0 -0
  99. package/lib/services/normalizer/ContentNormalizer.js +24 -0
  100. package/lib/services/normalizer/HtmlNormalizer.js +297 -0
  101. package/lib/services/normalizer/JsonNormalizer.js +82 -0
  102. package/lib/services/{__tests__/ContentNormalizer.test.js → normalizer/__tests__/HtmlNormalizer.test.js} +42 -4
  103. package/lib/services/normalizer/__tests__/JsonNormalizer.test.js +87 -0
  104. package/lib/services/normalizer/__tests__/__snapshots__/JsonNormalizer.test.js.snap +196 -0
  105. package/lib/services/normalizer/index.js +1 -0
  106. package/lib/styles/content.css +8 -0
  107. package/lib/utils/__tests__/findMarkByType.test.js +17 -0
  108. package/lib/utils/__tests__/isMarkAppliedToParent.test.js +53 -0
  109. package/lib/utils/__tests__/isNodeFullySelected.test.js +44 -0
  110. package/lib/utils/__tests__/resolveTextPosition.test.js +39 -0
  111. package/lib/utils/copyMark.js +5 -0
  112. package/lib/utils/index.js +1 -1
  113. package/lib/utils/isMarkAppliedToParent.js +2 -7
  114. package/lib/utils/isNodeFullySelected.js +4 -7
  115. package/lib/utils/resolveTextPosition.js +4 -6
  116. package/package.json +31 -27
  117. package/lib/services/ContentNormalizer.js +0 -194
  118. package/lib/utils/resolveNodePosition.js +0 -6
@@ -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
+ `;
@@ -1,6 +1,6 @@
1
1
  export { JsonSerializer } from './JsonSerializer';
2
2
  export { Storage } from './Storage';
3
3
  export { FavoriteColors } from './FavoriteColors';
4
- export { ContentNormalizer } from './ContentNormalizer';
4
+ export { ContentNormalizer } from './normalizer';
5
5
  export { ContextWindow } from './ContextWidnow';
6
6
  export { NodeFactory } from './NodeFactory';
@@ -0,0 +1,11 @@
1
+ export class BaseNormalizer {
2
+ content;
3
+
4
+ constructor({ content }) {
5
+ this.content = content;
6
+ }
7
+
8
+ normalize() {
9
+ throw new Error('Implement abstract method');
10
+ }
11
+ }
@@ -0,0 +1,24 @@
1
+ import { BrowserDomParser } from './BrowserDomParser';
2
+ import { HtmlNormalizer } from './HtmlNormalizer';
3
+ import { JsonNormalizer } from './JsonNormalizer';
4
+
5
+ export class ContentNormalizer {
6
+ static build(content, options = {}) {
7
+ return typeof content === 'string' ? this.#buildHtml(content, options) : this.#buildJson(content);
8
+ }
9
+
10
+ static #buildHtml(content, options) {
11
+ return new HtmlNormalizer({
12
+ content,
13
+ parser: options.parser || new BrowserDomParser()
14
+ });
15
+ }
16
+
17
+ static #buildJson(content) {
18
+ return new JsonNormalizer({ content });
19
+ }
20
+
21
+ static normalize(content, options = {}) {
22
+ return ContentNormalizer.build(content, options).normalize();
23
+ }
24
+ }
@@ -0,0 +1,297 @@
1
+ import { BaseNormalizer } from './BaseNormalizer';
2
+
3
+ export class HtmlNormalizer extends BaseNormalizer {
4
+ static BLOCK_NODE_NAMES = ['P', 'H1', 'H2', 'H3', 'H4'];
5
+ static ROOT_NODE_NAMES = HtmlNormalizer.BLOCK_NODE_NAMES.concat('UL', 'OL');
6
+
7
+ static BLOCK_STYLES = [
8
+ 'text-align',
9
+ 'line-height',
10
+ 'margin',
11
+ 'margin-top',
12
+ 'margin-bottom',
13
+ 'margin-left',
14
+ 'margin-right'
15
+ ];
16
+
17
+ #parser;
18
+
19
+ constructor({ content, parser }) {
20
+ super({ content });
21
+ this.#parser = parser;
22
+ this.dom = null;
23
+ }
24
+
25
+ normalize() {
26
+ this.normalizeHTML();
27
+ return this.normalizedHTML;
28
+ }
29
+
30
+ normalizeHTML() {
31
+ this.dom = this.#parser.parse(this.content.replace(/(\r)?\n/g, ''));
32
+
33
+ this.#removeComments();
34
+ this.#normalizeRootTags();
35
+ this.#iterateNodes(this.#normalizeBreakLines, (node) => node.tagName === 'BR');
36
+ this.#iterateNodes(this.#removeEmptyNodes, this.#isBlockNode);
37
+ this.#iterateNodes(this.#normalizeListItems, (node) => node.tagName === 'LI');
38
+ this.#normalizeBlockTextDecoration();
39
+ this.#normalizeBlockBackgroundColor();
40
+ }
41
+
42
+ get normalizedHTML() {
43
+ return this.dom.body.innerHTML;
44
+ }
45
+
46
+ get #NodeFilter() {
47
+ return this.#parser.types.NodeFilter;
48
+ }
49
+
50
+ get #Node() {
51
+ return this.#parser.types.Node;
52
+ }
53
+
54
+ #removeComments() {
55
+ const iterator = this.#createNodeIterator(this.#NodeFilter.SHOW_COMMENT);
56
+
57
+ this.#runIterator(iterator, (node) => node.remove());
58
+ }
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
+
84
+ #createNodeIterator(whatToShow, filter) {
85
+ return this.dom.createNodeIterator(this.dom.body, whatToShow, filter);
86
+ }
87
+
88
+ #iterateNodes(handler, condition = () => true) {
89
+ const checkCondition = (node) => node.tagName !== 'BODY' && condition.call(this, node);
90
+
91
+ const iterator = this.#createNodeIterator(this.#NodeFilter.SHOW_ELEMENT, {
92
+ acceptNode: (node) => checkCondition(node) ? this.#NodeFilter.FILTER_ACCEPT : this.#NodeFilter.FILTER_REJECT
93
+ });
94
+
95
+ this.#runIterator(iterator, handler);
96
+ }
97
+
98
+ #runIterator(iterator, handler) {
99
+ let currentNode = iterator.nextNode();
100
+
101
+ while (currentNode) {
102
+ handler.call(this, currentNode);
103
+ currentNode = iterator.nextNode();
104
+ }
105
+ }
106
+
107
+ #removeEmptyNodes(node) {
108
+ if (!node.innerHTML.trim()) node.remove();
109
+ }
110
+
111
+ #normalizeListItems(itemEl) {
112
+ const fragment = this.dom.createDocumentFragment();
113
+ const children = Array.from(itemEl.childNodes);
114
+ let capturingParagraph, previousNode;
115
+
116
+ const append = (node) => {
117
+ this.#assignElementProperties(node, itemEl, HtmlNormalizer.BLOCK_STYLES);
118
+ fragment.append(node);
119
+ };
120
+
121
+ this.#assignElementProperties(itemEl, itemEl.parentElement, HtmlNormalizer.BLOCK_STYLES);
122
+
123
+ for (const node of children) {
124
+ if (this.#isBlockNode(node)) {
125
+ append(node);
126
+ capturingParagraph = null;
127
+ previousNode = node;
128
+ continue;
129
+ }
130
+
131
+ if (node.tagName === 'BR' && previousNode && previousNode?.tagName !== 'BR') {
132
+ node.remove();
133
+ previousNode = node;
134
+ continue;
135
+ }
136
+
137
+ if (node.tagName === 'BR') {
138
+ const emptyLineEl = this.dom.createElement('p');
139
+
140
+ emptyLineEl.append(node);
141
+ append(emptyLineEl);
142
+ capturingParagraph = null;
143
+ previousNode = node;
144
+ continue;
145
+ }
146
+
147
+ if (!capturingParagraph) {
148
+ capturingParagraph = this.dom.createElement('p');
149
+ append(capturingParagraph);
150
+ }
151
+
152
+ capturingParagraph.append(node);
153
+ previousNode = node;
154
+ }
155
+
156
+ itemEl.append(fragment);
157
+ this.#removeStyleProperties(itemEl, HtmlNormalizer.BLOCK_STYLES);
158
+ }
159
+
160
+ #isBlockNode(node) {
161
+ return HtmlNormalizer.BLOCK_NODE_NAMES.includes(node.tagName);
162
+ }
163
+
164
+ #isRootNode(node) {
165
+ return HtmlNormalizer.ROOT_NODE_NAMES.includes(node.tagName);
166
+ }
167
+
168
+ #assignElementProperties(target, source, properties) {
169
+ for (const property of properties) {
170
+ const value = source.style.getPropertyValue(property);
171
+
172
+ if (value && !target.style.getPropertyValue(property)) {
173
+ target.style.setProperty(property, value);
174
+ }
175
+ }
176
+ }
177
+
178
+ #removeStyleProperties(element, properties) {
179
+ for (const property of properties) {
180
+ element.style.removeProperty(property);
181
+ }
182
+
183
+ if (element.style.length === 0) {
184
+ element.removeAttribute('style');
185
+ }
186
+ }
187
+
188
+ #normalizeBreakLines({ parentElement }) {
189
+ if (!this.#isBlockNode(parentElement)) return;
190
+ if (!parentElement.textContent) return;
191
+
192
+ const fragment = this.dom.createDocumentFragment();
193
+ const children = Array.from(parentElement.childNodes);
194
+ const parentTemplate = parentElement.cloneNode(true);
195
+
196
+ parentTemplate.innerHTML = '';
197
+ let capturingParagraph = parentTemplate.cloneNode();
198
+
199
+ const append = (node) => {
200
+ this.#assignElementProperties(node, parentElement, HtmlNormalizer.BLOCK_STYLES);
201
+ fragment.append(node);
202
+ };
203
+
204
+ for (const child of children) {
205
+ if (child.tagName === 'BR') {
206
+ append(capturingParagraph);
207
+ capturingParagraph = parentTemplate.cloneNode();
208
+ continue;
209
+ }
210
+
211
+ capturingParagraph.append(child);
212
+ }
213
+
214
+ fragment.append(capturingParagraph);
215
+ parentElement.replaceWith(fragment);
216
+ }
217
+
218
+ #normalizeBlockTextDecoration() {
219
+ const blockEls = this.dom.querySelectorAll('[style*="text-decoration"]:where(p, h1, h2, h3, h4, li)');
220
+
221
+ for (const blockEl of blockEls) {
222
+ this.#moveTextDecorationToChildren(blockEl);
223
+ }
224
+ }
225
+
226
+ #moveTextDecorationToChildren(blockEl) {
227
+ const blockDecoration = this.#parseTextDecoration(blockEl);
228
+
229
+ blockEl.style.removeProperty('text-decoration-line');
230
+ blockEl.style.removeProperty('text-decoration');
231
+ if (!blockEl.style.cssText) blockEl.removeAttribute('style');
232
+
233
+ if (blockDecoration.none) return;
234
+
235
+ for (const childNode of blockEl.childNodes) {
236
+ const textEl = this.#wrapTextNode(blockEl, childNode);
237
+ const textDecoration = this.#parseTextDecoration(textEl);
238
+
239
+ const mergedDecoration = {
240
+ underline: textDecoration.underline || blockDecoration.underline,
241
+ line_through: textDecoration.line_through || blockDecoration.line_through
242
+ };
243
+
244
+ textEl.style.removeProperty('text-decoration-line');
245
+ textEl.style.removeProperty('text-decoration');
246
+
247
+ textEl.style.textDecoration = Object.entries(mergedDecoration)
248
+ .filter(([, value]) => value)
249
+ .map(([name]) => name.replace('_', '-'))
250
+ .join(' ');
251
+ }
252
+ }
253
+
254
+ #parseTextDecoration(element) {
255
+ const { textDecoration, textDecorationLine } = element.style;
256
+ const decoration = textDecoration || textDecorationLine || '';
257
+
258
+ return {
259
+ none: decoration.includes('none'),
260
+ underline: decoration.includes('underline'),
261
+ line_through: decoration.includes('line-through')
262
+ };
263
+ }
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
+
287
+ #wrapTextNode(parent, node) {
288
+ if (node.nodeType !== this.#Node.TEXT_NODE) return node;
289
+
290
+ const span = this.dom.createElement('span');
291
+
292
+ span.append(node.cloneNode());
293
+ parent.replaceChild(span, node);
294
+
295
+ return span;
296
+ }
297
+ }