suneditor 3.0.0-alpha.9 → 3.0.0-beta.2

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 (315) hide show
  1. package/CONTRIBUTING.md +170 -22
  2. package/{LICENSE.txt → LICENSE} +9 -9
  3. package/README.md +168 -30
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +47 -21
  7. package/src/assets/design/color.css +121 -0
  8. package/src/assets/design/index.css +3 -0
  9. package/src/assets/design/size.css +35 -0
  10. package/src/assets/design/typography.css +37 -0
  11. package/src/assets/icons/defaultIcons.js +232 -0
  12. package/src/assets/suneditor-contents.css +181 -46
  13. package/src/assets/suneditor.css +1403 -650
  14. package/src/core/base/eventHandlers/handler_toolbar.js +35 -14
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +23 -4
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +49 -10
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +422 -224
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +83 -36
  19. package/src/core/base/eventManager.js +520 -179
  20. package/src/core/base/history.js +95 -41
  21. package/src/core/class/char.js +26 -11
  22. package/src/core/class/component.js +345 -137
  23. package/src/core/class/format.js +683 -519
  24. package/src/core/class/html.js +485 -305
  25. package/src/core/class/menu.js +133 -47
  26. package/src/core/class/nodeTransform.js +90 -71
  27. package/src/core/class/offset.js +408 -92
  28. package/src/core/class/selection.js +216 -106
  29. package/src/core/class/shortcuts.js +68 -8
  30. package/src/core/class/toolbar.js +106 -116
  31. package/src/core/class/ui.js +422 -0
  32. package/src/core/class/viewer.js +178 -74
  33. package/src/core/editor.js +496 -389
  34. package/src/core/section/actives.js +123 -27
  35. package/src/core/section/constructor.js +615 -206
  36. package/src/core/section/context.js +28 -23
  37. package/src/core/section/documentType.js +561 -0
  38. package/src/editorInjector/_classes.js +19 -5
  39. package/src/editorInjector/_core.js +71 -7
  40. package/src/editorInjector/index.js +63 -1
  41. package/src/events.js +622 -0
  42. package/src/helper/clipboard.js +59 -0
  43. package/src/helper/converter.js +202 -26
  44. package/src/helper/dom/domCheck.js +304 -0
  45. package/src/helper/dom/domQuery.js +669 -0
  46. package/src/helper/dom/domUtils.js +557 -0
  47. package/src/helper/dom/index.js +12 -0
  48. package/src/helper/env.js +46 -56
  49. package/src/helper/index.js +10 -4
  50. package/src/helper/keyCodeMap.js +183 -0
  51. package/src/helper/numbers.js +12 -8
  52. package/src/helper/unicode.js +9 -5
  53. package/src/langs/ckb.js +74 -4
  54. package/src/langs/cs.js +72 -2
  55. package/src/langs/da.js +73 -3
  56. package/src/langs/de.js +73 -4
  57. package/src/langs/en.js +23 -3
  58. package/src/langs/es.js +73 -4
  59. package/src/langs/fa.js +75 -3
  60. package/src/langs/fr.js +73 -3
  61. package/src/langs/he.js +73 -4
  62. package/src/langs/hu.js +230 -0
  63. package/src/langs/index.js +7 -3
  64. package/src/langs/it.js +70 -1
  65. package/src/langs/ja.js +72 -4
  66. package/src/langs/km.js +230 -0
  67. package/src/langs/ko.js +22 -2
  68. package/src/langs/lv.js +74 -5
  69. package/src/langs/nl.js +73 -4
  70. package/src/langs/pl.js +73 -4
  71. package/src/langs/pt_br.js +70 -1
  72. package/src/langs/ro.js +74 -5
  73. package/src/langs/ru.js +73 -4
  74. package/src/langs/se.js +73 -4
  75. package/src/langs/tr.js +73 -1
  76. package/src/langs/{ua.js → uk.js} +75 -6
  77. package/src/langs/ur.js +77 -8
  78. package/src/langs/zh_cn.js +74 -5
  79. package/src/modules/ApiManager.js +77 -54
  80. package/src/modules/Browser.js +667 -0
  81. package/src/modules/ColorPicker.js +162 -102
  82. package/src/modules/Controller.js +273 -142
  83. package/src/modules/Figure.js +925 -484
  84. package/src/modules/FileManager.js +121 -69
  85. package/src/modules/HueSlider.js +113 -61
  86. package/src/modules/Modal.js +291 -122
  87. package/src/modules/ModalAnchorEditor.js +383 -234
  88. package/src/modules/SelectMenu.js +270 -168
  89. package/src/modules/_DragHandle.js +2 -1
  90. package/src/modules/index.js +3 -3
  91. package/src/plugins/browser/audioGallery.js +83 -0
  92. package/src/plugins/browser/fileBrowser.js +103 -0
  93. package/src/plugins/browser/fileGallery.js +83 -0
  94. package/src/plugins/browser/imageGallery.js +81 -0
  95. package/src/plugins/browser/videoGallery.js +103 -0
  96. package/src/plugins/command/blockquote.js +40 -27
  97. package/src/plugins/command/exportPDF.js +134 -0
  98. package/src/plugins/command/fileUpload.js +229 -162
  99. package/src/plugins/command/list_bulleted.js +83 -47
  100. package/src/plugins/command/list_numbered.js +83 -47
  101. package/src/plugins/dropdown/align.js +66 -54
  102. package/src/plugins/dropdown/backgroundColor.js +63 -49
  103. package/src/plugins/dropdown/font.js +71 -47
  104. package/src/plugins/dropdown/fontColor.js +63 -48
  105. package/src/plugins/dropdown/formatBlock.js +70 -33
  106. package/src/plugins/dropdown/hr.js +92 -51
  107. package/src/plugins/dropdown/layout.js +37 -26
  108. package/src/plugins/dropdown/lineHeight.js +54 -38
  109. package/src/plugins/dropdown/list.js +60 -45
  110. package/src/plugins/dropdown/paragraphStyle.js +51 -30
  111. package/src/plugins/dropdown/table.js +2003 -813
  112. package/src/plugins/dropdown/template.js +38 -26
  113. package/src/plugins/dropdown/textStyle.js +43 -31
  114. package/src/plugins/field/mention.js +147 -86
  115. package/src/plugins/index.js +32 -6
  116. package/src/plugins/input/fontSize.js +161 -108
  117. package/src/plugins/input/pageNavigator.js +70 -0
  118. package/src/plugins/modal/audio.js +358 -173
  119. package/src/plugins/modal/drawing.js +531 -0
  120. package/src/plugins/modal/embed.js +886 -0
  121. package/src/plugins/modal/image.js +674 -362
  122. package/src/plugins/modal/link.js +100 -71
  123. package/src/plugins/modal/math.js +367 -167
  124. package/src/plugins/modal/video.js +691 -335
  125. package/src/plugins/popup/anchor.js +222 -0
  126. package/src/suneditor.js +50 -13
  127. package/src/themes/dark.css +122 -0
  128. package/src/typedef.js +130 -0
  129. package/types/assets/icons/defaultIcons.d.ts +153 -0
  130. package/types/core/base/eventHandlers/handler_toolbar.d.ts +41 -0
  131. package/types/core/base/eventHandlers/handler_ww_clipboard.d.ts +40 -0
  132. package/types/core/base/eventHandlers/handler_ww_dragDrop.d.ts +35 -0
  133. package/types/core/base/eventHandlers/handler_ww_key_input.d.ts +45 -0
  134. package/types/core/base/eventHandlers/handler_ww_mouse.d.ts +39 -0
  135. package/types/core/base/eventManager.d.ts +385 -0
  136. package/types/core/base/history.d.ts +81 -0
  137. package/types/core/class/char.d.ts +60 -0
  138. package/types/core/class/component.d.ts +212 -0
  139. package/types/core/class/format.d.ts +616 -0
  140. package/types/core/class/html.d.ts +422 -0
  141. package/types/core/class/menu.d.ts +126 -0
  142. package/types/core/class/nodeTransform.d.ts +93 -0
  143. package/types/core/class/offset.d.ts +522 -0
  144. package/types/core/class/selection.d.ts +188 -0
  145. package/types/core/class/shortcuts.d.ts +142 -0
  146. package/types/core/class/toolbar.d.ts +189 -0
  147. package/types/core/class/ui.d.ts +164 -0
  148. package/types/core/class/viewer.d.ts +140 -0
  149. package/types/core/editor.d.ts +610 -0
  150. package/types/core/section/actives.d.ts +46 -0
  151. package/types/core/section/constructor.d.ts +777 -0
  152. package/types/core/section/context.d.ts +45 -0
  153. package/types/core/section/documentType.d.ts +178 -0
  154. package/types/editorInjector/_classes.d.ts +41 -0
  155. package/types/editorInjector/_core.d.ts +92 -0
  156. package/types/editorInjector/index.d.ts +71 -0
  157. package/types/events.d.ts +273 -0
  158. package/types/helper/clipboard.d.ts +12 -0
  159. package/types/helper/converter.d.ts +197 -0
  160. package/types/helper/dom/domCheck.d.ts +189 -0
  161. package/types/helper/dom/domQuery.d.ts +223 -0
  162. package/types/helper/dom/domUtils.d.ts +226 -0
  163. package/types/helper/dom/index.d.ts +9 -0
  164. package/types/helper/env.d.ts +132 -0
  165. package/types/helper/index.d.ts +174 -0
  166. package/types/helper/keyCodeMap.d.ts +110 -0
  167. package/types/helper/numbers.d.ts +46 -0
  168. package/types/helper/unicode.d.ts +28 -0
  169. package/types/index.d.ts +120 -0
  170. package/{typings/Lang.d.ts → types/langs/_Lang.d.ts} +173 -103
  171. package/types/langs/ckb.d.ts +3 -0
  172. package/types/langs/cs.d.ts +3 -0
  173. package/types/langs/da.d.ts +3 -0
  174. package/types/langs/de.d.ts +3 -0
  175. package/types/langs/en.d.ts +3 -0
  176. package/types/langs/es.d.ts +3 -0
  177. package/types/langs/fa.d.ts +3 -0
  178. package/types/langs/fr.d.ts +3 -0
  179. package/types/langs/he.d.ts +3 -0
  180. package/types/langs/hu.d.ts +3 -0
  181. package/types/langs/index.d.ts +54 -0
  182. package/types/langs/it.d.ts +3 -0
  183. package/types/langs/ja.d.ts +3 -0
  184. package/types/langs/km.d.ts +3 -0
  185. package/types/langs/ko.d.ts +3 -0
  186. package/types/langs/lv.d.ts +3 -0
  187. package/types/langs/nl.d.ts +3 -0
  188. package/types/langs/pl.d.ts +3 -0
  189. package/types/langs/pt_br.d.ts +3 -0
  190. package/types/langs/ro.d.ts +3 -0
  191. package/types/langs/ru.d.ts +3 -0
  192. package/types/langs/se.d.ts +3 -0
  193. package/types/langs/tr.d.ts +3 -0
  194. package/types/langs/uk.d.ts +3 -0
  195. package/types/langs/ur.d.ts +3 -0
  196. package/types/langs/zh_cn.d.ts +3 -0
  197. package/types/modules/ApiManager.d.ts +125 -0
  198. package/types/modules/Browser.d.ts +326 -0
  199. package/types/modules/ColorPicker.d.ts +131 -0
  200. package/types/modules/Controller.d.ts +251 -0
  201. package/types/modules/Figure.d.ts +517 -0
  202. package/types/modules/FileManager.d.ts +202 -0
  203. package/types/modules/HueSlider.d.ts +136 -0
  204. package/types/modules/Modal.d.ts +111 -0
  205. package/types/modules/ModalAnchorEditor.d.ts +236 -0
  206. package/types/modules/SelectMenu.d.ts +194 -0
  207. package/types/modules/_DragHandle.d.ts +7 -0
  208. package/types/modules/index.d.ts +26 -0
  209. package/types/plugins/browser/audioGallery.d.ts +55 -0
  210. package/types/plugins/browser/fileBrowser.d.ts +64 -0
  211. package/types/plugins/browser/fileGallery.d.ts +55 -0
  212. package/types/plugins/browser/imageGallery.d.ts +51 -0
  213. package/types/plugins/browser/videoGallery.d.ts +57 -0
  214. package/types/plugins/command/blockquote.d.ts +28 -0
  215. package/types/plugins/command/exportPDF.d.ts +46 -0
  216. package/types/plugins/command/fileUpload.d.ts +156 -0
  217. package/types/plugins/command/list_bulleted.d.ts +46 -0
  218. package/types/plugins/command/list_numbered.d.ts +46 -0
  219. package/types/plugins/dropdown/align.d.ts +60 -0
  220. package/types/plugins/dropdown/backgroundColor.d.ts +63 -0
  221. package/types/plugins/dropdown/font.d.ts +54 -0
  222. package/types/plugins/dropdown/fontColor.d.ts +63 -0
  223. package/types/plugins/dropdown/formatBlock.d.ts +54 -0
  224. package/types/plugins/dropdown/hr.d.ts +71 -0
  225. package/types/plugins/dropdown/layout.d.ts +40 -0
  226. package/types/plugins/dropdown/lineHeight.d.ts +50 -0
  227. package/types/plugins/dropdown/list.d.ts +39 -0
  228. package/types/plugins/dropdown/paragraphStyle.d.ts +54 -0
  229. package/types/plugins/dropdown/table.d.ts +627 -0
  230. package/types/plugins/dropdown/template.d.ts +40 -0
  231. package/types/plugins/dropdown/textStyle.d.ts +41 -0
  232. package/types/plugins/field/mention.d.ts +102 -0
  233. package/types/plugins/index.d.ts +107 -0
  234. package/types/plugins/input/fontSize.d.ts +170 -0
  235. package/types/plugins/input/pageNavigator.d.ts +28 -0
  236. package/types/plugins/modal/audio.d.ts +269 -0
  237. package/types/plugins/modal/drawing.d.ts +246 -0
  238. package/types/plugins/modal/embed.d.ts +387 -0
  239. package/types/plugins/modal/image.d.ts +451 -0
  240. package/types/plugins/modal/link.d.ts +128 -0
  241. package/types/plugins/modal/math.d.ts +193 -0
  242. package/types/plugins/modal/video.d.ts +485 -0
  243. package/types/plugins/popup/anchor.d.ts +56 -0
  244. package/types/suneditor.d.ts +51 -0
  245. package/types/typedef.d.ts +233 -0
  246. package/.eslintignore +0 -7
  247. package/.eslintrc.json +0 -64
  248. package/src/assets/icons/_default.js +0 -194
  249. package/src/core/base/events.js +0 -320
  250. package/src/core/class/notice.js +0 -42
  251. package/src/helper/domUtils.js +0 -1177
  252. package/src/modules/FileBrowser.js +0 -271
  253. package/src/plugins/command/exportPdf.js +0 -168
  254. package/src/plugins/fileBrowser/imageGallery.js +0 -81
  255. package/src/themes/test.css +0 -61
  256. package/typings/CommandPlugin.d.ts +0 -8
  257. package/typings/DialogPlugin.d.ts +0 -20
  258. package/typings/FileBrowserPlugin.d.ts +0 -30
  259. package/typings/Module.d.ts +0 -15
  260. package/typings/Plugin.d.ts +0 -42
  261. package/typings/SubmenuPlugin.d.ts +0 -8
  262. package/typings/_classes.d.ts +0 -17
  263. package/typings/_colorPicker.d.ts +0 -60
  264. package/typings/_core.d.ts +0 -55
  265. package/typings/align.d.ts +0 -5
  266. package/typings/audio.d.ts +0 -5
  267. package/typings/backgroundColor.d.ts +0 -5
  268. package/typings/blockquote.d.ts +0 -5
  269. package/typings/char.d.ts +0 -39
  270. package/typings/component.d.ts +0 -38
  271. package/typings/context.d.ts +0 -39
  272. package/typings/converter.d.ts +0 -33
  273. package/typings/dialog.d.ts +0 -28
  274. package/typings/domUtils.d.ts +0 -361
  275. package/typings/editor.d.ts +0 -7
  276. package/typings/editor.ts +0 -542
  277. package/typings/env.d.ts +0 -70
  278. package/typings/eventManager.d.ts +0 -37
  279. package/typings/events.d.ts +0 -262
  280. package/typings/fileBrowser.d.ts +0 -42
  281. package/typings/fileManager.d.ts +0 -67
  282. package/typings/font.d.ts +0 -5
  283. package/typings/fontColor.d.ts +0 -5
  284. package/typings/fontSize.d.ts +0 -5
  285. package/typings/format.d.ts +0 -191
  286. package/typings/formatBlock.d.ts +0 -5
  287. package/typings/history.d.ts +0 -48
  288. package/typings/horizontalRule.d.ts +0 -5
  289. package/typings/image.d.ts +0 -5
  290. package/typings/imageGallery.d.ts +0 -5
  291. package/typings/index.d.ts +0 -21
  292. package/typings/index.modules.d.ts +0 -11
  293. package/typings/index.plugins.d.ts +0 -58
  294. package/typings/lineHeight.d.ts +0 -5
  295. package/typings/link.d.ts +0 -5
  296. package/typings/list.d.ts +0 -5
  297. package/typings/math.d.ts +0 -5
  298. package/typings/mediaContainer.d.ts +0 -25
  299. package/typings/mention.d.ts +0 -5
  300. package/typings/node.d.ts +0 -57
  301. package/typings/notice.d.ts +0 -16
  302. package/typings/numbers.d.ts +0 -29
  303. package/typings/offset.d.ts +0 -24
  304. package/typings/options.d.ts +0 -589
  305. package/typings/paragraphStyle.d.ts +0 -5
  306. package/typings/resizing.d.ts +0 -141
  307. package/typings/selection.d.ts +0 -94
  308. package/typings/shortcuts.d.ts +0 -13
  309. package/typings/suneditor.d.ts +0 -9
  310. package/typings/table.d.ts +0 -5
  311. package/typings/template.d.ts +0 -5
  312. package/typings/textStyle.d.ts +0 -5
  313. package/typings/toolbar.d.ts +0 -32
  314. package/typings/unicode.d.ts +0 -25
  315. package/typings/video.d.ts +0 -5
@@ -0,0 +1,669 @@
1
+ /**
2
+ * @fileoverview Implements Helper for querying the DOM.
3
+ */
4
+
5
+ import { zeroWidthRegExp } from '../unicode';
6
+ import domUtils from './domUtils';
7
+ import domCheck from './domCheck';
8
+
9
+ /**
10
+ * @description Returns the index compared to other sibling nodes.
11
+ * @param {Node} node The Node to find index
12
+ * @returns {number}
13
+ */
14
+ export function getPositionIndex(node) {
15
+ let idx = 0;
16
+ while ((node = node.previousSibling)) {
17
+ idx += 1;
18
+ }
19
+ return idx;
20
+ }
21
+
22
+ /**
23
+ * @description Returns the position of the "node" in the "parentNode" in a numerical array.
24
+ * - e.g.) <p><span>aa</span><span>bb</span></p> : getNodePath(node: "bb", parentNode: "<P>") -> [1, 0]
25
+ * @param {Node} node The Node to find position path
26
+ * @param {?Node} parentNode Parent node. If null, wysiwyg div area
27
+ * @param {?{s: number, e: number}=} _newOffsets If you send an object of the form "{s: 0, e: 0}", the text nodes that are attached together are merged into one, centered on the "node" argument.
28
+ * "_newOffsets.s" stores the length of the combined characters after "node" and "_newOffsets.e" stores the length of the combined characters before "node".
29
+ * Do not use unless absolutely necessary.
30
+ * @returns {Array<number>}
31
+ */
32
+ export function getNodePath(node, parentNode, _newOffsets) {
33
+ const path = [];
34
+ let finds = true;
35
+
36
+ getParentElement(node, (el) => {
37
+ if (el === parentNode) finds = false;
38
+ if (finds && !domCheck.isWysiwygFrame(el)) {
39
+ // merge text nodes
40
+ if (_newOffsets && el.nodeType === 3) {
41
+ let temp = null,
42
+ tempText = null;
43
+ _newOffsets.s = _newOffsets.e = 0;
44
+
45
+ let previous = el.previousSibling;
46
+ while (previous?.nodeType === 3) {
47
+ tempText = previous.textContent.replace(zeroWidthRegExp, '');
48
+ _newOffsets.s += tempText.length;
49
+ el.textContent = tempText + el.textContent;
50
+ temp = previous;
51
+ previous = previous.previousSibling;
52
+ domUtils.removeItem(temp);
53
+ }
54
+
55
+ let next = el.nextSibling;
56
+ while (next?.nodeType === 3) {
57
+ tempText = next.textContent.replace(zeroWidthRegExp, '');
58
+ _newOffsets.e += tempText.length;
59
+ el.textContent += tempText;
60
+ temp = next;
61
+ next = next.nextSibling;
62
+ domUtils.removeItem(temp);
63
+ }
64
+ }
65
+
66
+ // index push
67
+ path.push(el);
68
+ }
69
+ return false;
70
+ });
71
+
72
+ return path.map(getPositionIndex).reverse();
73
+ }
74
+
75
+ /**
76
+ * @template {Node} T
77
+ * @description Returns the node in the location of the path array obtained from "helper.dom.getNodePath".
78
+ * @param {Array<number>} offsets Position array, array obtained from "helper.dom.getNodePath"
79
+ * @param {Node} parentNode Base parent element
80
+ * @returns {T}
81
+ */
82
+ export function getNodeFromPath(offsets, parentNode) {
83
+ let current = parentNode;
84
+ let nodes;
85
+
86
+ for (let i = 0, len = offsets.length; i < len; i++) {
87
+ nodes = current.childNodes;
88
+ if (nodes.length === 0) break;
89
+ if (nodes.length <= offsets[i]) {
90
+ current = nodes[nodes.length - 1];
91
+ } else {
92
+ current = nodes[offsets[i]];
93
+ }
94
+ }
95
+
96
+ return /** @type {T} */ (current);
97
+ }
98
+
99
+ /**
100
+ * @template {HTMLElement} T
101
+ * @description Get all "children" of the argument value element (Without text nodes)
102
+ * @param {Node} element element to get child node
103
+ * @param {?(current: *) => boolean} validation Conditional function
104
+ * @returns {Array<T>}
105
+ */
106
+ export function getListChildren(element, validation) {
107
+ /** @type {Array<T>} */
108
+ const children = [];
109
+ if (!element) return children;
110
+
111
+ const el = /** @type {Element} */ (element);
112
+ if (!el.children || el.children.length === 0) return children;
113
+
114
+ validation =
115
+ validation ||
116
+ function () {
117
+ return true;
118
+ };
119
+
120
+ (function recursionFunc(current) {
121
+ if (el !== current && validation(current)) {
122
+ children.push(/** @type {T} */ (current));
123
+ }
124
+
125
+ if (current.children) {
126
+ for (let i = 0, len = current.children.length; i < len; i++) {
127
+ recursionFunc(current.children[i]);
128
+ }
129
+ }
130
+ })(el);
131
+
132
+ return /** @type {Array<T>} */ (children);
133
+ }
134
+
135
+ /**
136
+ * @template {Node} T
137
+ * @description Get all "childNodes" of the argument value element (Include text nodes)
138
+ * @param {Node} element element to get child node
139
+ * @param {?(current: *) => boolean} validation Conditional function
140
+ * @returns {Array<T>}
141
+ */
142
+ export function getListChildNodes(element, validation) {
143
+ const children = [];
144
+ if (!element || element.childNodes.length === 0) return children;
145
+
146
+ validation =
147
+ validation ||
148
+ function () {
149
+ return true;
150
+ };
151
+
152
+ (function recursionFunc(current) {
153
+ if (element !== current && validation(current)) {
154
+ children.push(current);
155
+ }
156
+
157
+ for (let i = 0, len = current.childNodes.length; i < len; i++) {
158
+ recursionFunc(current.childNodes[i]);
159
+ }
160
+ })(element);
161
+
162
+ return /** @type {Array<T>} */ (children);
163
+ }
164
+
165
+ /**
166
+ * @description Returns the number of parents nodes.
167
+ * - "0" when the parent node is the WYSIWYG area.
168
+ * - '-1' when the element argument is the WYSIWYG area.
169
+ * @param {Node} node The element to check
170
+ * @returns {number}
171
+ */
172
+ export function getNodeDepth(node) {
173
+ if (!node || domCheck.isWysiwygFrame(node)) return -1;
174
+
175
+ let depth = 0;
176
+ node = node.parentNode;
177
+
178
+ while (node && !domCheck.isWysiwygFrame(node)) {
179
+ depth += 1;
180
+ node = node.parentNode;
181
+ }
182
+
183
+ return depth;
184
+ }
185
+
186
+ /**
187
+ * @description Sort a node array by depth of element.
188
+ * @param {Array<Node>} array Node array
189
+ * @param {boolean} des true: descending order / false: ascending order
190
+ */
191
+ export function sortNodeByDepth(array, des) {
192
+ const t = !des ? -1 : 1;
193
+ const f = t * -1;
194
+
195
+ array.sort(function (a, b) {
196
+ if (!domCheck.isListCell(a) || !domCheck.isListCell(b)) return 0;
197
+ const a_i = getNodeDepth(a);
198
+ const b_i = getNodeDepth(b);
199
+ return a_i > b_i ? t : a_i < b_i ? f : 0;
200
+ });
201
+ }
202
+
203
+ /**
204
+ * @description Compares two elements to find a common ancestor, and returns the order of the two elements.
205
+ * @param {Node} a Node to compare.
206
+ * @param {Node} b Node to compare.
207
+ * @returns {{ancestor: HTMLElement|null, a: Node, b: Node, result: number}} { ancesstor, a, b, result: (a > b ? 1 : a < b ? -1 : 0) };
208
+ */
209
+ export function compareElements(a, b) {
210
+ let aNode = a,
211
+ bNode = b;
212
+ while (aNode && bNode && aNode.parentElement !== bNode.parentElement) {
213
+ aNode = aNode.parentElement;
214
+ bNode = bNode.parentElement;
215
+ }
216
+
217
+ if (!aNode || !bNode)
218
+ return {
219
+ ancestor: null,
220
+ a: a,
221
+ b: b,
222
+ result: 0
223
+ };
224
+
225
+ const children = aNode.parentNode.childNodes;
226
+ const aIndex = domUtils.getArrayIndex(children, aNode);
227
+ const bIndex = domUtils.getArrayIndex(children, bNode);
228
+
229
+ return {
230
+ ancestor: aNode.parentElement,
231
+ a: aNode,
232
+ b: bNode,
233
+ result: aIndex > bIndex ? 1 : aIndex < bIndex ? -1 : 0
234
+ };
235
+ }
236
+
237
+ /**
238
+ * @template {HTMLElement} T
239
+ * @description Get the parent element of the argument value.
240
+ * - A tag that satisfies the query condition is imported.
241
+ * @param {Node} element Reference element
242
+ * @param {string|((current: *) => boolean)|Node} query Query String (nodeName, .className, #ID, :name) or validation function.
243
+ * - Not use it like jquery.
244
+ * - Only one condition can be entered at a time.
245
+ * @param {?number=} depth Number of parent levels to depth.
246
+ * @returns {T|null} Not found: null
247
+ */
248
+ export function getParentElement(element, query, depth) {
249
+ let valid;
250
+
251
+ if (typeof query === 'function') {
252
+ valid = query;
253
+ } else if (typeof query === 'object') {
254
+ /** @param {Node} current */
255
+ valid = (current) => current === query;
256
+ } else {
257
+ let attr;
258
+ if (/^\./.test(query)) {
259
+ attr = 'className';
260
+ query = '(\\s|^)' + query.split('.')[1] + '(\\s|$)';
261
+ } else if (/^#/.test(query)) {
262
+ attr = 'id';
263
+ query = '^' + query.split('#')[1] + '$';
264
+ } else if (/^:/.test(query)) {
265
+ attr = 'name';
266
+ query = '^' + query.split(':')[1] + '$';
267
+ } else {
268
+ attr = 'nodeName';
269
+ query = '^' + query + '$';
270
+ }
271
+
272
+ const regExp = new RegExp(query, 'i');
273
+ /** @param {Node} el */
274
+ valid = (el) => regExp.test(el[attr]);
275
+ }
276
+
277
+ if (!depth) depth = Infinity;
278
+ let index = 0;
279
+ while (element && !valid(element)) {
280
+ if (index >= depth || domCheck.isWysiwygFrame(element)) {
281
+ return null;
282
+ }
283
+ element = element.parentElement;
284
+ index++;
285
+ }
286
+
287
+ return /** @type {T} */ (element);
288
+ }
289
+
290
+ /**
291
+ * @template {HTMLElement} T
292
+ * @description Gets all ancestors of the argument value.
293
+ * - Get all tags that satisfy the query condition.
294
+ * @param {Node} element Reference element
295
+ * @param {string|((current: *) => boolean)|Node} query Query String (nodeName, .className, #ID, :name) or validation function.
296
+ * Not use it like jquery.
297
+ * Only one condition can be entered at a time.
298
+ * @param {?number=} depth Number of parent levels to depth.
299
+ * @returns {Array<T>} Returned in an array in order.
300
+ */
301
+ export function getParentElements(element, query, depth) {
302
+ let valid;
303
+
304
+ if (typeof query === 'function') {
305
+ valid = query;
306
+ } else if (typeof query === 'object') {
307
+ /** @param {Node} current */
308
+ valid = (current) => current === query;
309
+ } else {
310
+ let attr;
311
+ if (/^\./.test(query)) {
312
+ attr = 'className';
313
+ query = '(\\s|^)' + query.split('.')[1] + '(\\s|$)';
314
+ } else if (/^#/.test(query)) {
315
+ attr = 'id';
316
+ query = '^' + query.split('#')[1] + '$';
317
+ } else if (/^:/.test(query)) {
318
+ attr = 'name';
319
+ query = '^' + query.split(':')[1] + '$';
320
+ } else {
321
+ attr = 'nodeName';
322
+ query = '^' + query + '$';
323
+ }
324
+
325
+ const regExp = new RegExp(query, 'i');
326
+ /** @param {Node} el */
327
+ valid = (el) => regExp.test(el[attr]);
328
+ }
329
+
330
+ const elementList = [];
331
+ if (!depth) depth = Infinity;
332
+ let index = 0;
333
+ while (index <= depth && element && !domCheck.isWysiwygFrame(element)) {
334
+ if (valid(element)) {
335
+ elementList.push(element);
336
+ }
337
+ element = element.parentElement;
338
+ index++;
339
+ }
340
+
341
+ return /** @type {Array<T>} */ (elementList);
342
+ }
343
+
344
+ /**
345
+ * @template {HTMLElement} T
346
+ * @description Gets the element with "data-command" attribute among the parent elements.
347
+ * @param {Node} target Target element
348
+ * @returns {T|null}
349
+ */
350
+ export function getCommandTarget(target) {
351
+ let n = /** @type {HTMLElement} */ (target);
352
+ while (n && !/^(UL)$/i.test(n.nodeName) && !domUtils.hasClass(n, 'sun-editor')) {
353
+ if (n.hasAttribute('data-command')) return /** @type {T} */ (n);
354
+ n = n.parentElement;
355
+ }
356
+
357
+ return null;
358
+ }
359
+
360
+ /**
361
+ * @template {HTMLElement} T
362
+ * @description Get the event.target element.
363
+ * @param {Event} event Event object
364
+ * @returns {T|null}
365
+ */
366
+ export function getEventTarget(event) {
367
+ return /** @type {T} */ (event.target);
368
+ }
369
+
370
+ /**
371
+ * @template {Node} T
372
+ * @description Get the child element of the argument value.
373
+ * - A tag that satisfies the query condition is imported.
374
+ * @param {Node} node Reference element
375
+ * @param {string|((current: *) => boolean)|Node} query Query String (nodeName, .className, #ID, :name) or validation function.
376
+ * @param {boolean} last If true returns the last node among the found child nodes. (default: first node)
377
+ * Not use it like jquery.
378
+ * Only one condition can be entered at a time.
379
+ * @returns {T|null} Not found: null
380
+ */
381
+ export function getEdgeChild(node, query, last) {
382
+ let valid;
383
+
384
+ if (typeof query === 'function') {
385
+ valid = query;
386
+ } else if (typeof query === 'object') {
387
+ valid = function (current) {
388
+ return current === query;
389
+ };
390
+ } else {
391
+ let attr;
392
+ if (/^\./.test(query)) {
393
+ attr = 'className';
394
+ query = '(\\s|^)' + query.split('.')[1] + '(\\s|$)';
395
+ } else if (/^#/.test(query)) {
396
+ attr = 'id';
397
+ query = '^' + query.split('#')[1] + '$';
398
+ } else if (/^:/.test(query)) {
399
+ attr = 'name';
400
+ query = '^' + query.split(':')[1] + '$';
401
+ } else {
402
+ attr = 'nodeName';
403
+ query = '^' + (query === 'text' ? '#' + query : query) + '$';
404
+ }
405
+
406
+ const regExp = new RegExp(query, 'i');
407
+ valid = function (el) {
408
+ return regExp.test(el[attr]);
409
+ };
410
+ }
411
+
412
+ const childList = getListChildNodes(node, (current) => valid(current));
413
+
414
+ return /** @type {T} */ (childList[last ? childList.length - 1 : 0]);
415
+ }
416
+
417
+ /**
418
+ * @description Get edge child nodes of the argument value.
419
+ * - 1. The first node of all the child nodes of the "first" element is returned.
420
+ * - 2. The last node of all the child nodes of the "last" element is returned.
421
+ * - 3. When there is no "last" element, the first and last nodes of all the children of the "first" element are returned.
422
+ * @param {Node} first First element
423
+ * @param {Node|null} last Last element
424
+ * @returns {{sc: Node, ec: Node}} { sc: "first", ec: "last" }
425
+ */
426
+ export function getEdgeChildNodes(first, last) {
427
+ if (!first) return;
428
+ if (!last) last = first;
429
+
430
+ while (first && first.nodeType === 1 && first.childNodes.length > 0 && !domCheck.isBreak(first)) first = first.firstChild;
431
+ while (last && last.nodeType === 1 && last.childNodes.length > 0 && !domCheck.isBreak(last)) last = last.lastChild;
432
+
433
+ return {
434
+ sc: first,
435
+ ec: last || first
436
+ };
437
+ }
438
+
439
+ /**
440
+ * @template {Node} T
441
+ * @description Gets the previous sibling last child. If there is no sibling, then it'll take it from the closest ancestor with child
442
+ * @param {Node} node Reference element
443
+ * @param {?Node=} ceiling Highest boundary allowed
444
+ * @returns {T|null} Not found: null
445
+ */
446
+ export function getPreviousDeepestNode(node, ceiling) {
447
+ let previousNode = node.previousSibling;
448
+ if (!previousNode) {
449
+ for (let parentNode = node.parentNode; parentNode; parentNode = parentNode.parentNode) {
450
+ if (parentNode === ceiling) return null;
451
+ if (parentNode.previousSibling) {
452
+ previousNode = parentNode.previousSibling;
453
+ break;
454
+ }
455
+ }
456
+ if (!previousNode) return null;
457
+ }
458
+
459
+ if (domCheck.isNonEditable(previousNode)) return /** @type {T} */ (/** @type {unknown} */ (previousNode));
460
+
461
+ while (previousNode.lastChild) previousNode = previousNode.lastChild;
462
+
463
+ return /** @type {T} */ (/** @type {unknown} */ (previousNode));
464
+ }
465
+
466
+ /**
467
+ * @template {Node} T
468
+ * @description Gets the next sibling first child. If there is no sibling, then it'll take it from the closest ancestor with child
469
+ * @param {Node} node Reference element
470
+ * @param {?Node=} ceiling Highest boundary allowed
471
+ * @returns {T|null} Not found: null
472
+ */
473
+ export function getNextDeepestNode(node, ceiling) {
474
+ let nextNode = node.nextSibling;
475
+ if (!nextNode) {
476
+ for (let parentNode = node.parentNode; parentNode; parentNode = parentNode.parentNode) {
477
+ if (parentNode === ceiling) return null;
478
+ if (parentNode.nextSibling) {
479
+ nextNode = parentNode.nextSibling;
480
+ break;
481
+ }
482
+ }
483
+ if (!nextNode) return null;
484
+ }
485
+
486
+ if (domCheck.isNonEditable(nextNode)) return /** @type {T} */ (/** @type {unknown} */ (nextNode));
487
+
488
+ while (nextNode.firstChild) nextNode = nextNode.firstChild;
489
+
490
+ return /** @type {T} */ (/** @type {unknown} */ (nextNode));
491
+ }
492
+
493
+ /**
494
+ * @description Find the index of the text node in the line element.
495
+ * @param {Node} line Line element (p, div, etc.)
496
+ * @param {Node} offsetContainer Base node to start searching
497
+ * @param {number} offset Base offset to start searching
498
+ * @param {?(current: *) => boolean=} validate Validation function
499
+ * @returns {number}
500
+ */
501
+ export function findTextIndexOnLine(line, offsetContainer, offset, validate) {
502
+ if (!line) return 0;
503
+ if (!validate) validate = () => true;
504
+
505
+ let index = 0;
506
+ let found = false;
507
+
508
+ (function recursionFunc(node) {
509
+ if (found || node.nodeType === 8) return;
510
+ if (validate(node)) return; // component.is
511
+
512
+ if (node.nodeType === 3) {
513
+ if (node === offsetContainer) {
514
+ index += offset;
515
+ found = true;
516
+ return;
517
+ }
518
+ index += node.textContent.length;
519
+ } else if (node.nodeType === 1) {
520
+ const childNodes = node.childNodes;
521
+ for (let i = 0, len = childNodes.length; i < len; i++) {
522
+ recursionFunc(childNodes[i]);
523
+ if (found) return;
524
+ }
525
+ }
526
+ })(line);
527
+
528
+ return index;
529
+ }
530
+
531
+ /**
532
+ * @description Find the end index of a sequence of at least minTabSize consecutive non-breaking spaces or spaces
533
+ * - which are interpreted as a tab key, occurring after a given base index in a text string.
534
+ * @param {Node} line Line element (p, div, etc.)
535
+ * @param {number} baseIndex Base index to start searching
536
+ * @param {number} minTabSize Minimum number of consecutive spaces to consider as a tab
537
+ * @returns {number} The adjusted index within the line element accounting for non-space characters
538
+ */
539
+ export function findTabEndIndex(line, baseIndex, minTabSize) {
540
+ if (!line) return 0;
541
+ const innerText = line.textContent;
542
+ const regex = new RegExp(`((\\u00A0|\\s){${minTabSize},})`, 'g');
543
+ let match;
544
+
545
+ regex.lastIndex = baseIndex;
546
+
547
+ while ((match = regex.exec(innerText)) !== null) {
548
+ if (match.index >= baseIndex) {
549
+ const spaceEndIndex = match.index + match[0].length - 1;
550
+ const precedingText = innerText.slice(0, spaceEndIndex + 1);
551
+ const nonSpaceCharCount = (precedingText.match(/[^\u00A0\s]/g) || []).length;
552
+ return spaceEndIndex + nonSpaceCharCount + minTabSize;
553
+ }
554
+ }
555
+
556
+ return 0;
557
+ }
558
+
559
+ /**
560
+ * @description Finds the table cell that appears visually at the bottom-right position,
561
+ * considering both rowSpan and colSpan, even if smaller cells are placed after large merged cells.
562
+ *
563
+ * @param {HTMLTableCellElement[]} cells
564
+ * @returns {HTMLTableCellElement|null}
565
+ */
566
+ export function findVisualLastCell(cells) {
567
+ if (!cells || cells.length === 0) return null;
568
+
569
+ /**
570
+ * @description visibility col index
571
+ * @type {Object<number, boolean[]>}
572
+ */
573
+ const occupied = {};
574
+
575
+ let target = null;
576
+ let maxRowEnd = -1;
577
+ let maxColEnd = -1;
578
+
579
+ for (const cell of cells) {
580
+ const row = /** @type {HTMLTableRowElement} */ (cell.parentElement);
581
+ const rowIndex = row.rowIndex;
582
+ const rowSpan = cell.rowSpan || 1;
583
+ const colSpan = cell.colSpan || 1;
584
+
585
+ // 현재 행에서 visual column index 찾기
586
+ if (!occupied[rowIndex]) occupied[rowIndex] = [];
587
+
588
+ let colIndex = 0;
589
+ const rowOcc = occupied[rowIndex];
590
+ while (rowOcc[colIndex]) colIndex++;
591
+
592
+ for (let i = 0; i < colSpan; i++) {
593
+ rowOcc[colIndex + i] = true;
594
+ }
595
+
596
+ for (let r = 1; r < rowSpan; r++) {
597
+ const nextRow = rowIndex + r;
598
+ if (!occupied[nextRow]) occupied[nextRow] = [];
599
+ for (let i = 0; i < colSpan; i++) {
600
+ occupied[nextRow][colIndex + i] = true;
601
+ }
602
+ }
603
+
604
+ const visualRowEnd = rowIndex + rowSpan - 1;
605
+ const visualColEnd = colIndex + colSpan - 1;
606
+
607
+ // right-bottom
608
+ if (visualRowEnd > maxRowEnd || (visualRowEnd === maxRowEnd && visualColEnd > maxColEnd)) {
609
+ maxRowEnd = visualRowEnd;
610
+ maxColEnd = visualColEnd;
611
+ target = cell;
612
+ }
613
+ }
614
+
615
+ return target;
616
+ }
617
+
618
+ /**
619
+ * @description Get nearest scrollable parent
620
+ * @param {Node} element Element
621
+ * @returns {HTMLElement|null}
622
+ */
623
+ export function getScrollParent(element) {
624
+ if (!element || /^(body|html)$/i.test(element.nodeName)) {
625
+ return null;
626
+ }
627
+
628
+ const el = /** @type {HTMLElement} */ (element);
629
+ if (el.scrollHeight > el.clientHeight) {
630
+ return el;
631
+ } else {
632
+ return getScrollParent(el.parentNode);
633
+ }
634
+ }
635
+
636
+ /**
637
+ * @description Get the argument iframe's document object if use the "iframe" or "fullPage" options
638
+ * @param {HTMLIFrameElement} iframe Iframe element (this.editor.frameContext.get('wysiwygFrame'))
639
+ * @returns {Document}
640
+ */
641
+ export function getIframeDocument(iframe) {
642
+ return iframe.contentWindow?.document || iframe.contentDocument;
643
+ }
644
+
645
+ const query = {
646
+ getPositionIndex,
647
+ getNodePath,
648
+ getNodeFromPath,
649
+ getListChildren,
650
+ getListChildNodes,
651
+ getNodeDepth,
652
+ sortNodeByDepth,
653
+ compareElements,
654
+ getParentElement,
655
+ getParentElements,
656
+ getCommandTarget,
657
+ getEventTarget,
658
+ getEdgeChild,
659
+ getEdgeChildNodes,
660
+ getPreviousDeepestNode,
661
+ getNextDeepestNode,
662
+ findTextIndexOnLine,
663
+ findTabEndIndex,
664
+ findVisualLastCell,
665
+ getScrollParent,
666
+ getIframeDocument
667
+ };
668
+
669
+ export default query;