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
@@ -3,31 +3,51 @@
3
3
  */
4
4
 
5
5
  import CoreInjector from '../../editorInjector/_core';
6
- import { domUtils, converter, numbers, unicode, env } from '../../helper';
6
+ import { dom, converter, numbers, unicode, clipboard } from '../../helper';
7
7
 
8
8
  const REQUIRED_DATA_ATTRS = 'data-se-[^\\s]+';
9
9
  const V2_MIG_DATA_ATTRS = '|data-index|data-file-size|data-file-name|data-exp|data-font-size';
10
10
 
11
- const HTML = function (editor) {
11
+ /**
12
+ * @typedef {Omit<HTML & Partial<__se__EditorInjector>, 'html'>} HTMLThis
13
+ */
14
+
15
+ /**
16
+ * @constructor
17
+ * @this {HTMLThis}
18
+ * @description All HTML related classes involved in the editing area
19
+ * @param {__se__EditorCore} editor - The root editor instance
20
+ */
21
+ function HTML(editor) {
12
22
  CoreInjector.call(this, editor);
23
+ const options = this.options;
13
24
 
14
25
  // members
15
- const options = this.options;
26
+ this.fontSizeUnitRegExp = null;
27
+
16
28
  this._isAllowedClassName = function (v) {
17
29
  return this.test(v) ? v : '';
18
- }.bind(this.options.get('allowedClassName'));
30
+ }.bind(options.get('allowedClassName'));
19
31
  this._allowHTMLComment = null;
20
32
  this._disallowedStyleNodesRegExp = null;
21
33
  this._htmlCheckWhitelistRegExp = null;
22
34
  this._htmlCheckBlacklistRegExp = null;
23
35
  this._elementWhitelistRegExp = null;
24
36
  this._elementBlacklistRegExp = null;
37
+ /** @type {Object<string, RegExp>} */
25
38
  this._attributeWhitelist = null;
26
- this._attributeWhitelistRegExp = null;
39
+ /** @type {Object<string, RegExp>} */
27
40
  this._attributeBlacklist = null;
41
+ this._attributeWhitelistRegExp = null;
28
42
  this._attributeBlacklistRegExp = null;
43
+ this._cleanStyleTagKeyRegExp = null;
44
+ this._cleanStyleRegExpMap = null;
29
45
  this._textStyleTags = options.get('_textStyleTags');
46
+ /** @type {Object<string, *>} */
30
47
  this._autoStyleify = null;
48
+ this.__disallowedTagsRegExp = null;
49
+ this.__disallowedTagNameRegExp = null;
50
+ this.__allowedTagNameRegExp = null;
31
51
 
32
52
  // clean styles
33
53
  const tagStyles = options.get('tagStyles');
@@ -61,7 +81,7 @@ const HTML = function (editor) {
61
81
  this._cleanStyleRegExpMap = stylesMap;
62
82
 
63
83
  // font size unit
64
- this.fontSizeUnitRegExp = new RegExp('\\d+(' + this.options.get('fontSizeUnits').join('|') + ')$', 'i');
84
+ this.fontSizeUnitRegExp = new RegExp('\\d+(' + options.get('fontSizeUnits').join('|') + ')$', 'i');
65
85
 
66
86
  // extra tags
67
87
  const allowedExtraTags = options.get('_allowedExtraTag');
@@ -96,6 +116,8 @@ const HTML = function (editor) {
96
116
  // attributes
97
117
  const regEndStr = '\\s*=\\s*(")[^"]*\\1';
98
118
  const _wAttr = options.get('attributeWhitelist');
119
+
120
+ /** @type {Object<string, RegExp>} */
99
121
  let tagsAttr = {};
100
122
  let allAttr = '';
101
123
  if (_wAttr) {
@@ -109,7 +131,7 @@ const HTML = function (editor) {
109
131
  }
110
132
  }
111
133
 
112
- this._attributeWhitelistRegExp = new RegExp('\\s(?:' + (allAttr || defaultAttr) + '|' + REQUIRED_DATA_ATTRS + (this.options.get('v2Migration') ? V2_MIG_DATA_ATTRS : '') + ')' + regEndStr, 'ig');
134
+ this._attributeWhitelistRegExp = new RegExp('\\s(?:' + (allAttr || defaultAttr) + '|' + REQUIRED_DATA_ATTRS + (options.get('v2Migration') ? V2_MIG_DATA_ATTRS : '') + ')' + regEndStr, 'ig');
113
135
  this._attributeWhitelist = tagsAttr;
114
136
 
115
137
  // blacklist
@@ -130,10 +152,10 @@ const HTML = function (editor) {
130
152
  this._attributeBlacklist = tagsAttr;
131
153
 
132
154
  // autoStyleify
133
- if (this.options.get('autoStyleify').length > 0) {
134
- const convertTextTags = this.options.get('convertTextTags');
155
+ if (options.get('autoStyleify').length > 0) {
156
+ const convertTextTags = options.get('convertTextTags');
135
157
  const styleToTag = {};
136
- this.options.get('autoStyleify').forEach((style) => {
158
+ options.get('autoStyleify').forEach((style) => {
137
159
  switch (style) {
138
160
  case 'bold':
139
161
  styleToTag.bold = { regex: /font-weight\s*:\s*bold/i, tag: convertTextTags.bold };
@@ -151,18 +173,24 @@ const HTML = function (editor) {
151
173
  });
152
174
  this._autoStyleify = styleToTag;
153
175
  }
154
- };
176
+ }
155
177
 
156
178
  HTML.prototype = {
157
179
  /**
158
- * @description Filter HTML by whitelist, blacklist, and validate.
159
- * @param {string} html HTML string to be filtered.
160
- * @param {object} params Filtering parameters.
161
- * @param {string} params.tagWhitelist Whitelist of allowed tags, specified as a string with tags separated by '|'. ex) "div|p|span".
162
- * @param {string} params.tagBlacklist Blacklist of disallowed tags, specified as a string with tags separated by '|'. ex) "script|iframe".
163
- * @param {function} params.validate Function to validate or replace individual elements based on custom conditions. Should return a new node for replacement, a string for outerHTML replacement, or null to remove the node.
164
- * @param {function} params.validateAll Function to validate or replace all elements based on custom conditions. Should return a new node for replacement, a string for outerHTML replacement, or null to remove the node.
165
- * @returns {string} Filtered HTML string.
180
+ * @this {HTMLThis}
181
+ * @description Filters an HTML string based on allowed and disallowed tags, with optional custom validation.
182
+ * - Removes blacklisted tags and keeps only whitelisted tags.
183
+ * - Allows custom validation functions to replace, modify, or remove elements.
184
+ * @param {string} html - The HTML string to be filtered.
185
+ * @param {Object} params - Filtering parameters.
186
+ * @param {string} [params.tagWhitelist] - Allowed tags, specified as a string with tags separated by '|'. (e.g. "div|p|span").
187
+ * @param {string} [params.tagBlacklist] - Disallowed tags, specified as a string with tags separated by '|'. (e.g. "script|iframe").
188
+ * @param {(node: Node) => Node | string | null} [params.validate] - Function to validate and modify individual nodes.
189
+ * - Return `null` to remove the node.
190
+ * - Return a `Node` to replace the current node.
191
+ * - Return a `string` to replace the node's outerHTML.
192
+ * @param {boolean} [params.validateAll] - Whether to apply validation to all nodes.
193
+ * @returns {string} - The filtered HTML string.
166
194
  */
167
195
  filter(html, { tagWhitelist, tagBlacklist, validate, validateAll }) {
168
196
  if (tagWhitelist) {
@@ -189,9 +217,7 @@ HTML.prototype = {
189
217
  } else if (validateAll) {
190
218
  const parseDocument = new DOMParser().parseFromString(html, 'text/html');
191
219
  const compClass = ['.se-component', '.se-flex-component'];
192
- const closestAny = function (element) {
193
- return compClass.some((selector) => element.closest(selector));
194
- };
220
+ const closestAny = (element) => compClass.some((selector) => element.closest(selector));
195
221
  parseDocument.body.querySelectorAll('*').forEach((node) => {
196
222
  if (!closestAny(node)) {
197
223
  const result = validate(node);
@@ -211,16 +237,19 @@ HTML.prototype = {
211
237
  },
212
238
 
213
239
  /**
214
- * @description Clean and compress the HTML code to suit the editor format.
215
- * @param {string} html HTML string
216
- * @param {boolean} requireFormat If true, text nodes that do not have a format node is wrapped with the format tag.
217
- * @param {string|RegExp|null} whitelist Regular expression of allowed tags.
218
- * RegExp object is create by helper.converter.createElementWhitelist method.
219
- * @param {string|RegExp|null} blacklist Regular expression of disallowed tags.
220
- * RegExp object is create by helper.converter.createElementBlacklist method.
221
- * @returns {string}
240
+ * @this {HTMLThis}
241
+ * @description Cleans and compresses HTML code to suit the editor format.
242
+ * @param {string} html HTML string to clean and compress
243
+ * @param {Object} [options] Cleaning options
244
+ * @param {boolean} [options.forceFormat=false] If true, wraps text nodes without a format node in the format tag.
245
+ * @param {string|RegExp|null} [options.whitelist=null] Regular expression of allowed tags.
246
+ * Create RegExp object using helper.converter.createElementWhitelist method.
247
+ * @param {string|RegExp|null} [options.blacklist=null] Regular expression of disallowed tags.
248
+ * Create RegExp object using helper.converter.createElementBlacklist method.
249
+ * @param {boolean} [options._freeCodeViewMode=false] If true, the free code view mode is enabled.
250
+ * @returns {string} Cleaned and compressed HTML string
222
251
  */
223
- clean(html, requireFormat, whitelist, blacklist) {
252
+ clean(html, { forceFormat, whitelist, blacklist, _freeCodeViewMode } = {}) {
224
253
  const { tagFilter, formatFilter, classFilter, styleNodeFilter, attrFilter, styleFilter } = this.options.get('strictMode');
225
254
  let cleanData = '';
226
255
 
@@ -232,9 +261,9 @@ HTML.prototype = {
232
261
  }
233
262
 
234
263
  if (this._autoStyleify) {
235
- const dom = new DOMParser().parseFromString(html, 'text/html');
236
- domUtils.getListChildNodes(dom.body, converter.spanToStyleNode.bind(null, this._autoStyleify));
237
- html = dom.body.innerHTML;
264
+ const domParser = new DOMParser().parseFromString(html, 'text/html');
265
+ dom.query.getListChildNodes(domParser.body, converter.spanToStyleNode.bind(null, this._autoStyleify));
266
+ html = domParser.body.innerHTML;
238
267
  }
239
268
 
240
269
  if (attrFilter || styleFilter) {
@@ -242,19 +271,33 @@ HTML.prototype = {
242
271
  }
243
272
 
244
273
  // get dom tree
245
- const dom = this._d.createRange().createContextualFragment(html, true);
274
+ const domParser = this._d.createRange().createContextualFragment(html);
246
275
 
247
276
  if (tagFilter) {
248
277
  try {
249
- this._consistencyCheckOfHTML(dom, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, tagFilter, formatFilter, classFilter);
278
+ this._consistencyCheckOfHTML(domParser, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, tagFilter, formatFilter, classFilter, _freeCodeViewMode);
250
279
  } catch (error) {
251
280
  console.warn('[SUNEDITOR.html.clean.fail]', error.message);
252
281
  }
253
282
  }
254
283
 
284
+ // iframe placeholder parsing
285
+ const iframePlaceholders = domParser.querySelectorAll('[data-se-iframe-holder]');
286
+ for (let i = 0, len = iframePlaceholders.length; i < len; i++) {
287
+ /** @type {HTMLIFrameElement} */
288
+ const iframe = dom.utils.createElement('iframe');
289
+
290
+ const attrs = JSON.parse(iframePlaceholders[i].getAttribute('data-se-iframe-holder-attrs'));
291
+ for (const [key, value] of Object.entries(attrs)) {
292
+ iframe.setAttribute(key, value);
293
+ }
294
+
295
+ iframePlaceholders[i].replaceWith(iframe);
296
+ }
297
+
255
298
  if (this.options.get('__pluginRetainFilter')) {
256
299
  this.editor._MELInfo.forEach((method, query) => {
257
- const infoLst = dom.querySelectorAll(query);
300
+ const infoLst = domParser.querySelectorAll(query);
258
301
  for (let i = 0, len = infoLst.length; i < len; i++) {
259
302
  method(infoLst[i]);
260
303
  }
@@ -262,17 +305,17 @@ HTML.prototype = {
262
305
  }
263
306
 
264
307
  if (formatFilter) {
265
- let domTree = dom.childNodes;
266
- if (!requireFormat) requireFormat = this._isFormatData(domTree);
267
- if (requireFormat) domTree = this._editFormat(dom).childNodes;
308
+ let domTree = domParser.childNodes;
309
+ if (!forceFormat) forceFormat = this._isFormatData(domTree);
310
+ if (forceFormat) domTree = this._editFormat(domParser).childNodes;
268
311
 
269
312
  for (let i = 0, len = domTree.length, t; i < len; i++) {
270
313
  t = domTree[i];
271
314
  if (this.__allowedTagNameRegExp.test(t.nodeName)) {
272
- cleanData += t.outerHTML;
315
+ cleanData += /** @type {HTMLElement} */ (t).outerHTML;
273
316
  continue;
274
317
  }
275
- cleanData += this._makeLine(t, requireFormat);
318
+ cleanData += this._makeLine(t, forceFormat);
276
319
  }
277
320
  }
278
321
 
@@ -293,29 +336,32 @@ HTML.prototype = {
293
336
  },
294
337
 
295
338
  /**
296
- * @description Insert an (HTML element / HTML string / plain string) at selection range.
297
- * If "frameOptions.get('charCounter_max')" is exceeded when "html" is added, null is returned without addition.
298
- * @param {Element|String} html HTML Element or HTML string or plain string
299
- * @param {boolean} rangeSelection If true, range select the inserted node.
300
- * @param {boolean} notCheckCharCount If true, it will be inserted even if "frameOptions.get('charCounter_max')" is exceeded.
301
- * @param {boolean} notCleanData If true, inserts the HTML string without refining it with html.clean.
339
+ * @this {HTMLThis}
340
+ * @description Inserts an (HTML element / HTML string / plain string) at the selection range.
341
+ * - If "frameOptions.get('charCounter_max')" is exceeded when "html" is added, null is returned without addition.
342
+ * @param {Node|string} html HTML Element or HTML string or plain string
343
+ * @param {Object} [options] Options
344
+ * @param {boolean} [options.selectInserted=false] If true, selects the range of the inserted node.
345
+ * @param {boolean} [options.skipCharCount=false] If true, inserts even if "frameOptions.get('charCounter_max')" is exceeded.
346
+ * @param {boolean} [options.skipCleaning=false] If true, inserts the HTML string without refining it with html.clean.
347
+ * @returns {HTMLElement|null} The inserted element or null if insertion failed
302
348
  */
303
- insert(html, rangeSelection, notCheckCharCount, notCleanData) {
304
- if (!this.editor.frameContext.get('wysiwygFrame').contains(this.selection.get().focusNode)) this.editor.focus();
349
+ insert(html, { selectInserted, skipCharCount, skipCleaning } = {}) {
350
+ if (!this.editor.frameContext.get('wysiwyg').contains(this.selection.get().focusNode)) this.editor.focus();
305
351
 
306
352
  if (typeof html === 'string') {
307
- if (!notCleanData) html = this.clean(html, false, null, null);
353
+ if (!skipCleaning) html = this.clean(html, { forceFormat: false, whitelist: null, blacklist: null });
308
354
  try {
309
- if (domUtils.isListCell(this.format.getLine(this.selection.getNode(), null))) {
310
- const dom = this._d.createRange().createContextualFragment(html);
311
- const domTree = dom.childNodes;
355
+ if (dom.check.isListCell(this.format.getLine(this.selection.getNode(), null))) {
356
+ const domParser = this._d.createRange().createContextualFragment(html);
357
+ const domTree = domParser.childNodes;
312
358
  if (this._isFormatData(domTree)) html = this._convertListCell(domTree);
313
359
  }
314
360
 
315
- const dom = this._d.createRange().createContextualFragment(html);
316
- const domTree = dom.childNodes;
361
+ const domParser = this._d.createRange().createContextualFragment(html);
362
+ const domTree = domParser.childNodes;
317
363
 
318
- if (!notCheckCharCount) {
364
+ if (!skipCharCount) {
319
365
  const type = this.editor.frameOptions.get('charCounter_type') === 'byte-html' ? 'outerHTML' : 'textContent';
320
366
  let checkHTML = '';
321
367
  for (let i = 0, len = domTree.length; i < len; i++) {
@@ -326,12 +372,12 @@ HTML.prototype = {
326
372
 
327
373
  let c, a, t, prev, firstCon;
328
374
  while ((c = domTree[0])) {
329
- if (prev?.nodeType === 3 && a?.nodeType === 1 && domUtils.isBreak(c)) {
375
+ if (prev?.nodeType === 3 && a?.nodeType === 1 && dom.check.isBreak(c)) {
330
376
  prev = c;
331
- domUtils.removeItem(c);
377
+ dom.utils.removeItem(c);
332
378
  continue;
333
379
  }
334
- t = this.insertNode(c, a, true);
380
+ t = this.insertNode(c, { afterNode: a, skipCharCount: true });
335
381
  a = t.container || t;
336
382
  if (!firstCon) firstCon = t;
337
383
  prev = c;
@@ -340,24 +386,24 @@ HTML.prototype = {
340
386
  if (prev?.nodeType === 3 && a?.nodeType === 1) a = prev;
341
387
  const offset = a.nodeType === 3 ? t.endOffset || a.textContent.length : a.childNodes.length;
342
388
 
343
- if (rangeSelection) {
389
+ if (selectInserted) {
344
390
  this.selection.setRange(firstCon.container || firstCon, firstCon.startOffset || 0, a, offset);
345
391
  } else if (!this.component.is(a)) {
346
392
  this.selection.setRange(a, offset, a, offset);
347
393
  }
348
394
  } catch (error) {
349
395
  if (this.editor.frameContext.get('isReadOnly') || this.editor.frameContext.get('isDisabled')) return;
350
- throw Error('[SUNEDITOR.html.insert.error]', error.message);
396
+ throw Error(`[SUNEDITOR.html.insert.error] ${error.message}`);
351
397
  }
352
398
  } else {
353
399
  if (this.component.is(html)) {
354
- this.component.insert(html, notCheckCharCount, false);
400
+ this.component.insert(html, { skipCharCount, skipSelection: false, skipHistory: false });
355
401
  } else {
356
402
  let afterNode = null;
357
- if (this.format.isLine(html) || domUtils.isMedia(html)) {
403
+ if (this.format.isLine(html) || dom.check.isMedia(html)) {
358
404
  afterNode = this.format.getLine(this.selection.getNode(), null);
359
405
  }
360
- this.insertNode(html, afterNode, notCheckCharCount);
406
+ this.insertNode(html, { afterNode, skipCharCount });
361
407
  }
362
408
  }
363
409
 
@@ -367,23 +413,26 @@ HTML.prototype = {
367
413
  },
368
414
 
369
415
  /**
416
+ * @this {HTMLThis}
370
417
  * @description Delete selected node and insert argument value node and return.
371
- * If the "afterNode" exists, it is inserted after the "afterNode"
372
- * Inserting a text node merges with both text nodes on both sides and returns a new "{ container, startOffset, endOffset }".
418
+ * - If the "afterNode" exists, it is inserted after the "afterNode"
419
+ * - Inserting a text node merges with both text nodes on both sides and returns a new "{ container, startOffset, endOffset }".
373
420
  * @param {Node} oNode Node to be inserted
374
- * @param {Node|null} afterNode If the node exists, it is inserted after the node
375
- * @param {boolean|null} notCheckCharCount If true, it will be inserted even if "frameOptions.get('charCounter_max')" is exceeded.
421
+ * @param {Object} [options] Options
422
+ * @param {Node} [options.afterNode=null] If the node exists, it is inserted after the node
423
+ * @param {boolean} [options.skipCharCount=null] If true, it will be inserted even if "frameOptions.get('charCounter_max')" is exceeded.
376
424
  * @returns {Object|Node|null}
377
425
  */
378
- insertNode(oNode, afterNode, notCheckCharCount) {
426
+ insertNode(oNode, { afterNode, skipCharCount } = {}) {
379
427
  let result = null;
380
- if (this.editor.frameContext.get('isReadOnly') || (!notCheckCharCount && !this.char.check(oNode, null))) {
428
+ if (this.editor.frameContext.get('isReadOnly') || (!skipCharCount && !this.char.check(oNode))) {
381
429
  return result;
382
430
  }
383
431
 
432
+ let fNode = null;
384
433
  let range = this.selection.getRange();
385
- let line = domUtils.isListCell(range.commonAncestorContainer) ? range.commonAncestorContainer : this.format.getLine(this.selection.getNode(), null);
386
- let insertListCell = domUtils.isListCell(line) && (domUtils.isListCell(oNode) || domUtils.isList(oNode));
434
+ let line = dom.check.isListCell(range.commonAncestorContainer) ? range.commonAncestorContainer : this.format.getLine(this.selection.getNode(), null);
435
+ let insertListCell = dom.check.isListCell(line) && (dom.check.isListCell(oNode) || dom.check.isList(oNode));
387
436
 
388
437
  let parentNode,
389
438
  originAfter,
@@ -393,12 +442,12 @@ HTML.prototype = {
393
442
  const isFormats = (!freeFormat && (this.format.isLine(oNode) || this.format.isBlock(oNode))) || this.component.isBasic(oNode);
394
443
 
395
444
  if (insertListCell) {
396
- tempAfterNode = afterNode || domUtils.isList(oNode) ? line.lastChild : line.nextElementSibling;
397
- tempParentNode = domUtils.isList(oNode) ? line : (tempAfterNode || line).parentNode;
445
+ tempAfterNode = afterNode || dom.check.isList(oNode) ? line.lastChild : line.nextElementSibling;
446
+ tempParentNode = dom.check.isList(oNode) ? line : (tempAfterNode || line).parentNode;
398
447
  }
399
448
 
400
- if (!afterNode && (isFormats || this.component.isBasic(oNode) || domUtils.isMedia(oNode))) {
401
- const isEdge = domUtils.isEdgePoint(range.endContainer, range.endOffset, 'end');
449
+ if (!afterNode && (isFormats || this.component.isBasic(oNode) || dom.check.isMedia(oNode))) {
450
+ const isEdge = dom.check.isEdgePoint(range.endContainer, range.endOffset, 'end');
402
451
  const r = this.remove();
403
452
  const container = r.container;
404
453
  const prevContainer = container === r.prevContainer && range.collapsed ? null : r.prevContainer;
@@ -416,30 +465,30 @@ HTML.prototype = {
416
465
  } else {
417
466
  tempAfterNode = null;
418
467
  }
419
- } else if (insertListCell && domUtils.isListCell(container) && !line.parentElement) {
420
- line = domUtils.createElement('LI');
468
+ } else if (insertListCell && dom.check.isListCell(container) && !line.parentElement) {
469
+ line = dom.utils.createElement('LI');
421
470
  tempParentNode.appendChild(line);
422
471
  container.appendChild(tempParentNode);
423
472
  tempAfterNode = null;
424
- } else if (container.nodeType === 3 || domUtils.isBreak(container) || insertListCell) {
425
- const depthFormat = domUtils.getParentElement(container, (current) => {
426
- return this.format.isBlock(current) || domUtils.isListCell(current);
473
+ } else if (container.nodeType === 3 || dom.check.isBreak(container) || insertListCell) {
474
+ const depthFormat = dom.query.getParentElement(container, (current) => {
475
+ return this.format.isBlock(current) || dom.check.isListCell(current);
427
476
  });
428
- afterNode = this.nodeTransform.split(container, r.offset, !depthFormat ? 0 : domUtils.getNodeDepth(depthFormat) + 1);
477
+ afterNode = this.nodeTransform.split(container, r.offset, !depthFormat ? 0 : dom.query.getNodeDepth(depthFormat) + 1);
429
478
  if (!afterNode) {
430
479
  tempAfterNode = afterNode = line;
431
480
  } else if (insertListCell) {
432
481
  if (line.contains(container)) {
433
- const subList = domUtils.isList(line.lastElementChild);
482
+ const subList = dom.check.isList(line.lastElementChild);
434
483
  let newCell = null;
435
484
  if (!isEdge) {
436
485
  newCell = line.cloneNode(false);
437
- newCell.appendChild(afterNode.textContent.trim() ? afterNode : domUtils.createTextNode(unicode.zeroWidthSpace));
486
+ newCell.appendChild(afterNode.textContent.trim() ? afterNode : dom.utils.createTextNode(unicode.zeroWidthSpace));
438
487
  }
439
488
  if (subList) {
440
489
  if (!newCell) {
441
490
  newCell = line.cloneNode(false);
442
- newCell.appendChild(domUtils.createTextNode(unicode.zeroWidthSpace));
491
+ newCell.appendChild(dom.utils.createTextNode(unicode.zeroWidthSpace));
443
492
  }
444
493
  newCell.appendChild(line.lastElementChild);
445
494
  }
@@ -472,18 +521,18 @@ HTML.prototype = {
472
521
  /** No Select range node */
473
522
  if (range.collapsed) {
474
523
  if (commonCon.nodeType === 3) {
475
- if (commonCon.textContent.length > endOff) afterNode = commonCon.splitText(endOff);
524
+ if (commonCon.textContent.length > endOff) afterNode = /** @type {Text} */ (commonCon).splitText(endOff);
476
525
  else afterNode = commonCon.nextSibling;
477
526
  } else {
478
- if (!domUtils.isBreak(parentNode)) {
527
+ if (!dom.check.isBreak(parentNode)) {
479
528
  const c = parentNode.childNodes[startOff];
480
- const focusNode = c?.nodeType === 3 && domUtils.isZeroWith(c) && domUtils.isBreak(c.nextSibling) ? c.nextSibling : c;
529
+ const focusNode = c?.nodeType === 3 && dom.check.isZeroWidth(c) && dom.check.isBreak(c.nextSibling) ? c.nextSibling : c;
481
530
  if (focusNode) {
482
- if (!focusNode.nextSibling && domUtils.isBreak(focusNode)) {
531
+ if (!focusNode.nextSibling && dom.check.isBreak(focusNode)) {
483
532
  parentNode.removeChild(focusNode);
484
533
  afterNode = null;
485
534
  } else {
486
- afterNode = domUtils.isBreak(focusNode) && !domUtils.isBreak(oNode) ? focusNode : focusNode.nextSibling;
535
+ afterNode = dom.check.isBreak(focusNode) && !dom.check.isBreak(oNode) ? focusNode : focusNode.nextSibling;
487
536
  }
488
537
  } else {
489
538
  afterNode = null;
@@ -498,15 +547,15 @@ HTML.prototype = {
498
547
  const isSameContainer = startCon === endCon;
499
548
 
500
549
  if (isSameContainer) {
501
- if (domUtils.isEdgePoint(endCon, endOff)) afterNode = endCon.nextSibling;
502
- else afterNode = endCon.splitText(endOff);
550
+ if (dom.check.isEdgePoint(endCon, endOff)) afterNode = endCon.nextSibling;
551
+ else afterNode = /** @type {Text} */ (endCon).splitText(endOff);
503
552
 
504
553
  let removeNode = startCon;
505
- if (!domUtils.isEdgePoint(startCon, startOff)) removeNode = startCon.splitText(startOff);
554
+ if (!dom.check.isEdgePoint(startCon, startOff)) removeNode = /** @type {Text} */ (startCon).splitText(startOff);
506
555
 
507
556
  parentNode.removeChild(removeNode);
508
557
  if (parentNode.childNodes.length === 0 && isFormats) {
509
- parentNode.innerHTML = '<br>';
558
+ /** @type {HTMLElement} */ (parentNode).innerHTML = '<br>';
510
559
  }
511
560
  } else {
512
561
  const removedTag = this.remove();
@@ -521,7 +570,7 @@ HTML.prototype = {
521
570
  }
522
571
  }
523
572
 
524
- if (domUtils.isListCell(container) && oNode.nodeType === 3) {
573
+ if (dom.check.isListCell(container) && oNode.nodeType === 3) {
525
574
  parentNode = container;
526
575
  afterNode = null;
527
576
  } else if (!isFormats && prevContainer) {
@@ -537,8 +586,8 @@ HTML.prototype = {
537
586
  } else {
538
587
  afterNode = null;
539
588
  }
540
- } else if (domUtils.isWysiwygFrame(container) && !this.format.isLine(oNode)) {
541
- parentNode = container.appendChild(domUtils.createElement(this.options.get('defaultLine')));
589
+ } else if (dom.check.isWysiwygFrame(container) && !this.format.isLine(oNode)) {
590
+ parentNode = container.appendChild(dom.utils.createElement(this.options.get('defaultLine')));
542
591
  afterNode = null;
543
592
  } else {
544
593
  afterNode = isFormats ? endCon : container === prevContainer ? container.nextSibling : container;
@@ -562,37 +611,38 @@ HTML.prototype = {
562
611
  // set node
563
612
  const wysiwyg = this.editor.frameContext.get('wysiwyg');
564
613
  if (!insertListCell) {
565
- if (domUtils.isWysiwygFrame(afterNode) || parentNode === wysiwyg.parentNode) {
614
+ if (dom.check.isWysiwygFrame(afterNode) || parentNode === wysiwyg.parentNode) {
566
615
  parentNode = wysiwyg;
567
616
  afterNode = null;
568
617
  }
569
618
 
570
- if (this.format.isLine(oNode) || this.format.isBlock(oNode) || (!domUtils.isListCell(parentNode) && this.component.isBasic(oNode))) {
619
+ if (this.format.isLine(oNode) || this.format.isBlock(oNode) || (!dom.check.isListCell(parentNode) && this.component.isBasic(oNode))) {
571
620
  const oldParent = parentNode;
572
- if (domUtils.isList(afterNode)) {
621
+ if (dom.check.isList(afterNode)) {
573
622
  parentNode = afterNode;
574
623
  afterNode = null;
575
- } else if (domUtils.isListCell(afterNode)) {
624
+ } else if (dom.check.isListCell(afterNode)) {
576
625
  parentNode = afterNode.previousElementSibling || afterNode;
577
626
  } else if (!originAfter && !afterNode) {
578
627
  const r = this.remove();
579
- const container = r.container.nodeType === 3 ? (domUtils.isListCell(this.format.getLine(r.container, null)) ? r.container : this.format.getLine(r.container, null) || r.container.parentNode) : r.container;
580
- const rangeCon = domUtils.isWysiwygFrame(container) || this.format.isBlock(container);
628
+ const container = r.container.nodeType === 3 ? (dom.check.isListCell(this.format.getLine(r.container, null)) ? r.container : this.format.getLine(r.container, null) || r.container.parentNode) : r.container;
629
+ const rangeCon = dom.check.isWysiwygFrame(container) || this.format.isBlock(container);
581
630
  parentNode = rangeCon ? container : container.parentNode;
582
631
  afterNode = rangeCon ? null : container.nextSibling;
583
632
  }
584
633
 
585
- if (oldParent.childNodes.length === 0 && parentNode !== oldParent) domUtils.removeItem(oldParent);
634
+ if (oldParent.childNodes.length === 0 && parentNode !== oldParent) dom.utils.removeItem(oldParent);
586
635
  }
587
636
 
588
- if (isFormats && !freeFormat && !this.format.isBlock(parentNode) && !domUtils.isListCell(parentNode) && !domUtils.isWysiwygFrame(parentNode)) {
637
+ if (isFormats && !freeFormat && !this.format.isBlock(parentNode) && !dom.check.isListCell(parentNode) && !dom.check.isWysiwygFrame(parentNode)) {
589
638
  afterNode = parentNode.nextElementSibling;
590
639
  parentNode = parentNode.parentNode;
591
640
  }
592
641
 
593
- if (domUtils.isWysiwygFrame(parentNode) && (oNode.nodeType === 3 || domUtils.isBreak(oNode))) {
594
- const fNode = domUtils.createElement(this.options.get('defaultLine'), null, oNode);
595
- oNode = fNode;
642
+ if (dom.check.isWysiwygFrame(parentNode) && (oNode.nodeType === 3 || dom.check.isBreak(oNode))) {
643
+ const formatNode = dom.utils.createElement(this.options.get('defaultLine'), null, oNode);
644
+ fNode = oNode;
645
+ oNode = formatNode;
596
646
  }
597
647
  }
598
648
 
@@ -609,12 +659,12 @@ HTML.prototype = {
609
659
  afterNode = parentNode === afterNode ? parentNode.lastChild : afterNode;
610
660
  }
611
661
 
612
- if (domUtils.isListCell(oNode) && !domUtils.isList(parentNode)) {
613
- if (domUtils.isListCell(parentNode)) {
662
+ if (dom.check.isListCell(oNode) && !dom.check.isList(parentNode)) {
663
+ if (dom.check.isListCell(parentNode)) {
614
664
  afterNode = parentNode.nextElementSibling;
615
665
  parentNode = parentNode.parentNode;
616
666
  } else {
617
- const ul = domUtils.createElement('ol');
667
+ const ul = dom.utils.createElement('ol');
618
668
  parentNode.insertBefore(ul, afterNode);
619
669
  parentNode = ul;
620
670
  afterNode = null;
@@ -626,11 +676,11 @@ HTML.prototype = {
626
676
  parentNode.insertBefore(oNode, afterNode);
627
677
 
628
678
  if (insertListCell) {
629
- if (domUtils.isZeroWith(line.textContent.trim())) {
630
- domUtils.removeItem(line);
679
+ if (dom.check.isZeroWidth(line.textContent.trim())) {
680
+ dom.utils.removeItem(line);
631
681
  oNode = oNode.lastChild;
632
682
  } else {
633
- const chList = domUtils.getArrayItem(line.children, domUtils.isList);
683
+ const chList = dom.utils.arrayFind(line.children, dom.check.isList);
634
684
  if (chList) {
635
685
  if (oNode !== chList) {
636
686
  oNode.appendChild(chList);
@@ -640,8 +690,8 @@ HTML.prototype = {
640
690
  oNode = parentNode;
641
691
  }
642
692
 
643
- if (domUtils.isZeroWith(line.textContent.trim())) {
644
- domUtils.removeItem(line);
693
+ if (dom.check.isZeroWidth(line.textContent.trim())) {
694
+ dom.utils.removeItem(line);
645
695
  }
646
696
  }
647
697
  }
@@ -650,6 +700,8 @@ HTML.prototype = {
650
700
  parentNode.appendChild(oNode);
651
701
  console.warn('[SUNEDITOR.html.insertNode.warn]', error);
652
702
  } finally {
703
+ if (fNode) oNode = fNode;
704
+
653
705
  const dupleNodes = parentNode.querySelectorAll('[data-duple]');
654
706
  if (dupleNodes.length > 0) {
655
707
  for (let i = 0, len = dupleNodes.length, d, c, ch, parent; i < len; i++) {
@@ -663,14 +715,14 @@ HTML.prototype = {
663
715
  }
664
716
 
665
717
  if (d === oNode) oNode = c;
666
- domUtils.removeItem(d);
718
+ dom.utils.removeItem(d);
667
719
  }
668
720
  }
669
721
 
670
722
  if ((this.format.isLine(oNode) || this.component.isBasic(oNode)) && startCon === endCon) {
671
723
  const cItem = this.format.getLine(commonCon, null);
672
- if (cItem?.nodeType === 1 && domUtils.isEmptyLine(cItem)) {
673
- domUtils.removeItem(cItem);
724
+ if (cItem?.nodeType === 1 && dom.check.isEmptyLine(cItem)) {
725
+ dom.utils.removeItem(cItem);
674
726
  }
675
727
  }
676
728
 
@@ -681,39 +733,17 @@ HTML.prototype = {
681
733
  if (!this.component.isBasic(oNode)) {
682
734
  let offset = 1;
683
735
  if (oNode.nodeType === 3) {
684
- const previous = oNode.previousSibling;
685
- const next = oNode.nextSibling;
686
- const previousText = !previous || previous.nodeType === 1 || domUtils.isZeroWith(previous) ? '' : previous.textContent;
687
- const nextText = !next || next.nodeType === 1 || domUtils.isZeroWith(next) ? '' : next.textContent;
688
-
689
- if (previous && previousText.length > 0) {
690
- oNode.textContent = previousText + oNode.textContent;
691
- domUtils.removeItem(previous);
692
- }
693
-
694
- if (next && nextText.length > 0) {
695
- oNode.textContent += nextText;
696
- domUtils.removeItem(next);
697
- }
698
-
699
- const newRange = {
700
- container: oNode,
701
- startOffset: previousText.length,
702
- endOffset: oNode.textContent.length - nextText.length
703
- };
704
-
705
- this.selection.setRange(oNode, newRange.startOffset, oNode, newRange.endOffset);
706
-
707
- result = newRange;
708
- } else if (!domUtils.isBreak(oNode) && !domUtils.isListCell(oNode) && this.format.isLine(parentNode)) {
736
+ offset = oNode.textContent.length;
737
+ this.selection.setRange(oNode, offset, oNode, offset);
738
+ } else if (!dom.check.isBreak(oNode) && !dom.check.isListCell(oNode) && this.format.isLine(parentNode)) {
709
739
  let zeroWidth = null;
710
- if (!oNode.previousSibling || domUtils.isBreak(oNode.previousSibling)) {
711
- zeroWidth = domUtils.createTextNode(unicode.zeroWidthSpace);
740
+ if (!oNode.previousSibling || dom.check.isBreak(oNode.previousSibling)) {
741
+ zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
712
742
  oNode.parentNode.insertBefore(zeroWidth, oNode);
713
743
  }
714
744
 
715
- if (!oNode.nextSibling || domUtils.isBreak(oNode.nextSibling)) {
716
- zeroWidth = domUtils.createTextNode(unicode.zeroWidthSpace);
745
+ if (!oNode.nextSibling || dom.check.isBreak(oNode.nextSibling)) {
746
+ zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
717
747
  oNode.parentNode.insertBefore(zeroWidth, oNode.nextSibling);
718
748
  }
719
749
 
@@ -726,26 +756,27 @@ HTML.prototype = {
726
756
  }
727
757
  }
728
758
 
729
- if (!result) {
730
- this.history.push(true);
731
- result = oNode;
732
- }
759
+ result = oNode;
733
760
  }
734
761
 
735
762
  return result;
736
763
  },
737
764
 
738
765
  /**
766
+ * @this {HTMLThis}
739
767
  * @description Delete the selected range.
740
- * Returns {container: "the last element after deletion", offset: "offset", prevContainer: "previousElementSibling Of the deleted area"}
741
- * @returns {Object}
768
+ * @returns {{container: Node, offset: number, commonCon?: Node|null, prevContainer?: Node|null}}
769
+ * - container: "the last element after deletion"
770
+ * - offset: "offset"
771
+ * - commonCon: "commonAncestorContainer"
772
+ * - prevContainer: "previousElementSibling Of the deleted area"
742
773
  */
743
774
  remove() {
744
775
  this.selection._resetRangeToTextNode();
745
776
 
746
777
  const range = this.selection.getRange();
747
778
  const isStartEdge = range.startOffset === 0;
748
- const isEndEdge = domUtils.isEdgePoint(range.endContainer, range.endOffset, 'end');
779
+ const isEndEdge = dom.check.isEdgePoint(range.endContainer, range.endOffset, 'end');
749
780
  let prevContainer = null;
750
781
  let startPrevEl = null;
751
782
  let endNextEl = null;
@@ -765,62 +796,66 @@ HTML.prototype = {
765
796
  let endCon = range.endContainer;
766
797
  let startOff = range.startOffset;
767
798
  let endOff = range.endOffset;
768
- const commonCon = range.commonAncestorContainer.nodeType === 3 && range.commonAncestorContainer.parentNode === startCon.parentNode ? startCon.parentNode : range.commonAncestorContainer;
799
+ const commonCon = /** @type {HTMLElement} */ (range.commonAncestorContainer.nodeType === 3 && range.commonAncestorContainer.parentNode === startCon.parentNode ? startCon.parentNode : range.commonAncestorContainer);
769
800
  if (commonCon === startCon && commonCon === endCon) {
770
801
  if (this.component.isBasic(commonCon)) {
771
802
  const compInfo = this.component.get(commonCon);
772
803
  const compContainer = compInfo.container;
773
- const parent = compContainer.parentNode;
804
+ const parent = compContainer.parentElement;
774
805
 
775
806
  const next = compContainer.nextSibling || compContainer.previousSibling;
776
807
  const nextOffset = next === compContainer.previousSibling ? next?.textContent?.length || 1 : 0;
777
808
  const parentNext = parent.nextElementSibling || parent.previousElementSibling;
778
809
  const parentNextOffset = parentNext === parent.previousElementSibling ? parentNext?.textContent?.length || 1 : 0;
779
810
 
780
- domUtils.removeItem(compContainer);
811
+ dom.utils.removeItem(compContainer);
781
812
 
782
813
  if (this.format.isLine(parent)) {
783
814
  if (parent.childNodes.length === 0) {
784
- domUtils.removeItem(parent);
815
+ dom.utils.removeItem(parent);
785
816
  return {
786
817
  container: parentNext,
787
- offset: parentNextOffset
818
+ offset: parentNextOffset,
819
+ commonCon
788
820
  };
789
821
  } else {
790
822
  return {
791
823
  container: next,
792
- offset: nextOffset
824
+ offset: nextOffset,
825
+ commonCon
793
826
  };
794
827
  }
795
828
  } else {
796
829
  return {
797
830
  container: parentNext,
798
- offset: parentNextOffset
831
+ offset: parentNextOffset,
832
+ commonCon
799
833
  };
800
834
  }
801
835
  } else {
802
836
  if ((commonCon.nodeType === 1 && startOff === 0 && endOff === 1) || (commonCon.nodeType === 3 && startOff === 0 && endOff === commonCon.textContent.length)) {
803
- const nextEl = domUtils.getNextDeepestNode(commonCon, this.editor.frameContext.get('wysiwygFrame'));
804
- const prevEl = domUtils.getPreviousDeepestNode(commonCon, this.editor.frameContext.get('wysiwygFrame'));
837
+ const nextEl = dom.query.getNextDeepestNode(commonCon, this.editor.frameContext.get('wysiwyg'));
838
+ const prevEl = dom.query.getPreviousDeepestNode(commonCon, this.editor.frameContext.get('wysiwyg'));
805
839
  const line = this.format.getLine(commonCon);
806
- domUtils.removeItem(commonCon);
840
+ dom.utils.removeItem(commonCon);
807
841
 
808
842
  let rEl = nextEl || prevEl;
809
843
  let rOffset = nextEl ? 0 : rEl?.nodeType === 3 ? rEl.textContent.length : 1;
810
844
 
811
845
  const npEl = this.format.getLine(rEl) || this.component.get(rEl);
812
846
  if (line !== npEl) {
813
- rEl = npEl;
847
+ rEl = /** @type {Node} */ (npEl);
814
848
  rOffset = rOffset === 0 ? 0 : 1;
815
849
  }
816
850
 
817
- if (domUtils.isZeroWith(line)) {
818
- domUtils.removeItem(line);
851
+ if (dom.check.isZeroWidth(line) && !line.contains(rEl)) {
852
+ dom.utils.removeItem(line);
819
853
  }
820
854
 
821
855
  return {
822
856
  container: rEl,
823
- offset: rOffset
857
+ offset: rOffset,
858
+ commonCon
824
859
  };
825
860
  }
826
861
 
@@ -833,15 +868,17 @@ HTML.prototype = {
833
868
  if (!startCon || !endCon)
834
869
  return {
835
870
  container: commonCon,
836
- offset: 0
871
+ offset: 0,
872
+ commonCon
837
873
  };
838
874
 
839
875
  if (startCon === endCon && range.collapsed) {
840
- if (domUtils.isZeroWith(startCon.textContent?.substr(startOff))) {
876
+ if (dom.check.isZeroWidth(startCon.textContent?.substring(startOff))) {
841
877
  return {
842
878
  container: startCon,
843
879
  offset: startOff,
844
- prevContainer: startCon && startCon.parentNode ? startCon : null
880
+ prevContainer: startCon && startCon.parentNode ? startCon : null,
881
+ commonCon
845
882
  };
846
883
  }
847
884
  }
@@ -849,9 +886,9 @@ HTML.prototype = {
849
886
  let beforeNode = null;
850
887
  let afterNode = null;
851
888
 
852
- const childNodes = domUtils.getListChildNodes(commonCon, null);
853
- let startIndex = domUtils.getArrayIndex(childNodes, startCon);
854
- let endIndex = domUtils.getArrayIndex(childNodes, endCon);
889
+ const childNodes = dom.query.getListChildNodes(commonCon, null);
890
+ let startIndex = dom.utils.getArrayIndex(childNodes, startCon);
891
+ let endIndex = dom.utils.getArrayIndex(childNodes, endCon);
855
892
 
856
893
  if (childNodes.length > 0 && startIndex > -1 && endIndex > -1) {
857
894
  for (let i = startIndex + 1, startNode = startCon; i >= 0; i--) {
@@ -870,25 +907,28 @@ HTML.prototype = {
870
907
  }
871
908
  } else {
872
909
  if (childNodes.length === 0) {
873
- if (this.format.isLine(commonCon) || this.format.isBlock(commonCon) || domUtils.isWysiwygFrame(commonCon) || domUtils.isBreak(commonCon) || domUtils.isMedia(commonCon)) {
910
+ if (this.format.isLine(commonCon) || this.format.isBlock(commonCon) || dom.check.isWysiwygFrame(commonCon) || dom.check.isBreak(commonCon) || dom.check.isMedia(commonCon)) {
874
911
  return {
875
912
  container: commonCon,
876
- offset: 0
913
+ offset: 0,
914
+ commonCon
877
915
  };
878
- } else if (commonCon.nodeType === 3) {
916
+ } else if (dom.check.isText(commonCon)) {
879
917
  return {
880
918
  container: commonCon,
881
- offset: endOff
919
+ offset: endOff,
920
+ commonCon
882
921
  };
883
922
  }
884
923
  childNodes.push(commonCon);
885
924
  startCon = endCon = commonCon;
886
925
  } else {
887
926
  startCon = endCon = childNodes[0];
888
- if (domUtils.isBreak(startCon) || domUtils.isZeroWith(startCon)) {
927
+ if (dom.check.isBreak(startCon) || dom.check.isZeroWidth(startCon)) {
889
928
  return {
890
- container: domUtils.isMedia(commonCon) ? commonCon : startCon,
891
- offset: 0
929
+ container: dom.check.isMedia(commonCon) ? commonCon : startCon,
930
+ offset: 0,
931
+ commonCon
892
932
  };
893
933
  }
894
934
  }
@@ -896,29 +936,33 @@ HTML.prototype = {
896
936
  startIndex = endIndex = 0;
897
937
  }
898
938
 
939
+ const _isText = dom.check.isText;
940
+ const _isElement = dom.check.isElement;
899
941
  for (let i = startIndex; i <= endIndex; i++) {
900
- const item = childNodes[i];
942
+ const item = /** @type {Text} */ (childNodes[i]);
901
943
 
902
- if (item.length === 0 || (item.nodeType === 3 && item.data === undefined)) {
944
+ if (_isText(item) && (item.data === undefined || item.length === 0)) {
903
945
  this._nodeRemoveListItem(item);
904
946
  continue;
905
947
  }
906
948
 
907
949
  if (item === startCon) {
908
- if (startCon.nodeType === 1) {
950
+ if (_isElement(startCon)) {
909
951
  if (this.component.is(startCon)) continue;
910
- else beforeNode = domUtils.createTextNode(startCon.textContent);
952
+ else beforeNode = dom.utils.createTextNode(startCon.textContent);
911
953
  } else {
954
+ const sc = /** @type {Text} */ (startCon);
955
+ const ec = /** @type {Text} */ (endCon);
912
956
  if (item === endCon) {
913
- beforeNode = domUtils.createTextNode(startCon.substringData(0, startOff) + endCon.substringData(endOff, endCon.length - endOff));
957
+ beforeNode = dom.utils.createTextNode(sc.substringData(0, startOff) + ec.substringData(endOff, ec.length - endOff));
914
958
  offset = startOff;
915
959
  } else {
916
- beforeNode = domUtils.createTextNode(startCon.substringData(0, startOff));
960
+ beforeNode = dom.utils.createTextNode(sc.substringData(0, startOff));
917
961
  }
918
962
  }
919
963
 
920
964
  if (beforeNode.length > 0) {
921
- startCon.data = beforeNode.data;
965
+ /** @type {Text} */ (startCon).data = beforeNode.data;
922
966
  } else {
923
967
  this._nodeRemoveListItem(startCon);
924
968
  }
@@ -928,15 +972,15 @@ HTML.prototype = {
928
972
  }
929
973
 
930
974
  if (item === endCon) {
931
- if (endCon.nodeType === 1) {
932
- if (this.component.is(endCon)) continue;
933
- else afterNode = domUtils.createTextNode(endCon.textContent);
975
+ if (_isText(endCon)) {
976
+ afterNode = dom.utils.createTextNode(endCon.substringData(endOff, endCon.length - endOff));
934
977
  } else {
935
- afterNode = domUtils.createTextNode(endCon.substringData(endOff, endCon.length - endOff));
978
+ if (this.component.is(endCon)) continue;
979
+ else afterNode = dom.utils.createTextNode(endCon.textContent);
936
980
  }
937
981
 
938
982
  if (afterNode.length > 0) {
939
- endCon.data = afterNode.data;
983
+ /** @type {Text} */ (endCon).data = afterNode.data;
940
984
  } else {
941
985
  this._nodeRemoveListItem(endCon);
942
986
  }
@@ -947,14 +991,25 @@ HTML.prototype = {
947
991
  this._nodeRemoveListItem(item);
948
992
  }
949
993
 
950
- const endUl = domUtils.getParentElement(endCon, 'ul');
951
- const startLi = domUtils.getParentElement(startCon, 'li');
994
+ const endUl = dom.query.getParentElement(endCon, 'ul');
995
+ const startLi = dom.query.getParentElement(startCon, 'li');
952
996
  if (endUl && startLi && startLi.contains(endUl)) {
953
997
  container = endUl.previousSibling;
954
998
  offset = container.textContent.length;
955
999
  } else {
956
1000
  container = endCon && endCon.parentNode ? endCon : startCon && startCon.parentNode ? startCon : range.endContainer || range.startContainer;
957
- offset = !isStartEdge && !isEndEdge ? offset : isEndEdge ? container.textContent.length : 0;
1001
+ if (isStartEdge || isEndEdge) {
1002
+ if (isEndEdge) {
1003
+ if (container.nodeType === 1 && container.childNodes.length === 0) {
1004
+ container.appendChild(dom.utils.createElement('BR'));
1005
+ offset = 1;
1006
+ } else {
1007
+ offset = container.textContent.length;
1008
+ }
1009
+ } else {
1010
+ offset = 0;
1011
+ }
1012
+ }
958
1013
  }
959
1014
 
960
1015
  if (!this.format.getLine(container) && !(startCon && startCon.parentNode)) {
@@ -967,7 +1022,7 @@ HTML.prototype = {
967
1022
  }
968
1023
  }
969
1024
 
970
- if (!domUtils.isWysiwygFrame(container) && container.childNodes.length === 0) {
1025
+ if (!dom.check.isWysiwygFrame(container) && container.childNodes.length === 0) {
971
1026
  const rc = this.nodeTransform.removeAllParents(container, null, null);
972
1027
  if (rc) container = rc.sc || rc.ec || this.editor.frameContext.get('wysiwyg');
973
1028
  }
@@ -976,21 +1031,24 @@ HTML.prototype = {
976
1031
  this.selection.setRange(container, offset, container, offset);
977
1032
 
978
1033
  return {
979
- container: container,
980
- offset: offset,
981
- prevContainer: prevContainer
1034
+ container,
1035
+ offset,
1036
+ prevContainer,
1037
+ commonCon
982
1038
  };
983
1039
  },
984
1040
 
985
1041
  /**
1042
+ * @this {HTMLThis}
986
1043
  * @description Gets the current content
987
- * @param {boolean} withFrame Gets the current content with containing parent div.sun-editor-editable (<div class="sun-editor-editable">{content}</div>).
1044
+ * @param {Object} [options] Options
1045
+ * @param {boolean} [options.withFrame=false] Gets the current content with containing parent div.sun-editor-editable (<div class="sun-editor-editable">{content}</div>).
988
1046
  * Ignored for targetOptions.get('iframe_fullPage') is true.
989
- * @param {boolean} includeFullPage Return only the content of the body without headers when the "iframe_fullPage" option is true
990
- * @param {number|Array.<number>|undefined} rootKey Root index
991
- * @returns {string|Array.<string>}
1047
+ * @param {boolean} [options.includeFullPage=false] Return only the content of the body without headers when the "iframe_fullPage" option is true
1048
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1049
+ * @returns {string|Object<*, string>}
992
1050
  */
993
- get(withFrame, includeFullPage, rootKey) {
1051
+ get({ withFrame, includeFullPage, rootKey } = {}) {
994
1052
  if (!rootKey) rootKey = [this.status.rootKey];
995
1053
  else if (!Array.isArray(rootKey)) rootKey = [rootKey];
996
1054
 
@@ -1000,23 +1058,23 @@ HTML.prototype = {
1000
1058
  this.editor.changeFrameContext(rootKey[i]);
1001
1059
 
1002
1060
  const fc = this.editor.frameContext;
1003
- const renderHTML = domUtils.createElement('DIV', null, this._convertToCode(fc.get('wysiwyg'), true));
1004
- const editableEls = domUtils.getListChildren(renderHTML, (current) => current.hasAttribute('contenteditable'));
1061
+ const renderHTML = dom.utils.createElement('DIV', null, this._convertToCode(fc.get('wysiwyg'), true));
1062
+ const editableEls = dom.query.getListChildren(renderHTML, (current) => current.hasAttribute('contenteditable'));
1005
1063
 
1006
1064
  for (let j = 0, jlen = editableEls.length; j < jlen; j++) {
1007
1065
  editableEls[j].removeAttribute('contenteditable');
1008
1066
  }
1009
1067
 
1010
- const content = this.clean(renderHTML.innerHTML, false, null, null);
1068
+ const content = renderHTML.innerHTML;
1011
1069
  if (this.editor.frameOptions.get('iframe_fullPage')) {
1012
1070
  if (includeFullPage) {
1013
- const attrs = domUtils.getAttributesToString(fc.get('_wd').body, ['contenteditable']);
1014
- r = '<!DOCTYPE html><html>' + fc.get('_wd').head.outerHTML + '<body ' + attrs + '>' + content + '</body></html>';
1071
+ const attrs = dom.utils.getAttributesToString(fc.get('_wd').body, ['contenteditable']);
1072
+ r = `<!DOCTYPE html><html>${fc.get('_wd').head.outerHTML}<body ${attrs}>${content}</body></html>`;
1015
1073
  } else {
1016
1074
  r = content;
1017
1075
  }
1018
1076
  } else {
1019
- r = withFrame ? '<div class="sun-editor-editable' + (this.options.get('_rtl') ? ' se-rtl' : '') + '">' + content + '</div>' : renderHTML.innerHTML;
1077
+ r = withFrame ? `<div class="${this.options.get('_editableClass') + '' + (this.options.get('_rtl') ? ' se-rtl' : '')}">${content}</div>` : renderHTML.innerHTML;
1020
1078
  }
1021
1079
 
1022
1080
  resultValue[rootKey[i]] = r;
@@ -1027,13 +1085,15 @@ HTML.prototype = {
1027
1085
  },
1028
1086
 
1029
1087
  /**
1030
- * @description Sets the HTML string
1031
- * @param {string|undefined} html HTML string
1032
- * @param {number|Array.<number>|undefined} rootKey Root index
1088
+ * @this {HTMLThis}
1089
+ * @description Sets the HTML string to the editor content
1090
+ * @param {string} html HTML string
1091
+ * @param {Object} [options] Options
1092
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1033
1093
  */
1034
- set(html, rootKey) {
1094
+ set(html, { rootKey } = {}) {
1035
1095
  this.selection.removeRange();
1036
- const convertValue = html === null || html === undefined ? '' : this.clean(html, true, null, null);
1096
+ const convertValue = html === null || html === undefined ? '' : this.clean(html, { forceFormat: true, whitelist: null, blacklist: null });
1037
1097
 
1038
1098
  if (!rootKey) rootKey = [this.status.rootKey];
1039
1099
  else if (!Array.isArray(rootKey)) rootKey = [rootKey];
@@ -1053,20 +1113,22 @@ HTML.prototype = {
1053
1113
  },
1054
1114
 
1055
1115
  /**
1116
+ * @this {HTMLThis}
1056
1117
  * @description Add content to the end of content.
1057
- * @param {string} content Content to Input
1058
- * @param {number|Array.<number>|undefined} rootKey Root index
1118
+ * @param {string} html Content to Input
1119
+ * @param {Object} [options] Options
1120
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1059
1121
  */
1060
- add(content, rootKey) {
1122
+ add(html, { rootKey } = {}) {
1061
1123
  if (!rootKey) rootKey = [this.status.rootKey];
1062
1124
  else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1063
1125
 
1064
1126
  for (let i = 0; i < rootKey.length; i++) {
1065
1127
  this.editor.changeFrameContext(rootKey[i]);
1066
- const convertValue = this.clean(content, true, null, null);
1128
+ const convertValue = this.clean(html, { forceFormat: true, whitelist: null, blacklist: null });
1067
1129
 
1068
1130
  if (!this.editor.frameContext.get('isCodeView')) {
1069
- const temp = domUtils.createElement('DIV', null, convertValue);
1131
+ const temp = dom.utils.createElement('DIV', null, convertValue);
1070
1132
  const children = temp.children;
1071
1133
  const len = children.length;
1072
1134
  for (let j = 0; j < len; j++) {
@@ -1082,11 +1144,54 @@ HTML.prototype = {
1082
1144
  },
1083
1145
 
1084
1146
  /**
1147
+ * @this {HTMLThis}
1148
+ * @description Gets the current content to JSON data
1149
+ * @param {Object} [options] Options
1150
+ * @param {boolean} [options.withFrame=false] Gets the current content with containing parent div.sun-editor-editable (<div class="sun-editor-editable">{content}</div>).
1151
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1152
+ * @returns {Object<string, *>} JSON data
1153
+ */
1154
+ getJson({ withFrame, rootKey } = {}) {
1155
+ return converter.htmlToJson(this.get({ withFrame, rootKey }));
1156
+ },
1157
+
1158
+ /**
1159
+ * @this {HTMLThis}
1160
+ * @description Sets the JSON data to the editor content
1161
+ * @param {Object<string, *>} jsdonData HTML string
1162
+ * @param {Object} [options] Options
1163
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1164
+ */
1165
+ setJson(jsdonData, { rootKey } = {}) {
1166
+ this.set(converter.jsonToHtml(jsdonData), { rootKey });
1167
+ },
1168
+
1169
+ /**
1170
+ * @this {HTMLThis}
1171
+ * @description Call "clipboard.write" to copy the contents and display a success/failure toast message.
1172
+ * @param {Element|Text|string} content Content to be copied to the clipboard
1173
+ * @returns {Promise<boolean>} Success or failure
1174
+ */
1175
+ async copy(content) {
1176
+ try {
1177
+ await clipboard.write(content);
1178
+ this.editor.ui.showToast(this.lang.message_copy_success, this.options.get('toastMessageTime').copy);
1179
+ return true;
1180
+ } catch (err) {
1181
+ console.error('[SUNEDITOR.html.copy.fail] :', err);
1182
+ this.editor.ui.showToast(this.lang.message_copy_fail, this.options.get('toastMessageTime').copy, 'error');
1183
+ return false;
1184
+ }
1185
+ },
1186
+
1187
+ /**
1188
+ * @this {HTMLThis}
1085
1189
  * @description Sets the content of the iframe's head tag and body tag when using the "iframe" or "iframe_fullPage" option.
1086
- * @param {Object} ctx { head: HTML string, body: HTML string}
1087
- * @param {number|Array.<number>|undefined} rootKey Root index
1190
+ * @param {{head: string, body: string}} ctx { head: HTML string, body: HTML string}
1191
+ * @param {Object} [options] Options
1192
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1088
1193
  */
1089
- setFullPage(ctx, rootKey) {
1194
+ setFullPage(ctx, { rootKey } = {}) {
1090
1195
  if (!this.editor.frameOptions.get('iframe')) return false;
1091
1196
 
1092
1197
  if (!rootKey) rootKey = [this.status.rootKey];
@@ -1095,24 +1200,30 @@ HTML.prototype = {
1095
1200
  for (let i = 0; i < rootKey.length; i++) {
1096
1201
  this.editor.changeFrameContext(rootKey[i]);
1097
1202
  if (ctx.head) this.editor.frameContext.get('_wd').head.innerHTML = ctx.head.replace(this.__disallowedTagsRegExp, '');
1098
- if (ctx.body) this.editor.frameContext.get('_wd').body.innerHTML = this.clean(ctx.body, true, null, null);
1203
+ if (ctx.body) this.editor.frameContext.get('_wd').body.innerHTML = this.clean(ctx.body, { forceFormat: true, whitelist: null, blacklist: null });
1099
1204
  this.editor._resetComponents();
1100
1205
  }
1101
1206
  },
1102
1207
 
1103
1208
  /**
1209
+ * @this {HTMLThis}
1104
1210
  * @description HTML code compression
1105
1211
  * @param {string} html HTML string
1106
1212
  * @returns {string} HTML string
1107
1213
  */
1108
1214
  compress(html) {
1109
- return html.replace(/\n/g, '').replace(/(>)(?:\s+)(<)/g, '$1$2');
1215
+ return html
1216
+ .replace(/\n/g, '')
1217
+ .replace(/(>)(?:\s+)(<)/g, '$1$2')
1218
+ .trim();
1110
1219
  },
1111
1220
 
1112
1221
  /**
1222
+ * @private
1223
+ * @this {HTMLThis}
1113
1224
  * @description construct wysiwyg area element to html string
1114
- * @param {Element|String} html WYSIWYG element (this.editor.frameContext.get('wysiwyg')) or HTML string.
1115
- * @param {Boolean} comp If true, does not line break and indentation of tags.
1225
+ * @param {Node|string} html WYSIWYG element (this.editor.frameContext.get('wysiwyg')) or HTML string.
1226
+ * @param {boolean} comp If true, does not line break and indentation of tags.
1116
1227
  * @returns {string}
1117
1228
  */
1118
1229
  _convertToCode(html, comp) {
@@ -1125,8 +1236,8 @@ HTML.prototype = {
1125
1236
  };
1126
1237
  const brChar = comp ? '' : '\n';
1127
1238
 
1128
- let indentSize = comp ? 0 : this.status.codeIndentSize * 1;
1129
- indentSize = indentSize > 0 ? new Array(indentSize + 1).join(' ') : '';
1239
+ const codeSize = comp ? 0 : this.status.codeIndentSize * 1;
1240
+ const indentSize = codeSize > 0 ? new Array(codeSize + 1).join(' ') : '';
1130
1241
 
1131
1242
  (function recursionFunc(element, indent) {
1132
1243
  const children = element.childNodes;
@@ -1144,22 +1255,22 @@ HTML.prototype = {
1144
1255
  continue;
1145
1256
  }
1146
1257
  if (node.nodeType === 3) {
1147
- if (!domUtils.isList(node.parentElement)) returnHTML += converter.htmlToEntity(/^\n+$/.test(node.data) ? '' : node.data);
1258
+ if (!dom.check.isList(node.parentElement)) returnHTML += converter.htmlToEntity(/^\n+$/.test(/** @type {Text} */ (node).data) ? '' : /** @type {Text} */ (node).data);
1148
1259
  continue;
1149
1260
  }
1150
1261
  if (node.childNodes.length === 0) {
1151
- returnHTML += (/^HR$/i.test(node.nodeName) ? brChar : '') + (/^PRE$/i.test(node.parentElement.nodeName) && /^BR$/i.test(node.nodeName) ? '' : elementIndent) + node.outerHTML + br;
1262
+ returnHTML += (/^HR$/i.test(node.nodeName) ? brChar : '') + (/^PRE$/i.test(node.parentElement.nodeName) && /^BR$/i.test(node.nodeName) ? '' : elementIndent) + /** @type {HTMLElement} */ (node).outerHTML + br;
1152
1263
  continue;
1153
1264
  }
1154
1265
 
1155
- if (!node.outerHTML) {
1266
+ if (!(/** @type {HTMLElement} */ (node).outerHTML)) {
1156
1267
  returnHTML += new XMLSerializer().serializeToString(node);
1157
1268
  } else {
1158
1269
  tag = node.nodeName.toLowerCase();
1159
1270
  tagIndent = elementIndent || nodeRegTest ? indent : '';
1160
- returnHTML += (lineBR || (elementRegTest ? '' : br)) + tagIndent + node.outerHTML.match(wRegExp('<' + tag + '[^>]*>', 'i'))[0] + br;
1161
- recursionFunc(node, indent + indentSize, '');
1162
- returnHTML += (/\n$/.test(returnHTML) ? tagIndent : '') + '</' + tag + '>' + (lineBR || br || elementRegTest ? brChar : '' || /^(TH|TD)$/i.test(node.nodeName) ? brChar : '');
1271
+ returnHTML += (lineBR || (elementRegTest ? '' : br)) + tagIndent + /** @type {HTMLElement} */ (node).outerHTML.match(wRegExp('<' + tag + '[^>]*>', 'i'))[0] + br;
1272
+ recursionFunc(node, indent + indentSize + '');
1273
+ returnHTML += (/\n$/.test(returnHTML) ? tagIndent : '') + '</' + tag + '>' + (lineBR || br || elementRegTest ? brChar : /^(TH|TD)$/i.test(node.nodeName) ? brChar : '');
1163
1274
  }
1164
1275
  }
1165
1276
  })(wDoc, '');
@@ -1167,24 +1278,31 @@ HTML.prototype = {
1167
1278
  return returnHTML.trim() + brChar;
1168
1279
  },
1169
1280
 
1281
+ /**
1282
+ * @private
1283
+ * @this {HTMLThis}
1284
+ * @description Checks whether the given list item node should be removed and handles necessary clean-up.
1285
+ * @param {Node} item The list item node to be checked.
1286
+ */
1170
1287
  _nodeRemoveListItem(item) {
1171
1288
  const line = this.format.getLine(item, null);
1172
- domUtils.removeItem(item);
1289
+ dom.utils.removeItem(item);
1173
1290
 
1174
- if (!domUtils.isListCell(line)) return;
1291
+ if (!dom.check.isListCell(line)) return;
1175
1292
 
1176
1293
  this.nodeTransform.removeAllParents(line, null, null);
1177
1294
 
1178
- if (domUtils.isList(line?.firstChild)) {
1179
- line.insertBefore(domUtils.createTextNode(unicode.zeroWidthSpace), line.firstChild);
1295
+ if (dom.check.isList(line?.firstChild)) {
1296
+ line.insertBefore(dom.utils.createTextNode(unicode.zeroWidthSpace), line.firstChild);
1180
1297
  }
1181
1298
  },
1182
1299
 
1183
1300
  /**
1301
+ * @private
1302
+ * @this {HTMLThis}
1184
1303
  * @description Recursive function when used to place a node in "BrLine" in "html.insertNode"
1185
1304
  * @param {Node} oNode Node to be inserted
1186
1305
  * @returns {Node} "oNode"
1187
- * @private
1188
1306
  */
1189
1307
  _setIntoFreeFormat(oNode) {
1190
1308
  const parentNode = oNode.parentNode;
@@ -1206,8 +1324,8 @@ HTML.prototype = {
1206
1324
  parentNode.insertBefore(lastONode, oNode);
1207
1325
  }
1208
1326
 
1209
- if (oNode.childNodes.length === 0) domUtils.removeItem(oNode);
1210
- oNode = domUtils.createElement('BR');
1327
+ if (oNode.childNodes.length === 0) dom.utils.removeItem(oNode);
1328
+ oNode = dom.utils.createElement('BR');
1211
1329
  parentNode.insertBefore(oNode, lastONode.nextSibling);
1212
1330
  }
1213
1331
 
@@ -1215,43 +1333,47 @@ HTML.prototype = {
1215
1333
  },
1216
1334
 
1217
1335
  /**
1336
+ * @private
1337
+ * @this {HTMLThis}
1218
1338
  * @description Returns HTML string according to tag type and configurati isExcludeFormat.
1219
1339
  * @param {Node} node Node
1220
- * @param {boolean} requireFormat If true, text nodes that do not have a format node is wrapped with the format tag.
1221
- * @private
1340
+ * @param {boolean} forceFormat If true, text nodes that do not have a format node is wrapped with the format tag.
1222
1341
  */
1223
- _makeLine(node, requireFormat) {
1342
+ _makeLine(node, forceFormat) {
1224
1343
  const defaultLine = this.options.get('defaultLine');
1225
1344
  // element
1226
1345
  if (node.nodeType === 1) {
1227
1346
  if (this.__disallowedTagNameRegExp.test(node.nodeName)) return '';
1228
- if (domUtils.isExcludeFormat(node)) return node.outerHTML;
1347
+ if (dom.check.isExcludeFormat(node)) return node.outerHTML;
1229
1348
 
1230
1349
  const ch =
1231
- domUtils.getListChildNodes(node, (current) => {
1232
- return domUtils.isSpanWithoutAttr(current) && !domUtils.getParentElement(current, domUtils.isExcludeFormat);
1350
+ dom.query.getListChildNodes(node, (current) => {
1351
+ return dom.check.isSpanWithoutAttr(current) && !dom.query.getParentElement(current, dom.check.isExcludeFormat);
1233
1352
  }) || [];
1234
- for (let i = ch.length - 1; i >= 0; i--) {
1235
- ch[i].outerHTML = ch[i].innerHTML;
1353
+ for (let i = ch.length - 1, c; i >= 0; i--) {
1354
+ c = /** @type {HTMLElement} */ (ch[i]);
1355
+ c.outerHTML = c.innerHTML;
1236
1356
  }
1237
1357
 
1238
1358
  if (
1239
- !requireFormat ||
1359
+ !forceFormat ||
1240
1360
  this.format.isLine(node) ||
1241
1361
  this.format.isBlock(node) ||
1242
1362
  this.component.is(node) ||
1243
- domUtils.isMedia(node) ||
1244
- domUtils.isFigure(node) ||
1245
- (domUtils.isAnchor(node) && domUtils.isMedia(node.firstElementChild))
1363
+ dom.check.isMedia(node) ||
1364
+ dom.check.isFigure(node) ||
1365
+ (dom.check.isAnchor(node) && dom.check.isMedia(node.firstElementChild))
1246
1366
  ) {
1247
- return domUtils.isSpanWithoutAttr(node) ? node.innerHTML : node.outerHTML;
1367
+ const n = /** @type {HTMLElement} */ (node);
1368
+ return dom.check.isSpanWithoutAttr(node) ? n.innerHTML : n.outerHTML;
1248
1369
  } else {
1249
- return '<' + defaultLine + '>' + (domUtils.isSpanWithoutAttr(node) ? node.innerHTML : node.outerHTML) + '</' + defaultLine + '>';
1370
+ const n = /** @type {HTMLElement} */ (node);
1371
+ return '<' + defaultLine + '>' + (dom.check.isSpanWithoutAttr(node) ? n.innerHTML : n.outerHTML) + '</' + defaultLine + '>';
1250
1372
  }
1251
1373
  }
1252
1374
  // text
1253
1375
  if (node.nodeType === 3) {
1254
- if (!requireFormat) return converter.htmlToEntity(node.textContent);
1376
+ if (!forceFormat) return converter.htmlToEntity(node.textContent);
1255
1377
  const textArray = node.textContent.split(/\n/g);
1256
1378
  let html = '';
1257
1379
  for (let i = 0, tLen = textArray.length, text; i < tLen; i++) {
@@ -1269,43 +1391,48 @@ HTML.prototype = {
1269
1391
  },
1270
1392
 
1271
1393
  /**
1394
+ * @private
1395
+ * @this {HTMLThis}
1272
1396
  * @description Fix tags that do not fit the editor format.
1273
- * @param {Element} documentFragment Document fragment "DOCUMENT_FRAGMENT_NODE" (nodeType === 11)
1397
+ * @param {DocumentFragment} documentFragment Document fragment "DOCUMENT_FRAGMENT_NODE" (nodeType === 11)
1274
1398
  * @param {RegExp} htmlCheckWhitelistRegExp Editor tags whitelist
1275
1399
  * @param {RegExp} htmlCheckBlacklistRegExp Editor tags blacklist
1276
- * @private
1400
+ * @param {boolean} tagFilter Tag filter option
1401
+ * @param {boolean} formatFilter Format filter option
1402
+ * @param {boolean} classFilter Class name filter option
1403
+ * @param {boolean} _freeCodeViewMode Enforces strict HTML validation based on the editor`s policy
1277
1404
  */
1278
- _consistencyCheckOfHTML(documentFragment, htmlCheckWhitelistRegExp, htmlCheckBlacklistRegExp, tagFilter, formatFilter, classFilter) {
1405
+ _consistencyCheckOfHTML(documentFragment, htmlCheckWhitelistRegExp, htmlCheckBlacklistRegExp, tagFilter, formatFilter, classFilter, _freeCodeViewMode) {
1279
1406
  const removeTags = [],
1280
1407
  emptyTags = [],
1281
1408
  wrongList = [],
1282
1409
  withoutFormatCells = [];
1283
1410
 
1284
1411
  // wrong position
1285
- const wrongTags = domUtils.getListChildNodes(documentFragment, (current) => {
1412
+ const wrongTags = dom.query.getListChildNodes(documentFragment, (current) => {
1286
1413
  if (formatFilter && current.nodeType !== 1) {
1287
- if (domUtils.isList(current.parentElement)) removeTags.push(current);
1414
+ if (dom.check.isList(current.parentElement)) removeTags.push(current);
1288
1415
  return false;
1289
1416
  }
1290
1417
 
1291
1418
  // tag filter
1292
1419
  if (tagFilter) {
1293
1420
  // white list
1294
- if (htmlCheckBlacklistRegExp.test(current.nodeName) || (!htmlCheckWhitelistRegExp.test(current.nodeName) && current.childNodes.length === 0 && domUtils.isExcludeFormat(current))) {
1421
+ if (htmlCheckBlacklistRegExp.test(current.nodeName) || (!htmlCheckWhitelistRegExp.test(current.nodeName) && current.childNodes.length === 0 && dom.check.isExcludeFormat(current))) {
1295
1422
  removeTags.push(current);
1296
1423
  return false;
1297
1424
  }
1298
1425
  }
1299
1426
 
1300
- const nrtag = !domUtils.getParentElement(current, domUtils.isExcludeFormat);
1427
+ const nrtag = !dom.query.getParentElement(current, dom.check.isExcludeFormat);
1301
1428
 
1302
1429
  // formatFilter
1303
1430
  if (formatFilter) {
1304
1431
  // empty tags
1305
1432
  if (
1306
- !domUtils.isTableElements(current) &&
1307
- !domUtils.isListCell(current) &&
1308
- !domUtils.isAnchor(current) &&
1433
+ !dom.check.isTableElements(current) &&
1434
+ !dom.check.isListCell(current) &&
1435
+ !dom.check.isAnchor(current) &&
1309
1436
  (this.format.isLine(current) || this.format.isBlock(current) || this.format.isTextStyleNode(current)) &&
1310
1437
  current.childNodes.length === 0 &&
1311
1438
  nrtag
@@ -1315,13 +1442,13 @@ HTML.prototype = {
1315
1442
  }
1316
1443
 
1317
1444
  // wrong list
1318
- if (domUtils.isList(current.parentNode) && !domUtils.isList(current) && !domUtils.isListCell(current)) {
1445
+ if (dom.check.isList(current.parentNode) && !dom.check.isList(current) && !dom.check.isListCell(current)) {
1319
1446
  wrongList.push(current);
1320
1447
  return false;
1321
1448
  }
1322
1449
 
1323
1450
  // table cells
1324
- if (domUtils.isTableCell(current)) {
1451
+ if (dom.check.isTableCell(current)) {
1325
1452
  const fel = current.firstElementChild;
1326
1453
  if (!this.format.isLine(fel) && !this.format.isBlock(fel) && !this.component.is(fel)) {
1327
1454
  withoutFormatCells.push(current);
@@ -1345,16 +1472,17 @@ HTML.prototype = {
1345
1472
  }
1346
1473
 
1347
1474
  const result =
1475
+ !_freeCodeViewMode &&
1348
1476
  current.parentNode !== documentFragment &&
1349
1477
  nrtag &&
1350
- ((domUtils.isListCell(current) && !domUtils.isList(current.parentNode)) ||
1351
- ((this.format.isLine(current) || this.component.is(current)) && !this.format.isBlock(current.parentNode) && !domUtils.getParentElement(current, this.component.is.bind(this.component))));
1478
+ ((dom.check.isListCell(current) && !dom.check.isList(current.parentNode)) ||
1479
+ ((this.format.isLine(current) || this.component.is(current)) && !this.format.isBlock(current.parentNode) && !dom.query.getParentElement(current, this.component.is.bind(this.component))));
1352
1480
 
1353
1481
  return result;
1354
1482
  });
1355
1483
 
1356
1484
  for (let i = 0, len = removeTags.length; i < len; i++) {
1357
- domUtils.removeItem(removeTags[i]);
1485
+ dom.utils.removeItem(removeTags[i]);
1358
1486
  }
1359
1487
 
1360
1488
  const checkTags = [];
@@ -1363,7 +1491,7 @@ HTML.prototype = {
1363
1491
  p = t.parentNode;
1364
1492
  if (!p || !p.parentNode) continue;
1365
1493
 
1366
- if (domUtils.getParentElement(t, domUtils.isListCell)) {
1494
+ if (dom.query.getParentElement(t, dom.check.isListCell)) {
1367
1495
  const cellChildren = t.childNodes;
1368
1496
  for (let j = cellChildren.length - 1; len >= 0; j--) {
1369
1497
  p.insertBefore(t, cellChildren[j]);
@@ -1377,13 +1505,13 @@ HTML.prototype = {
1377
1505
 
1378
1506
  for (let i = 0, len = checkTags.length, t; i < len; i++) {
1379
1507
  t = checkTags[i];
1380
- if (domUtils.isZeroWith(t.textContent.trim())) {
1381
- domUtils.removeItem(t);
1508
+ if (dom.check.isZeroWidth(t.textContent.trim())) {
1509
+ dom.utils.removeItem(t);
1382
1510
  }
1383
1511
  }
1384
1512
 
1385
1513
  for (let i = 0, len = emptyTags.length; i < len; i++) {
1386
- domUtils.removeItem(emptyTags[i]);
1514
+ dom.utils.removeItem(emptyTags[i]);
1387
1515
  }
1388
1516
 
1389
1517
  for (let i = 0, len = wrongList.length, t, tp, children, p; i < len; i++) {
@@ -1391,7 +1519,7 @@ HTML.prototype = {
1391
1519
  p = t.parentNode;
1392
1520
  if (!p) continue;
1393
1521
 
1394
- tp = domUtils.createElement('LI');
1522
+ tp = dom.utils.createElement('LI');
1395
1523
 
1396
1524
  if (this.format.isLine(t)) {
1397
1525
  children = t.childNodes;
@@ -1399,7 +1527,7 @@ HTML.prototype = {
1399
1527
  tp.appendChild(children[0]);
1400
1528
  }
1401
1529
  p.insertBefore(tp, t);
1402
- domUtils.removeItem(t);
1530
+ dom.utils.removeItem(t);
1403
1531
  } else {
1404
1532
  t = t.nextSibling;
1405
1533
  tp.appendChild(wrongList[i]);
@@ -1409,17 +1537,18 @@ HTML.prototype = {
1409
1537
 
1410
1538
  for (let i = 0, len = withoutFormatCells.length, t, f; i < len; i++) {
1411
1539
  t = withoutFormatCells[i];
1412
- f = domUtils.createElement('DIV');
1540
+ f = dom.utils.createElement('DIV');
1413
1541
  f.innerHTML = t.textContent.trim().length === 0 && t.children.length === 0 ? '<br>' : t.innerHTML;
1414
1542
  t.innerHTML = f.outerHTML;
1415
1543
  }
1416
1544
  },
1417
1545
 
1418
1546
  /**
1547
+ * @private
1548
+ * @this {HTMLThis}
1419
1549
  * @description Removes attribute values such as style and converts tags that do not conform to the "html5" standard.
1420
1550
  * @param {string} html HTML string
1421
1551
  * @returns {string} HTML string
1422
- * @private
1423
1552
  */
1424
1553
  _styleNodeConvertor(html) {
1425
1554
  if (!this._disallowedStyleNodesRegExp) return html;
@@ -1431,18 +1560,19 @@ HTML.prototype = {
1431
1560
  },
1432
1561
 
1433
1562
  /**
1434
- * @description Determines if formatting is required and returns a domTree
1435
- * @param {Element} dom documentFragment
1436
- * @returns {Element}
1437
1563
  * @private
1564
+ * @this {HTMLThis}
1565
+ * @description Determines if formatting is required and returns a domTree
1566
+ * @param {DocumentFragment} domFrag documentFragment
1567
+ * @returns {DocumentFragment}
1438
1568
  */
1439
- _editFormat(dom) {
1569
+ _editFormat(domFrag) {
1440
1570
  let value = '',
1441
1571
  f;
1442
- const tempTree = dom.childNodes;
1572
+ const tempTree = domFrag.childNodes;
1443
1573
 
1444
1574
  for (let i = 0, len = tempTree.length, n; i < len; i++) {
1445
- n = tempTree[i];
1575
+ n = /** @type {HTMLElement} */ (tempTree[i]);
1446
1576
  if (this.__allowedTagNameRegExp.test(n.nodeName)) {
1447
1577
  value += n.outerHTML;
1448
1578
  continue;
@@ -1450,8 +1580,8 @@ HTML.prototype = {
1450
1580
 
1451
1581
  if (n.nodeType === 8) {
1452
1582
  value += '<!-- ' + n.textContent + ' -->';
1453
- } else if (!this.format.isLine(n) && !this.format.isBlock(n) && !this.component.is(n) && !/meta/i.test(n.nodeName) && !domUtils.isExcludeFormat(n)) {
1454
- if (!f) f = domUtils.createElement(this.options.get('defaultLine'));
1583
+ } else if (!/meta/i.test(n.nodeName) && !this.format.isLine(n) && !this.format.isBlock(n) && !this.component.is(n) && !dom.check.isExcludeFormat(n)) {
1584
+ if (!f) f = dom.utils.createElement(this.options.get('defaultLine'));
1455
1585
  f.appendChild(n);
1456
1586
  i--;
1457
1587
  len--;
@@ -1469,22 +1599,31 @@ HTML.prototype = {
1469
1599
  return this._d.createRange().createContextualFragment(value);
1470
1600
  },
1471
1601
 
1602
+ /**
1603
+ * @private
1604
+ * @this {HTMLThis}
1605
+ * @description Converts a list of DOM nodes into an HTML list structure.
1606
+ * - If the node is already a list, its innerHTML is used. If it is a block element,
1607
+ * - the function is called recursively.
1608
+ * @param {__se__NodeCollection} domTree List of DOM nodes to be converted.
1609
+ * @returns {string} The generated HTML list.
1610
+ */
1472
1611
  _convertListCell(domTree) {
1473
1612
  let html = '';
1474
1613
 
1475
1614
  for (let i = 0, len = domTree.length, node; i < len; i++) {
1476
1615
  node = domTree[i];
1477
1616
  if (node.nodeType === 1) {
1478
- if (domUtils.isList(node)) {
1617
+ if (dom.check.isList(node)) {
1479
1618
  html += node.innerHTML;
1480
- } else if (domUtils.isListCell(node)) {
1619
+ } else if (dom.check.isListCell(node)) {
1481
1620
  html += node.outerHTML;
1482
1621
  } else if (this.format.isLine(node)) {
1483
1622
  html += '<li>' + (node.innerHTML.trim() || '<br>') + '</li>';
1484
- } else if (this.format.isBlock(node) && !domUtils.isTableElements(node)) {
1485
- html += this._convertListCell(node);
1623
+ } else if (this.format.isBlock(node) && !dom.check.isTableElements(node)) {
1624
+ html += this._convertListCell(node.children);
1486
1625
  } else {
1487
- html += '<li>' + node.outerHTML + '</li>';
1626
+ html += '<li>' + /** @type {HTMLElement} */ (node).outerHTML + '</li>';
1488
1627
  }
1489
1628
  } else {
1490
1629
  html += '<li>' + (node.textContent || '<br>') + '</li>';
@@ -1494,12 +1633,19 @@ HTML.prototype = {
1494
1633
  return html;
1495
1634
  },
1496
1635
 
1636
+ /**
1637
+ * @private
1638
+ * @this {HTMLThis}
1639
+ * @description Checks whether the provided DOM nodes require formatting.
1640
+ * @param {NodeList} domTree List of DOM nodes to check.
1641
+ * @returns {boolean} True if formatting is required, otherwise false.
1642
+ */
1497
1643
  _isFormatData(domTree) {
1498
1644
  let requireFormat = false;
1499
1645
 
1500
1646
  for (let i = 0, len = domTree.length, t; i < len; i++) {
1501
1647
  t = domTree[i];
1502
- if (t.nodeType === 1 && !this.format.isTextStyleNode(t) && !domUtils.isBreak(t) && !this.__disallowedTagNameRegExp.test(t.nodeName)) {
1648
+ if (t.nodeType === 1 && !this.format.isTextStyleNode(t) && !dom.check.isBreak(t) && !this.__disallowedTagNameRegExp.test(t.nodeName)) {
1503
1649
  requireFormat = true;
1504
1650
  break;
1505
1651
  }
@@ -1508,6 +1654,16 @@ HTML.prototype = {
1508
1654
  return requireFormat;
1509
1655
  },
1510
1656
 
1657
+ /**
1658
+ * @private
1659
+ * @this {HTMLThis}
1660
+ * @description Cleans the inline style attributes of an HTML element.
1661
+ * - Extracts allowed styles and removes disallowed ones based on editor settings.
1662
+ * @param {string} m The full matched string from a regular expression.
1663
+ * @param {Array|null} v The list of allowed attributes.
1664
+ * @param {string} name The tag name of the element being cleaned.
1665
+ * @returns {Array} The updated list of allowed attributes including cleaned styles.
1666
+ */
1511
1667
  _cleanStyle(m, v, name) {
1512
1668
  let sv = (m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/) || [])[0];
1513
1669
  if (this._textStyleTags.includes(name) && !sv && (m.match(/<[^\s]+\s(.+)/) || [])[1]) {
@@ -1515,7 +1671,7 @@ HTML.prototype = {
1515
1671
  const face = (m.match(/\sface="([^"]+)"/i) || [])[1];
1516
1672
  const color = (m.match(/\scolor="([^"]+)"/i) || [])[1];
1517
1673
  if (size || face || color) {
1518
- sv = 'style="' + (size ? 'font-size:' + numbers.get(size / 3.333, 1) + 'rem;' : '') + (face ? 'font-family:' + face + ';' : '') + (color ? 'color:' + color + ';' : '') + '"';
1674
+ sv = 'style="' + (size ? 'font-size:' + numbers.get(Number(size) / 3.333, 1) + 'rem;' : '') + (face ? 'font-family:' + face + ';' : '') + (color ? 'color:' + color + ';' : '') + '"';
1519
1675
  }
1520
1676
  }
1521
1677
 
@@ -1538,8 +1694,8 @@ HTML.prototype = {
1538
1694
  for (let i = 0, len = style.length, r; i < len; i++) {
1539
1695
  r = style[i].match(/([a-zA-Z0-9-]+)(:)([^"']+)/);
1540
1696
  if (r && !/inherit|initial|revert|unset/i.test(r[3])) {
1541
- const k = env.kebabToCamelCase(r[1].trim());
1542
- const cs = this.editor.frameContext.get('wwComputedStyle')[k].replace(/"/g, '');
1697
+ const k = converter.kebabToCamelCase(r[1].trim());
1698
+ const cs = this.editor.frameContext.get('wwComputedStyle')[k]?.replace(/"/g, '');
1543
1699
  const c = r[3].trim();
1544
1700
  switch (k) {
1545
1701
  case 'fontFamily':
@@ -1548,7 +1704,7 @@ HTML.prototype = {
1548
1704
  case 'fontSize':
1549
1705
  if (!this.plugins.fontSize) continue;
1550
1706
  if (!this.fontSizeUnitRegExp.test(r[0])) {
1551
- r[0] = r[0].replace((r[0].match(/:\s*([^;]+)/) || [])[1], converter.fontSize.bind(null, this.options.get('fontSizeUnits')[0]));
1707
+ r[0] = r[0].replace((r[0].match(/:\s*([^;]+)/) || [])[1], converter.toFontUnit.bind(null, this.options.get('fontSizeUnits')[0]));
1552
1708
  }
1553
1709
  break;
1554
1710
  case 'color':
@@ -1571,10 +1727,11 @@ HTML.prototype = {
1571
1727
  },
1572
1728
 
1573
1729
  /**
1730
+ * @private
1731
+ * @this {HTMLThis}
1574
1732
  * @description Delete disallowed tags
1575
1733
  * @param {string} html HTML string
1576
1734
  * @returns {string}
1577
- * @private
1578
1735
  */
1579
1736
  _deleteDisallowedTags(html, whitelistRegExp, blacklistRegExp) {
1580
1737
  if (whitelistRegExp.test('<font>')) {
@@ -1584,6 +1741,13 @@ HTML.prototype = {
1584
1741
  return html.replace(whitelistRegExp, '').replace(blacklistRegExp, '');
1585
1742
  },
1586
1743
 
1744
+ /**
1745
+ * @private
1746
+ * @this {HTMLThis}
1747
+ * @description Recursively checks for duplicate text style nodes within a given parent node.
1748
+ * @param {Node} oNode The node to check for duplicate styles.
1749
+ * @param {Node} parentNode The parent node where the duplicate check occurs.
1750
+ */
1587
1751
  _checkDuplicateNode(oNode, parentNode) {
1588
1752
  // eslint-disable-next-line @typescript-eslint/no-this-alias
1589
1753
  const inst = this;
@@ -1596,6 +1760,15 @@ HTML.prototype = {
1596
1760
  })(oNode);
1597
1761
  },
1598
1762
 
1763
+ /**
1764
+ * @private
1765
+ * @this {HTMLThis}
1766
+ * @description Recursively checks for duplicate text style nodes within a given parent node.
1767
+ * - If duplicate styles are found, redundant attributes are removed.
1768
+ * @param {Node} oNode The node to check for duplicate styles.
1769
+ * @param {Node} parentNode The parent node where the duplicate check occurs.
1770
+ * @returns {Node} The cleaned node with redundant styles removed.
1771
+ */
1599
1772
  _dupleCheck(oNode, parentNode) {
1600
1773
  if (!this.format.isTextStyleNode(oNode)) return;
1601
1774
 
@@ -1608,7 +1781,7 @@ HTML.prototype = {
1608
1781
  const inst = this.format;
1609
1782
  let duple = false;
1610
1783
  (function recursionFunc(ancestor) {
1611
- if (domUtils.isWysiwygFrame(ancestor) || !inst.isTextStyleNode(ancestor)) return;
1784
+ if (dom.check.isWysiwygFrame(ancestor) || !inst.isTextStyleNode(ancestor)) return;
1612
1785
  if (ancestor.nodeName === nodeName) {
1613
1786
  duple = true;
1614
1787
  const styles = ancestor.style.cssText.match(/[^;]+;/g) || [];
@@ -1642,11 +1815,12 @@ HTML.prototype = {
1642
1815
  };
1643
1816
 
1644
1817
  /**
1818
+ * @private
1819
+ * @this {HTMLThis}
1645
1820
  * @description Tag and tag attribute check RegExp function.
1646
1821
  * @param {string} m RegExp value
1647
1822
  * @param {string} t RegExp value
1648
1823
  * @returns {string}
1649
- * @private
1650
1824
  */
1651
1825
  function CleanElements(attrFilter, styleFilter, m, t) {
1652
1826
  if (/^<[a-z0-9]+:[a-z0-9]+/i.test(m)) return m;
@@ -1687,7 +1861,7 @@ function CleanElements(attrFilter, styleFilter, m, t) {
1687
1861
  }
1688
1862
 
1689
1863
  // figure
1690
- if (domUtils.isMedia(tagName) || domUtils.isFigure(tagName)) {
1864
+ if (dom.check.isMedia(tagName) || dom.check.isFigure(tagName)) {
1691
1865
  const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
1692
1866
  if (!v) v = [];
1693
1867
  if (sv) v.push(sv[0]);
@@ -1703,6 +1877,12 @@ function CleanElements(attrFilter, styleFilter, m, t) {
1703
1877
  return t;
1704
1878
  }
1705
1879
 
1880
+ /**
1881
+ * @private
1882
+ * @description Get related list
1883
+ * @param {string} str Regular expression string
1884
+ * @param {string} str2 Regular expression string
1885
+ */
1706
1886
  function GetRegList(str, str2) {
1707
1887
  return !str ? '^' : str === '*' ? '[a-z-]+' : !str2 ? str : str + '|' + str2;
1708
1888
  }