suneditor 3.0.0-beta.2 → 3.0.0-beta.20

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 (177) hide show
  1. package/CONTRIBUTING.md +186 -184
  2. package/LICENSE +21 -21
  3. package/README.md +157 -180
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +126 -123
  7. package/src/assets/design/color.css +131 -121
  8. package/src/assets/design/index.css +3 -3
  9. package/src/assets/design/size.css +37 -35
  10. package/src/assets/design/typography.css +37 -37
  11. package/src/assets/icons/defaultIcons.js +247 -232
  12. package/src/assets/suneditor-contents.css +779 -778
  13. package/src/assets/suneditor.css +43 -35
  14. package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
  19. package/src/core/base/eventManager.js +1550 -1484
  20. package/src/core/base/history.js +355 -355
  21. package/src/core/class/char.js +163 -162
  22. package/src/core/class/component.js +856 -842
  23. package/src/core/class/format.js +3433 -3422
  24. package/src/core/class/html.js +1927 -1890
  25. package/src/core/class/menu.js +357 -346
  26. package/src/core/class/nodeTransform.js +424 -424
  27. package/src/core/class/offset.js +858 -891
  28. package/src/core/class/selection.js +710 -620
  29. package/src/core/class/shortcuts.js +98 -98
  30. package/src/core/class/toolbar.js +438 -430
  31. package/src/core/class/ui.js +424 -422
  32. package/src/core/class/viewer.js +750 -750
  33. package/src/core/editor.js +1810 -1708
  34. package/src/core/section/actives.js +268 -241
  35. package/src/core/section/constructor.js +1348 -1661
  36. package/src/core/section/context.js +102 -102
  37. package/src/core/section/documentType.js +582 -561
  38. package/src/core/section/options.js +367 -0
  39. package/src/core/util/instanceCheck.js +59 -0
  40. package/src/editorInjector/_classes.js +36 -36
  41. package/src/editorInjector/_core.js +92 -92
  42. package/src/editorInjector/index.js +75 -75
  43. package/src/events.js +634 -622
  44. package/src/helper/clipboard.js +59 -59
  45. package/src/helper/converter.js +586 -564
  46. package/src/helper/dom/domCheck.js +304 -304
  47. package/src/helper/dom/domQuery.js +677 -669
  48. package/src/helper/dom/domUtils.js +618 -557
  49. package/src/helper/dom/index.js +12 -12
  50. package/src/helper/env.js +249 -240
  51. package/src/helper/index.js +25 -25
  52. package/src/helper/keyCodeMap.js +183 -183
  53. package/src/helper/numbers.js +72 -72
  54. package/src/helper/unicode.js +47 -47
  55. package/src/langs/ckb.js +231 -231
  56. package/src/langs/cs.js +231 -231
  57. package/src/langs/da.js +231 -231
  58. package/src/langs/de.js +231 -231
  59. package/src/langs/en.js +230 -230
  60. package/src/langs/es.js +231 -231
  61. package/src/langs/fa.js +231 -231
  62. package/src/langs/fr.js +231 -231
  63. package/src/langs/he.js +231 -231
  64. package/src/langs/hu.js +230 -230
  65. package/src/langs/index.js +28 -28
  66. package/src/langs/it.js +231 -231
  67. package/src/langs/ja.js +230 -230
  68. package/src/langs/km.js +230 -230
  69. package/src/langs/ko.js +230 -230
  70. package/src/langs/lv.js +231 -231
  71. package/src/langs/nl.js +231 -231
  72. package/src/langs/pl.js +231 -231
  73. package/src/langs/pt_br.js +231 -231
  74. package/src/langs/ro.js +231 -231
  75. package/src/langs/ru.js +231 -231
  76. package/src/langs/se.js +231 -231
  77. package/src/langs/tr.js +231 -231
  78. package/src/langs/uk.js +231 -231
  79. package/src/langs/ur.js +231 -231
  80. package/src/langs/zh_cn.js +231 -231
  81. package/src/modules/ApiManager.js +191 -191
  82. package/src/modules/Browser.js +669 -667
  83. package/src/modules/ColorPicker.js +364 -362
  84. package/src/modules/Controller.js +474 -454
  85. package/src/modules/Figure.js +1620 -1617
  86. package/src/modules/FileManager.js +359 -359
  87. package/src/modules/HueSlider.js +577 -565
  88. package/src/modules/Modal.js +346 -346
  89. package/src/modules/ModalAnchorEditor.js +643 -643
  90. package/src/modules/SelectMenu.js +549 -549
  91. package/src/modules/_DragHandle.js +17 -17
  92. package/src/modules/index.js +14 -14
  93. package/src/plugins/browser/audioGallery.js +83 -83
  94. package/src/plugins/browser/fileBrowser.js +103 -103
  95. package/src/plugins/browser/fileGallery.js +83 -83
  96. package/src/plugins/browser/imageGallery.js +81 -81
  97. package/src/plugins/browser/videoGallery.js +103 -103
  98. package/src/plugins/command/blockquote.js +61 -60
  99. package/src/plugins/command/exportPDF.js +134 -134
  100. package/src/plugins/command/fileUpload.js +456 -456
  101. package/src/plugins/command/list_bulleted.js +149 -148
  102. package/src/plugins/command/list_numbered.js +152 -151
  103. package/src/plugins/dropdown/align.js +157 -155
  104. package/src/plugins/dropdown/backgroundColor.js +108 -104
  105. package/src/plugins/dropdown/font.js +141 -137
  106. package/src/plugins/dropdown/fontColor.js +109 -105
  107. package/src/plugins/dropdown/formatBlock.js +170 -178
  108. package/src/plugins/dropdown/hr.js +152 -152
  109. package/src/plugins/dropdown/layout.js +83 -83
  110. package/src/plugins/dropdown/lineHeight.js +131 -130
  111. package/src/plugins/dropdown/list.js +123 -122
  112. package/src/plugins/dropdown/paragraphStyle.js +138 -138
  113. package/src/plugins/dropdown/table.js +4110 -4000
  114. package/src/plugins/dropdown/template.js +83 -83
  115. package/src/plugins/dropdown/textStyle.js +149 -149
  116. package/src/plugins/field/mention.js +242 -242
  117. package/src/plugins/index.js +120 -120
  118. package/src/plugins/input/fontSize.js +414 -410
  119. package/src/plugins/input/pageNavigator.js +71 -70
  120. package/src/plugins/modal/audio.js +677 -677
  121. package/src/plugins/modal/drawing.js +537 -531
  122. package/src/plugins/modal/embed.js +886 -886
  123. package/src/plugins/modal/image.js +1377 -1376
  124. package/src/plugins/modal/link.js +248 -240
  125. package/src/plugins/modal/math.js +563 -563
  126. package/src/plugins/modal/video.js +1226 -1226
  127. package/src/plugins/popup/anchor.js +224 -222
  128. package/src/suneditor.js +114 -107
  129. package/src/themes/dark.css +132 -122
  130. package/src/typedef.js +132 -130
  131. package/types/assets/icons/defaultIcons.d.ts +8 -0
  132. package/types/core/base/eventManager.d.ts +29 -4
  133. package/types/core/class/char.d.ts +2 -1
  134. package/types/core/class/component.d.ts +1 -2
  135. package/types/core/class/format.d.ts +8 -1
  136. package/types/core/class/html.d.ts +8 -0
  137. package/types/core/class/menu.d.ts +8 -0
  138. package/types/core/class/offset.d.ts +24 -26
  139. package/types/core/class/selection.d.ts +2 -0
  140. package/types/core/class/toolbar.d.ts +6 -0
  141. package/types/core/class/ui.d.ts +1 -1
  142. package/types/core/editor.d.ts +34 -12
  143. package/types/core/section/constructor.d.ts +5 -638
  144. package/types/core/section/documentType.d.ts +12 -2
  145. package/types/core/section/options.d.ts +740 -0
  146. package/types/core/util/instanceCheck.d.ts +50 -0
  147. package/types/editorInjector/_core.d.ts +5 -5
  148. package/types/editorInjector/index.d.ts +2 -2
  149. package/types/events.d.ts +2 -0
  150. package/types/helper/converter.d.ts +9 -0
  151. package/types/helper/dom/domQuery.d.ts +5 -5
  152. package/types/helper/dom/domUtils.d.ts +8 -0
  153. package/types/helper/env.d.ts +6 -1
  154. package/types/helper/index.d.ts +4 -1
  155. package/types/index.d.ts +122 -120
  156. package/types/langs/_Lang.d.ts +194 -194
  157. package/types/modules/ColorPicker.d.ts +5 -1
  158. package/types/modules/Controller.d.ts +8 -4
  159. package/types/modules/Figure.d.ts +2 -1
  160. package/types/modules/HueSlider.d.ts +4 -1
  161. package/types/modules/SelectMenu.d.ts +1 -1
  162. package/types/plugins/command/blockquote.d.ts +1 -0
  163. package/types/plugins/command/list_bulleted.d.ts +1 -0
  164. package/types/plugins/command/list_numbered.d.ts +1 -0
  165. package/types/plugins/dropdown/align.d.ts +1 -0
  166. package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
  167. package/types/plugins/dropdown/font.d.ts +1 -0
  168. package/types/plugins/dropdown/fontColor.d.ts +1 -0
  169. package/types/plugins/dropdown/formatBlock.d.ts +3 -2
  170. package/types/plugins/dropdown/lineHeight.d.ts +1 -0
  171. package/types/plugins/dropdown/list.d.ts +1 -0
  172. package/types/plugins/dropdown/table.d.ts +6 -0
  173. package/types/plugins/input/fontSize.d.ts +1 -0
  174. package/types/plugins/modal/drawing.d.ts +4 -0
  175. package/types/plugins/modal/link.d.ts +32 -15
  176. package/types/suneditor.d.ts +13 -9
  177. package/types/typedef.d.ts +8 -0
@@ -1,424 +1,424 @@
1
- /**
2
- * @fileoverview Node util class
3
- */
4
-
5
- import CoreInjector from '../../editorInjector/_core';
6
- import { dom, unicode, numbers } from '../../helper';
7
-
8
- /**
9
- * @typedef {Omit<NodeTransform & Partial<__se__EditorInjector>, 'nodeTransform'>} NodeTransformThis
10
- */
11
-
12
- /**
13
- * @constructor
14
- * @this {NodeTransformThis}
15
- * @description Node utility class. split, merge, etc.
16
- * @param {__se__EditorCore} editor - The root editor instance
17
- */
18
- function NodeTransform(editor) {
19
- CoreInjector.call(this, editor);
20
- }
21
-
22
- NodeTransform.prototype = {
23
- /**
24
- * @this {NodeTransformThis}
25
- * @template {HTMLElement} T
26
- * @description Split all tags based on "baseNode"
27
- * @param {Node} baseNode Element or text node on which to base
28
- * @param {?number|Node} offset Text offset of "baseNode" (Only valid when "baseNode" is a text node)
29
- * @param {number} [depth=0] The nesting depth of the element being split. (default: 0)
30
- * @returns {T} The last element of the splited tag.
31
- */
32
- split(baseNode, offset, depth) {
33
- if (dom.check.isWysiwygFrame(baseNode) || this.component.is(baseNode) || !baseNode) return /** @type {T} */ (baseNode);
34
-
35
- if (offset && !numbers.is(offset)) {
36
- const children = baseNode.childNodes;
37
- let index = dom.query.getPositionIndex(/** @type {Node} */ (offset));
38
- const prev = baseNode.cloneNode(false);
39
- const next = baseNode.cloneNode(false);
40
- for (let i = 0, len = children.length; i < len; i++) {
41
- if (i < index) prev.appendChild(children[i]);
42
- else if (i > index) next.appendChild(children[i]);
43
- else continue;
44
- i--;
45
- len--;
46
- index--;
47
- }
48
-
49
- if (prev.childNodes.length > 0) baseNode.parentNode.insertBefore(prev, baseNode);
50
- if (next.childNodes.length > 0) baseNode.parentNode.insertBefore(next, /** @type {HTMLElement|Text} */ (baseNode).nextElementSibling);
51
-
52
- return /** @type {T} */ (baseNode);
53
- }
54
-
55
- const bp = baseNode.parentNode;
56
- let index = 0;
57
- let suffixIndex = 1;
58
- let next = true;
59
- let newEl, children, temp;
60
- if (!depth || depth < 0) depth = 0;
61
-
62
- if (dom.check.isText(baseNode)) {
63
- index = dom.query.getPositionIndex(baseNode);
64
- offset = Number(offset);
65
- if (offset >= 0 && baseNode.length !== offset) {
66
- baseNode.splitText(offset);
67
- const after = /** @type {Text} */ (dom.query.getNodeFromPath([index + 1], bp));
68
- if (dom.check.isZeroWidth(after)) after.data = unicode.zeroWidthSpace;
69
- }
70
- } else if (baseNode.nodeType === 1) {
71
- if (offset === 0) {
72
- while (baseNode.firstChild) {
73
- baseNode = baseNode.firstChild;
74
- }
75
- if (baseNode.nodeType === 3) {
76
- const after = dom.utils.createTextNode(unicode.zeroWidthSpace);
77
- baseNode.parentNode.insertBefore(after, baseNode);
78
- baseNode = after;
79
- }
80
- }
81
-
82
- if (!baseNode.previousSibling) {
83
- if (dom.query.getNodeDepth(baseNode) === depth) next = false;
84
- } else {
85
- baseNode = baseNode.previousSibling;
86
- }
87
- }
88
-
89
- if (baseNode.nodeType === 1) suffixIndex = 0;
90
- let depthEl = baseNode;
91
- while (dom.query.getNodeDepth(depthEl) > depth) {
92
- index = dom.query.getPositionIndex(depthEl) + suffixIndex;
93
- depthEl = depthEl.parentNode;
94
-
95
- temp = newEl;
96
- newEl = depthEl.cloneNode(false);
97
- children = depthEl.childNodes;
98
-
99
- if (temp) {
100
- if (dom.check.isListCell(newEl) && dom.check.isList(temp) && temp.firstElementChild) {
101
- newEl.innerHTML = temp.firstElementChild.innerHTML;
102
- dom.utils.removeItem(temp.firstElementChild);
103
- if (temp.children.length > 0) newEl.appendChild(temp);
104
- } else {
105
- newEl.appendChild(temp);
106
- }
107
- }
108
-
109
- while (children[index]) {
110
- newEl.appendChild(children[index]);
111
- }
112
- }
113
-
114
- if (depthEl.childNodes.length <= 1 && (!depthEl.firstChild || depthEl.firstChild.textContent.length === 0)) /** @type {HTMLElement} */ (depthEl).innerHTML = '<br>';
115
-
116
- const pElement = depthEl.parentNode;
117
- if (next) depthEl = depthEl.nextSibling;
118
- if (!newEl) return /** @type {T} */ (depthEl);
119
-
120
- this.mergeSameTags(newEl, null, false);
121
- this.mergeNestedTags(newEl, dom.check.isList);
122
-
123
- if (newEl.childNodes.length > 0) pElement.insertBefore(newEl, depthEl);
124
- else newEl = depthEl;
125
-
126
- if (dom.check.isListCell(newEl) && newEl.children && dom.check.isList(newEl.children[0])) {
127
- newEl.insertBefore(dom.utils.createElement('BR'), newEl.children[0]);
128
- }
129
-
130
- if (bp.childNodes.length === 0) dom.utils.removeItem(bp);
131
-
132
- return /** @type {T} */ (newEl);
133
- },
134
-
135
- /**
136
- * @this {NodeTransformThis}
137
- * @description Use with "npdePath (dom-query-GetNodePath)" to merge the same attributes and tags if they are present and modify the nodepath.
138
- * - If "offset" has been changed, it will return as much "offset" as it has been modified.
139
- * - An array containing change offsets is returned in the order of the "nodePathArray" array.
140
- * @param {Node} element Element
141
- * @param {?number[][]=} nodePathArray Array of NodePath object ([dom-query-GetNodePath(), ..])
142
- * @param {?boolean=} onlyText If true, non-text nodes like 'span', 'strong'.. are ignored.
143
- * @returns {Array<number>} [offset, ..]
144
- */
145
- mergeSameTags(element, nodePathArray, onlyText) {
146
- // eslint-disable-next-line @typescript-eslint/no-this-alias
147
- const inst = this;
148
- const nodePathLen = nodePathArray ? nodePathArray.length : 0;
149
- let offsets = null;
150
-
151
- if (nodePathLen) {
152
- offsets = Array.apply(null, new Array(nodePathLen)).map(Number.prototype.valueOf, 0);
153
- }
154
-
155
- (function recursionFunc(current, depth, depthIndex) {
156
- const children = current.childNodes;
157
-
158
- for (let i = 0, len = children.length, child, next; i < len; i++) {
159
- child = /** @type {HTMLElement} */ (children[i]);
160
- next = /** @type {HTMLElement} */ (children[i + 1]);
161
- if (!child) break;
162
- if (dom.check.isBreak(child) || dom.check.isMedia(child) || dom.check.isInputElement(child)) continue;
163
- if ((onlyText && inst.format._isIgnoreNodeChange(child)) || (!onlyText && (dom.check.isTableElements(child) || dom.check.isListCell(child) || (inst.format.isLine(child) && !inst.format.isBrLine(child))))) {
164
- if (dom.check.isTableElements(child) || dom.check.isListCell(child)) {
165
- recursionFunc(child, depth + 1, i);
166
- }
167
- continue;
168
- }
169
- if (len === 1 && current.nodeName === child.nodeName && current.parentNode) {
170
- // update nodePath
171
- if (nodePathLen) {
172
- let path, c, p, cDepth, spliceDepth;
173
- for (let n = 0; n < nodePathLen; n++) {
174
- path = nodePathArray[n];
175
- if (path && path[depth] === i) {
176
- c = child;
177
- p = current;
178
- cDepth = depth;
179
- spliceDepth = true;
180
- while (cDepth >= 0) {
181
- if (dom.utils.getArrayIndex(p.childNodes, c) !== path[cDepth]) {
182
- spliceDepth = false;
183
- break;
184
- }
185
- c = child.parentNode;
186
- p = c.parentNode;
187
- cDepth--;
188
- }
189
- if (spliceDepth) {
190
- path.splice(depth, 1);
191
- path[depth] = i;
192
- }
193
- }
194
- }
195
- }
196
-
197
- // merge tag
198
- dom.utils.copyTagAttributes(child, current);
199
- current.parentNode.insertBefore(child, current);
200
- dom.utils.removeItem(current);
201
- }
202
-
203
- if (!next) {
204
- if (child.nodeType === 1) recursionFunc(child, depth + 1, i);
205
- break;
206
- }
207
-
208
- if (child.nodeName === next.nodeName && dom.check.isSameAttributes(child, next) && child.getAttribute?.('href') === next.getAttribute?.('href')) {
209
- const childs = child.childNodes;
210
- let childLength = 0;
211
- for (let n = 0, nLen = childs.length; n < nLen; n++) {
212
- if (childs[n].textContent.length > 0) childLength++;
213
- }
214
-
215
- const l = child.lastChild;
216
- const r = next.firstChild;
217
- let addOffset = 0;
218
- if (l && r) {
219
- const textOffset = l.nodeType === 3 && r.nodeType === 3;
220
- addOffset = l.textContent.length;
221
- let tempL = l.previousSibling;
222
- while (tempL && tempL.nodeType === 3) {
223
- addOffset += tempL.textContent.length;
224
- tempL = tempL.previousSibling;
225
- }
226
-
227
- if (childLength > 0 && l.nodeType === 3 && r.nodeType === 3 && (l.textContent.length > 0 || r.textContent.length > 0)) childLength--;
228
-
229
- if (nodePathLen) {
230
- let path = null;
231
- for (let n = 0; n < nodePathLen; n++) {
232
- path = nodePathArray[n];
233
- if (path && path[depth] > i) {
234
- if (depth > 0 && path[depth - 1] !== depthIndex) continue;
235
-
236
- path[depth] -= 1;
237
- if (path[depth + 1] >= 0 && path[depth] === i) {
238
- path[depth + 1] += childLength;
239
- if (textOffset) {
240
- if (l && l.nodeType === 3 && r && r.nodeType === 3) {
241
- offsets[n] += addOffset;
242
- }
243
- }
244
- }
245
- }
246
- }
247
- }
248
- }
249
-
250
- if (child.nodeType === 3) {
251
- addOffset = child.textContent.length;
252
- child.textContent += next.textContent;
253
- if (nodePathLen) {
254
- let path = null;
255
- for (let n = 0; n < nodePathLen; n++) {
256
- path = nodePathArray[n];
257
- if (path && path[depth] > i) {
258
- if (depth > 0 && path[depth - 1] !== depthIndex) continue;
259
-
260
- path[depth] -= 1;
261
- if (path[depth + 1] >= 0 && path[depth] === i) {
262
- path[depth + 1] += childLength;
263
- offsets[n] += addOffset;
264
- }
265
- }
266
- }
267
- }
268
- } else {
269
- child.innerHTML += next.innerHTML;
270
- }
271
-
272
- dom.utils.removeItem(next);
273
- i--;
274
- } else if (child.nodeType === 1) {
275
- recursionFunc(child, depth + 1, i);
276
- }
277
- }
278
- })(element, 0, 0);
279
-
280
- return offsets;
281
- },
282
-
283
- /**
284
- * @this {NodeTransformThis}
285
- * @description Remove nested tags without other child nodes.
286
- * @param {Node} element Element object
287
- * @param {?(current: Node) => boolean|string=} validation Validation function / String("tag1|tag2..") / If null, all tags are applicable.
288
- */
289
- mergeNestedTags(element, validation) {
290
- if (typeof validation === 'string') {
291
- const tagRegExp = new RegExp(`^(${validation ? validation : '.+'})$`, 'i');
292
- validation = (current) => tagRegExp.test(current.nodeName);
293
- } else if (typeof validation !== 'function') {
294
- validation = () => true;
295
- }
296
-
297
- (function recursionFunc(current) {
298
- let children = current.children;
299
- if (children.length === 1 && children[0].nodeName === current.nodeName && validation(current)) {
300
- const temp = children[0];
301
- children = temp.children;
302
- while (children[0]) {
303
- current.appendChild(children[0]);
304
- }
305
- current.removeChild(temp);
306
- }
307
-
308
- for (let i = 0, len = current.children.length; i < len; i++) {
309
- recursionFunc(current.children[i]);
310
- }
311
- })(/** @type {Element} */ (element));
312
- },
313
-
314
- /**
315
- * @this {NodeTransformThis}
316
- * @description Delete itself and all parent nodes that match the condition.
317
- * - Returns an {sc: previousSibling, ec: nextSibling}(the deleted node reference) or null.
318
- * @param {Node} item Node to be remove
319
- * @param {?(current: Node) => boolean=} validation Validation function. default(Deleted if it only have breakLine and blanks)
320
- * @param {?Node=} stopParent Stop when the parent node reaches stopParent
321
- * @returns {{sc: Node|null, ec: Node|null}|null} {sc: previousSibling, ec: nextSibling} (the deleted node reference) or null.
322
- */
323
- removeAllParents(item, validation, stopParent) {
324
- if (!item) return null;
325
- let cc = null;
326
- if (!validation) {
327
- validation = (current) => {
328
- if (current === stopParent || this.component.is(current)) return false;
329
- const text = current.textContent.trim();
330
- return text.length === 0 || /^(\n|\u200B)+$/.test(text);
331
- };
332
- }
333
-
334
- (function recursionFunc(element) {
335
- if (!dom.check.isWysiwygFrame(element)) {
336
- const parent = element.parentNode;
337
- if (parent && validation(element)) {
338
- cc = {
339
- sc: element.previousElementSibling,
340
- ec: element.nextElementSibling
341
- };
342
- dom.utils.removeItem(element);
343
- recursionFunc(/** @type {Element} */ (parent));
344
- }
345
- }
346
- })(/** @type {Element} */ (item));
347
-
348
- return cc;
349
- },
350
-
351
- /**
352
- * @this {NodeTransformThis}
353
- * @description Delete a empty child node of argument element
354
- * @param {Node} element Element node
355
- * @param {?Node} notRemoveNode Do not remove node
356
- * @param {boolean} forceDelete When all child nodes are deleted, the parent node is also deleted.
357
- */
358
- removeEmptyNode(element, notRemoveNode, forceDelete) {
359
- // eslint-disable-next-line @typescript-eslint/no-this-alias
360
- const inst = this;
361
- const allowedEmptyTags = this.options.get('allowedEmptyTags');
362
-
363
- if (notRemoveNode) {
364
- notRemoveNode = dom.query.getParentElement(notRemoveNode, (current) => element === current.parentElement);
365
- }
366
-
367
- (function recursionFunc(current) {
368
- if (inst.format._notTextNode(current) || current === notRemoveNode || dom.check.isNonEditable(current)) return 0;
369
- if (current !== element && dom.check.isZeroWidth(current.textContent) && (!current.firstChild || !dom.check.isBreak(current.firstChild)) && !current.querySelector(allowedEmptyTags)) {
370
- if (current.parentNode) {
371
- current.parentNode.removeChild(current);
372
- return -1;
373
- }
374
- } else {
375
- const children = current.children;
376
- for (let i = 0, len = children.length, r = 0; i < len; i++) {
377
- if (!children[i + r] || inst.component.is(children[i + r])) continue;
378
- r += recursionFunc(children[i + r]);
379
- }
380
- }
381
-
382
- return 0;
383
- })(/** @type {Element} */ (element));
384
-
385
- if (element.childNodes.length === 0) {
386
- if (forceDelete) {
387
- dom.utils.removeItem(element);
388
- } else {
389
- /** @type {HTMLElement} */ (element).innerHTML = '<br>';
390
- }
391
- }
392
- },
393
-
394
- /**
395
- * @this {NodeTransformThis}
396
- * @description Creates a nested node structure from the given array of nodes.
397
- * @param {__se__NodeCollection} nodeArray An array of nodes to clone. The first node in the array will be the top-level parent.
398
- * @param {?(current: Node) => boolean=} validate A validate function.
399
- * @returns {{ parent: Node, inner: Node }} An object containing the top-level parent node and the innermost child node.
400
- */
401
- createNestedNode(nodeArray, validate) {
402
- if (typeof validate !== 'function') validate = () => true;
403
-
404
- const el = /** @type {HTMLElement} */ (nodeArray[0].cloneNode(false));
405
- let n = el;
406
- for (let i = 1, len = nodeArray.length, t; i < len; i++) {
407
- if (!validate(nodeArray[i])) continue;
408
- t = /** @type {HTMLElement} */ (nodeArray[i].cloneNode(false));
409
- n.appendChild(t);
410
- n = t;
411
- }
412
-
413
- n.innerHTML = '';
414
-
415
- return {
416
- parent: el,
417
- inner: n
418
- };
419
- },
420
-
421
- constructor: NodeTransform
422
- };
423
-
424
- export default NodeTransform;
1
+ /**
2
+ * @fileoverview Node util class
3
+ */
4
+
5
+ import CoreInjector from '../../editorInjector/_core';
6
+ import { dom, unicode, numbers } from '../../helper';
7
+
8
+ /**
9
+ * @typedef {Omit<NodeTransform & Partial<__se__EditorInjector>, 'nodeTransform'>} NodeTransformThis
10
+ */
11
+
12
+ /**
13
+ * @constructor
14
+ * @this {NodeTransformThis}
15
+ * @description Node utility class. split, merge, etc.
16
+ * @param {__se__EditorCore} editor - The root editor instance
17
+ */
18
+ function NodeTransform(editor) {
19
+ CoreInjector.call(this, editor);
20
+ }
21
+
22
+ NodeTransform.prototype = {
23
+ /**
24
+ * @this {NodeTransformThis}
25
+ * @template {HTMLElement} T
26
+ * @description Split all tags based on "baseNode"
27
+ * @param {Node} baseNode Element or text node on which to base
28
+ * @param {?number|Node} offset Text offset of "baseNode" (Only valid when "baseNode" is a text node)
29
+ * @param {number} [depth=0] The nesting depth of the element being split. (default: 0)
30
+ * @returns {T} The last element of the splited tag.
31
+ */
32
+ split(baseNode, offset, depth) {
33
+ if (dom.check.isWysiwygFrame(baseNode) || this.component.is(baseNode) || !baseNode) return /** @type {T} */ (baseNode);
34
+
35
+ if (offset && !numbers.is(offset)) {
36
+ const children = baseNode.childNodes;
37
+ let index = dom.query.getPositionIndex(/** @type {Node} */ (offset));
38
+ const prev = baseNode.cloneNode(false);
39
+ const next = baseNode.cloneNode(false);
40
+ for (let i = 0, len = children.length; i < len; i++) {
41
+ if (i < index) prev.appendChild(children[i]);
42
+ else if (i > index) next.appendChild(children[i]);
43
+ else continue;
44
+ i--;
45
+ len--;
46
+ index--;
47
+ }
48
+
49
+ if (prev.childNodes.length > 0) baseNode.parentNode.insertBefore(prev, baseNode);
50
+ if (next.childNodes.length > 0) baseNode.parentNode.insertBefore(next, /** @type {HTMLElement|Text} */ (baseNode).nextElementSibling);
51
+
52
+ return /** @type {T} */ (baseNode);
53
+ }
54
+
55
+ const bp = baseNode.parentNode;
56
+ let index = 0;
57
+ let suffixIndex = 1;
58
+ let next = true;
59
+ let newEl, children, temp;
60
+ if (!depth || depth < 0) depth = 0;
61
+
62
+ if (dom.check.isText(baseNode)) {
63
+ index = dom.query.getPositionIndex(baseNode);
64
+ offset = Number(offset);
65
+ if (offset >= 0 && baseNode.length !== offset) {
66
+ baseNode.splitText(offset);
67
+ const after = /** @type {Text} */ (dom.query.getNodeFromPath([index + 1], bp));
68
+ if (dom.check.isZeroWidth(after)) after.data = unicode.zeroWidthSpace;
69
+ }
70
+ } else if (baseNode.nodeType === 1) {
71
+ if (offset === 0) {
72
+ while (baseNode.firstChild) {
73
+ baseNode = baseNode.firstChild;
74
+ }
75
+ if (baseNode.nodeType === 3) {
76
+ const after = dom.utils.createTextNode(unicode.zeroWidthSpace);
77
+ baseNode.parentNode.insertBefore(after, baseNode);
78
+ baseNode = after;
79
+ }
80
+ }
81
+
82
+ if (!baseNode.previousSibling) {
83
+ if (dom.query.getNodeDepth(baseNode) === depth) next = false;
84
+ } else {
85
+ baseNode = baseNode.previousSibling;
86
+ }
87
+ }
88
+
89
+ if (baseNode.nodeType === 1) suffixIndex = 0;
90
+ let depthEl = baseNode;
91
+ while (dom.query.getNodeDepth(depthEl) > depth) {
92
+ index = dom.query.getPositionIndex(depthEl) + suffixIndex;
93
+ depthEl = depthEl.parentNode;
94
+
95
+ temp = newEl;
96
+ newEl = depthEl.cloneNode(false);
97
+ children = depthEl.childNodes;
98
+
99
+ if (temp) {
100
+ if (dom.check.isListCell(newEl) && dom.check.isList(temp) && temp.firstElementChild) {
101
+ newEl.innerHTML = temp.firstElementChild.innerHTML;
102
+ dom.utils.removeItem(temp.firstElementChild);
103
+ if (temp.children.length > 0) newEl.appendChild(temp);
104
+ } else {
105
+ newEl.appendChild(temp);
106
+ }
107
+ }
108
+
109
+ while (children[index]) {
110
+ newEl.appendChild(children[index]);
111
+ }
112
+ }
113
+
114
+ if (depthEl.childNodes.length <= 1 && (!depthEl.firstChild || depthEl.firstChild.textContent.length === 0)) /** @type {HTMLElement} */ (depthEl).innerHTML = '<br>';
115
+
116
+ const pElement = depthEl.parentNode;
117
+ if (next) depthEl = depthEl.nextSibling;
118
+ if (!newEl) return /** @type {T} */ (depthEl);
119
+
120
+ this.mergeSameTags(newEl, null, false);
121
+ this.mergeNestedTags(newEl, dom.check.isList);
122
+
123
+ if (newEl.childNodes.length > 0) pElement.insertBefore(newEl, depthEl);
124
+ else newEl = depthEl;
125
+
126
+ if (dom.check.isListCell(newEl) && newEl.children && dom.check.isList(newEl.children[0])) {
127
+ newEl.insertBefore(dom.utils.createElement('BR'), newEl.children[0]);
128
+ }
129
+
130
+ if (bp.childNodes.length === 0) dom.utils.removeItem(bp);
131
+
132
+ return /** @type {T} */ (newEl);
133
+ },
134
+
135
+ /**
136
+ * @this {NodeTransformThis}
137
+ * @description Use with "npdePath (dom-query-GetNodePath)" to merge the same attributes and tags if they are present and modify the nodepath.
138
+ * - If "offset" has been changed, it will return as much "offset" as it has been modified.
139
+ * - An array containing change offsets is returned in the order of the "nodePathArray" array.
140
+ * @param {Node} element Element
141
+ * @param {?number[][]=} nodePathArray Array of NodePath object ([dom-query-GetNodePath(), ..])
142
+ * @param {?boolean=} onlyText If true, non-text nodes like 'span', 'strong'.. are ignored.
143
+ * @returns {Array<number>} [offset, ..]
144
+ */
145
+ mergeSameTags(element, nodePathArray, onlyText) {
146
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
147
+ const inst = this;
148
+ const nodePathLen = nodePathArray ? nodePathArray.length : 0;
149
+ let offsets = null;
150
+
151
+ if (nodePathLen) {
152
+ offsets = Array.apply(null, new Array(nodePathLen)).map(Number.prototype.valueOf, 0);
153
+ }
154
+
155
+ (function recursionFunc(current, depth, depthIndex) {
156
+ const children = current.childNodes;
157
+
158
+ for (let i = 0, len = children.length, child, next; i < len; i++) {
159
+ child = /** @type {HTMLElement} */ (children[i]);
160
+ next = /** @type {HTMLElement} */ (children[i + 1]);
161
+ if (!child) break;
162
+ if (dom.check.isBreak(child) || dom.check.isMedia(child) || dom.check.isInputElement(child)) continue;
163
+ if ((onlyText && inst.format._isIgnoreNodeChange(child)) || (!onlyText && (dom.check.isTableElements(child) || dom.check.isListCell(child) || (inst.format.isLine(child) && !inst.format.isBrLine(child))))) {
164
+ if (dom.check.isTableElements(child) || dom.check.isListCell(child)) {
165
+ recursionFunc(child, depth + 1, i);
166
+ }
167
+ continue;
168
+ }
169
+ if (len === 1 && current.nodeName === child.nodeName && current.parentNode) {
170
+ // update nodePath
171
+ if (nodePathLen) {
172
+ let path, c, p, cDepth, spliceDepth;
173
+ for (let n = 0; n < nodePathLen; n++) {
174
+ path = nodePathArray[n];
175
+ if (path && path[depth] === i) {
176
+ c = child;
177
+ p = current;
178
+ cDepth = depth;
179
+ spliceDepth = true;
180
+ while (cDepth >= 0) {
181
+ if (dom.utils.getArrayIndex(p.childNodes, c) !== path[cDepth]) {
182
+ spliceDepth = false;
183
+ break;
184
+ }
185
+ c = child.parentNode;
186
+ p = c.parentNode;
187
+ cDepth--;
188
+ }
189
+ if (spliceDepth) {
190
+ path.splice(depth, 1);
191
+ path[depth] = i;
192
+ }
193
+ }
194
+ }
195
+ }
196
+
197
+ // merge tag
198
+ dom.utils.copyTagAttributes(child, current);
199
+ current.parentNode.insertBefore(child, current);
200
+ dom.utils.removeItem(current);
201
+ }
202
+
203
+ if (!next) {
204
+ if (child.nodeType === 1) recursionFunc(child, depth + 1, i);
205
+ break;
206
+ }
207
+
208
+ if (child.nodeName === next.nodeName && dom.check.isSameAttributes(child, next) && child.getAttribute?.('href') === next.getAttribute?.('href')) {
209
+ const childs = child.childNodes;
210
+ let childLength = 0;
211
+ for (let n = 0, nLen = childs.length; n < nLen; n++) {
212
+ if (childs[n].textContent.length > 0) childLength++;
213
+ }
214
+
215
+ const l = child.lastChild;
216
+ const r = next.firstChild;
217
+ let addOffset = 0;
218
+ if (l && r) {
219
+ const textOffset = l.nodeType === 3 && r.nodeType === 3;
220
+ addOffset = l.textContent.length;
221
+ let tempL = l.previousSibling;
222
+ while (tempL && tempL.nodeType === 3) {
223
+ addOffset += tempL.textContent.length;
224
+ tempL = tempL.previousSibling;
225
+ }
226
+
227
+ if (childLength > 0 && l.nodeType === 3 && r.nodeType === 3 && (l.textContent.length > 0 || r.textContent.length > 0)) childLength--;
228
+
229
+ if (nodePathLen) {
230
+ let path = null;
231
+ for (let n = 0; n < nodePathLen; n++) {
232
+ path = nodePathArray[n];
233
+ if (path && path[depth] > i) {
234
+ if (depth > 0 && path[depth - 1] !== depthIndex) continue;
235
+
236
+ path[depth] -= 1;
237
+ if (path[depth + 1] >= 0 && path[depth] === i) {
238
+ path[depth + 1] += childLength;
239
+ if (textOffset) {
240
+ if (l && l.nodeType === 3 && r && r.nodeType === 3) {
241
+ offsets[n] += addOffset;
242
+ }
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ if (child.nodeType === 3) {
251
+ addOffset = child.textContent.length;
252
+ child.textContent += next.textContent;
253
+ if (nodePathLen) {
254
+ let path = null;
255
+ for (let n = 0; n < nodePathLen; n++) {
256
+ path = nodePathArray[n];
257
+ if (path && path[depth] > i) {
258
+ if (depth > 0 && path[depth - 1] !== depthIndex) continue;
259
+
260
+ path[depth] -= 1;
261
+ if (path[depth + 1] >= 0 && path[depth] === i) {
262
+ path[depth + 1] += childLength;
263
+ offsets[n] += addOffset;
264
+ }
265
+ }
266
+ }
267
+ }
268
+ } else {
269
+ child.innerHTML += next.innerHTML;
270
+ }
271
+
272
+ dom.utils.removeItem(next);
273
+ i--;
274
+ } else if (child.nodeType === 1) {
275
+ recursionFunc(child, depth + 1, i);
276
+ }
277
+ }
278
+ })(element, 0, 0);
279
+
280
+ return offsets;
281
+ },
282
+
283
+ /**
284
+ * @this {NodeTransformThis}
285
+ * @description Remove nested tags without other child nodes.
286
+ * @param {Node} element Element object
287
+ * @param {?(current: Node) => boolean|string=} validation Validation function / String("tag1|tag2..") / If null, all tags are applicable.
288
+ */
289
+ mergeNestedTags(element, validation) {
290
+ if (typeof validation === 'string') {
291
+ const tagRegExp = new RegExp(`^(${validation ? validation : '.+'})$`, 'i');
292
+ validation = (current) => tagRegExp.test(current.nodeName);
293
+ } else if (typeof validation !== 'function') {
294
+ validation = () => true;
295
+ }
296
+
297
+ (function recursionFunc(current) {
298
+ let children = current.children;
299
+ if (children.length === 1 && children[0].nodeName === current.nodeName && validation(current)) {
300
+ const temp = children[0];
301
+ children = temp.children;
302
+ while (children[0]) {
303
+ current.appendChild(children[0]);
304
+ }
305
+ current.removeChild(temp);
306
+ }
307
+
308
+ for (let i = 0, len = current.children.length; i < len; i++) {
309
+ recursionFunc(current.children[i]);
310
+ }
311
+ })(/** @type {Element} */ (element));
312
+ },
313
+
314
+ /**
315
+ * @this {NodeTransformThis}
316
+ * @description Delete itself and all parent nodes that match the condition.
317
+ * - Returns an {sc: previousSibling, ec: nextSibling}(the deleted node reference) or null.
318
+ * @param {Node} item Node to be remove
319
+ * @param {?(current: Node) => boolean=} validation Validation function. default(Deleted if it only have breakLine and blanks)
320
+ * @param {?Node=} stopParent Stop when the parent node reaches stopParent
321
+ * @returns {{sc: Node|null, ec: Node|null}|null} {sc: previousSibling, ec: nextSibling} (the deleted node reference) or null.
322
+ */
323
+ removeAllParents(item, validation, stopParent) {
324
+ if (!item) return null;
325
+ let cc = null;
326
+ if (!validation) {
327
+ validation = (current) => {
328
+ if (current === stopParent || this.component.is(current)) return false;
329
+ const text = current.textContent.trim();
330
+ return text.length === 0 || /^(\n|\u200B)+$/.test(text);
331
+ };
332
+ }
333
+
334
+ (function recursionFunc(element) {
335
+ if (!dom.check.isWysiwygFrame(element)) {
336
+ const parent = element.parentNode;
337
+ if (parent && validation(element)) {
338
+ cc = {
339
+ sc: element.previousElementSibling,
340
+ ec: element.nextElementSibling
341
+ };
342
+ dom.utils.removeItem(element);
343
+ recursionFunc(/** @type {Element} */ (parent));
344
+ }
345
+ }
346
+ })(/** @type {Element} */ (item));
347
+
348
+ return cc;
349
+ },
350
+
351
+ /**
352
+ * @this {NodeTransformThis}
353
+ * @description Delete a empty child node of argument element
354
+ * @param {Node} element Element node
355
+ * @param {?Node} notRemoveNode Do not remove node
356
+ * @param {boolean} forceDelete When all child nodes are deleted, the parent node is also deleted.
357
+ */
358
+ removeEmptyNode(element, notRemoveNode, forceDelete) {
359
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
360
+ const inst = this;
361
+ const allowedEmptyTags = this.options.get('allowedEmptyTags');
362
+
363
+ if (notRemoveNode) {
364
+ notRemoveNode = dom.query.getParentElement(notRemoveNode, (current) => element === current.parentElement);
365
+ }
366
+
367
+ (function recursionFunc(current) {
368
+ if (inst.format._notTextNode(current) || current === notRemoveNode || dom.check.isNonEditable(current)) return 0;
369
+ if (current !== element && dom.check.isZeroWidth(current.textContent) && (!current.firstChild || !dom.check.isBreak(current.firstChild)) && !current.querySelector(allowedEmptyTags)) {
370
+ if (current.parentNode) {
371
+ current.parentNode.removeChild(current);
372
+ return -1;
373
+ }
374
+ } else {
375
+ const children = current.children;
376
+ for (let i = 0, len = children.length, r = 0; i < len; i++) {
377
+ if (!children[i + r] || inst.component.is(children[i + r])) continue;
378
+ r += recursionFunc(children[i + r]);
379
+ }
380
+ }
381
+
382
+ return 0;
383
+ })(/** @type {Element} */ (element));
384
+
385
+ if (element.childNodes.length === 0) {
386
+ if (forceDelete) {
387
+ dom.utils.removeItem(element);
388
+ } else {
389
+ /** @type {HTMLElement} */ (element).innerHTML = '<br>';
390
+ }
391
+ }
392
+ },
393
+
394
+ /**
395
+ * @this {NodeTransformThis}
396
+ * @description Creates a nested node structure from the given array of nodes.
397
+ * @param {__se__NodeCollection} nodeArray An array of nodes to clone. The first node in the array will be the top-level parent.
398
+ * @param {?(current: Node) => boolean=} validate A validate function.
399
+ * @returns {{ parent: Node, inner: Node }} An object containing the top-level parent node and the innermost child node.
400
+ */
401
+ createNestedNode(nodeArray, validate) {
402
+ if (typeof validate !== 'function') validate = () => true;
403
+
404
+ const el = /** @type {HTMLElement} */ (nodeArray[0].cloneNode(false));
405
+ let n = el;
406
+ for (let i = 1, len = nodeArray.length, t; i < len; i++) {
407
+ if (!validate(nodeArray[i])) continue;
408
+ t = /** @type {HTMLElement} */ (nodeArray[i].cloneNode(false));
409
+ n.appendChild(t);
410
+ n = t;
411
+ }
412
+
413
+ n.innerHTML = '';
414
+
415
+ return {
416
+ parent: el,
417
+ inner: n
418
+ };
419
+ },
420
+
421
+ constructor: NodeTransform
422
+ };
423
+
424
+ export default NodeTransform;