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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/CONTRIBUTING.md +186 -184
  2. package/LICENSE +21 -21
  3. package/README.md +157 -180
  4. package/dist/suneditor.min.css +1 -1
  5. package/dist/suneditor.min.js +1 -1
  6. package/package.json +126 -123
  7. package/src/assets/design/color.css +131 -121
  8. package/src/assets/design/index.css +3 -3
  9. package/src/assets/design/size.css +37 -35
  10. package/src/assets/design/typography.css +37 -37
  11. package/src/assets/icons/defaultIcons.js +247 -232
  12. package/src/assets/suneditor-contents.css +779 -778
  13. package/src/assets/suneditor.css +43 -35
  14. package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
  15. package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
  16. package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
  17. package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
  18. package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
  19. package/src/core/base/eventManager.js +1550 -1484
  20. package/src/core/base/history.js +355 -355
  21. package/src/core/class/char.js +163 -162
  22. package/src/core/class/component.js +856 -842
  23. package/src/core/class/format.js +3433 -3422
  24. package/src/core/class/html.js +1927 -1890
  25. package/src/core/class/menu.js +357 -346
  26. package/src/core/class/nodeTransform.js +424 -424
  27. package/src/core/class/offset.js +858 -891
  28. package/src/core/class/selection.js +710 -620
  29. package/src/core/class/shortcuts.js +98 -98
  30. package/src/core/class/toolbar.js +438 -430
  31. package/src/core/class/ui.js +424 -422
  32. package/src/core/class/viewer.js +750 -750
  33. package/src/core/editor.js +1810 -1708
  34. package/src/core/section/actives.js +268 -241
  35. package/src/core/section/constructor.js +1348 -1661
  36. package/src/core/section/context.js +102 -102
  37. package/src/core/section/documentType.js +582 -561
  38. package/src/core/section/options.js +367 -0
  39. package/src/core/util/instanceCheck.js +59 -0
  40. package/src/editorInjector/_classes.js +36 -36
  41. package/src/editorInjector/_core.js +92 -92
  42. package/src/editorInjector/index.js +75 -75
  43. package/src/events.js +634 -622
  44. package/src/helper/clipboard.js +59 -59
  45. package/src/helper/converter.js +586 -564
  46. package/src/helper/dom/domCheck.js +304 -304
  47. package/src/helper/dom/domQuery.js +677 -669
  48. package/src/helper/dom/domUtils.js +618 -557
  49. package/src/helper/dom/index.js +12 -12
  50. package/src/helper/env.js +249 -240
  51. package/src/helper/index.js +25 -25
  52. package/src/helper/keyCodeMap.js +183 -183
  53. package/src/helper/numbers.js +72 -72
  54. package/src/helper/unicode.js +47 -47
  55. package/src/langs/ckb.js +231 -231
  56. package/src/langs/cs.js +231 -231
  57. package/src/langs/da.js +231 -231
  58. package/src/langs/de.js +231 -231
  59. package/src/langs/en.js +230 -230
  60. package/src/langs/es.js +231 -231
  61. package/src/langs/fa.js +231 -231
  62. package/src/langs/fr.js +231 -231
  63. package/src/langs/he.js +231 -231
  64. package/src/langs/hu.js +230 -230
  65. package/src/langs/index.js +28 -28
  66. package/src/langs/it.js +231 -231
  67. package/src/langs/ja.js +230 -230
  68. package/src/langs/km.js +230 -230
  69. package/src/langs/ko.js +230 -230
  70. package/src/langs/lv.js +231 -231
  71. package/src/langs/nl.js +231 -231
  72. package/src/langs/pl.js +231 -231
  73. package/src/langs/pt_br.js +231 -231
  74. package/src/langs/ro.js +231 -231
  75. package/src/langs/ru.js +231 -231
  76. package/src/langs/se.js +231 -231
  77. package/src/langs/tr.js +231 -231
  78. package/src/langs/uk.js +231 -231
  79. package/src/langs/ur.js +231 -231
  80. package/src/langs/zh_cn.js +231 -231
  81. package/src/modules/ApiManager.js +191 -191
  82. package/src/modules/Browser.js +669 -667
  83. package/src/modules/ColorPicker.js +364 -362
  84. package/src/modules/Controller.js +474 -454
  85. package/src/modules/Figure.js +1620 -1617
  86. package/src/modules/FileManager.js +359 -359
  87. package/src/modules/HueSlider.js +577 -565
  88. package/src/modules/Modal.js +346 -346
  89. package/src/modules/ModalAnchorEditor.js +643 -643
  90. package/src/modules/SelectMenu.js +549 -549
  91. package/src/modules/_DragHandle.js +17 -17
  92. package/src/modules/index.js +14 -14
  93. package/src/plugins/browser/audioGallery.js +83 -83
  94. package/src/plugins/browser/fileBrowser.js +103 -103
  95. package/src/plugins/browser/fileGallery.js +83 -83
  96. package/src/plugins/browser/imageGallery.js +81 -81
  97. package/src/plugins/browser/videoGallery.js +103 -103
  98. package/src/plugins/command/blockquote.js +61 -60
  99. package/src/plugins/command/exportPDF.js +134 -134
  100. package/src/plugins/command/fileUpload.js +456 -456
  101. package/src/plugins/command/list_bulleted.js +149 -148
  102. package/src/plugins/command/list_numbered.js +152 -151
  103. package/src/plugins/dropdown/align.js +157 -155
  104. package/src/plugins/dropdown/backgroundColor.js +108 -104
  105. package/src/plugins/dropdown/font.js +141 -137
  106. package/src/plugins/dropdown/fontColor.js +109 -105
  107. package/src/plugins/dropdown/formatBlock.js +170 -178
  108. package/src/plugins/dropdown/hr.js +152 -152
  109. package/src/plugins/dropdown/layout.js +83 -83
  110. package/src/plugins/dropdown/lineHeight.js +131 -130
  111. package/src/plugins/dropdown/list.js +123 -122
  112. package/src/plugins/dropdown/paragraphStyle.js +138 -138
  113. package/src/plugins/dropdown/table.js +4110 -4000
  114. package/src/plugins/dropdown/template.js +83 -83
  115. package/src/plugins/dropdown/textStyle.js +149 -149
  116. package/src/plugins/field/mention.js +242 -242
  117. package/src/plugins/index.js +120 -120
  118. package/src/plugins/input/fontSize.js +414 -410
  119. package/src/plugins/input/pageNavigator.js +71 -70
  120. package/src/plugins/modal/audio.js +677 -677
  121. package/src/plugins/modal/drawing.js +537 -531
  122. package/src/plugins/modal/embed.js +886 -886
  123. package/src/plugins/modal/image.js +1377 -1376
  124. package/src/plugins/modal/link.js +248 -240
  125. package/src/plugins/modal/math.js +563 -563
  126. package/src/plugins/modal/video.js +1226 -1226
  127. package/src/plugins/popup/anchor.js +224 -222
  128. package/src/suneditor.js +114 -107
  129. package/src/themes/dark.css +132 -122
  130. package/src/typedef.js +132 -130
  131. package/types/assets/icons/defaultIcons.d.ts +8 -0
  132. package/types/core/base/eventManager.d.ts +29 -4
  133. package/types/core/class/char.d.ts +2 -1
  134. package/types/core/class/component.d.ts +1 -2
  135. package/types/core/class/format.d.ts +8 -1
  136. package/types/core/class/html.d.ts +8 -0
  137. package/types/core/class/menu.d.ts +8 -0
  138. package/types/core/class/offset.d.ts +24 -26
  139. package/types/core/class/selection.d.ts +2 -0
  140. package/types/core/class/toolbar.d.ts +6 -0
  141. package/types/core/class/ui.d.ts +1 -1
  142. package/types/core/editor.d.ts +34 -12
  143. package/types/core/section/constructor.d.ts +5 -638
  144. package/types/core/section/documentType.d.ts +12 -2
  145. package/types/core/section/options.d.ts +740 -0
  146. package/types/core/util/instanceCheck.d.ts +50 -0
  147. package/types/editorInjector/_core.d.ts +5 -5
  148. package/types/editorInjector/index.d.ts +2 -2
  149. package/types/events.d.ts +2 -0
  150. package/types/helper/converter.d.ts +9 -0
  151. package/types/helper/dom/domQuery.d.ts +5 -5
  152. package/types/helper/dom/domUtils.d.ts +8 -0
  153. package/types/helper/env.d.ts +6 -1
  154. package/types/helper/index.d.ts +4 -1
  155. package/types/index.d.ts +122 -120
  156. package/types/langs/_Lang.d.ts +194 -194
  157. package/types/modules/ColorPicker.d.ts +5 -1
  158. package/types/modules/Controller.d.ts +8 -4
  159. package/types/modules/Figure.d.ts +2 -1
  160. package/types/modules/HueSlider.d.ts +4 -1
  161. package/types/modules/SelectMenu.d.ts +1 -1
  162. package/types/plugins/command/blockquote.d.ts +1 -0
  163. package/types/plugins/command/list_bulleted.d.ts +1 -0
  164. package/types/plugins/command/list_numbered.d.ts +1 -0
  165. package/types/plugins/dropdown/align.d.ts +1 -0
  166. package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
  167. package/types/plugins/dropdown/font.d.ts +1 -0
  168. package/types/plugins/dropdown/fontColor.d.ts +1 -0
  169. package/types/plugins/dropdown/formatBlock.d.ts +3 -2
  170. package/types/plugins/dropdown/lineHeight.d.ts +1 -0
  171. package/types/plugins/dropdown/list.d.ts +1 -0
  172. package/types/plugins/dropdown/table.d.ts +6 -0
  173. package/types/plugins/input/fontSize.d.ts +1 -0
  174. package/types/plugins/modal/drawing.d.ts +4 -0
  175. package/types/plugins/modal/link.d.ts +32 -15
  176. package/types/suneditor.d.ts +13 -9
  177. package/types/typedef.d.ts +8 -0
@@ -1,1890 +1,1927 @@
1
- /**
2
- * @fileoverview Char class
3
- */
4
-
5
- import CoreInjector from '../../editorInjector/_core';
6
- import { dom, converter, numbers, unicode, clipboard } from '../../helper';
7
-
8
- const REQUIRED_DATA_ATTRS = 'data-se-[^\\s]+';
9
- const V2_MIG_DATA_ATTRS = '|data-index|data-file-size|data-file-name|data-exp|data-font-size';
10
-
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) {
22
- CoreInjector.call(this, editor);
23
- const options = this.options;
24
-
25
- // members
26
- this.fontSizeUnitRegExp = null;
27
-
28
- this._isAllowedClassName = function (v) {
29
- return this.test(v) ? v : '';
30
- }.bind(options.get('allowedClassName'));
31
- this._allowHTMLComment = null;
32
- this._disallowedStyleNodesRegExp = null;
33
- this._htmlCheckWhitelistRegExp = null;
34
- this._htmlCheckBlacklistRegExp = null;
35
- this._elementWhitelistRegExp = null;
36
- this._elementBlacklistRegExp = null;
37
- /** @type {Object<string, RegExp>} */
38
- this._attributeWhitelist = null;
39
- /** @type {Object<string, RegExp>} */
40
- this._attributeBlacklist = null;
41
- this._attributeWhitelistRegExp = null;
42
- this._attributeBlacklistRegExp = null;
43
- this._cleanStyleTagKeyRegExp = null;
44
- this._cleanStyleRegExpMap = null;
45
- this._textStyleTags = options.get('_textStyleTags');
46
- /** @type {Object<string, *>} */
47
- this._autoStyleify = null;
48
- this.__disallowedTagsRegExp = null;
49
- this.__disallowedTagNameRegExp = null;
50
- this.__allowedTagNameRegExp = null;
51
-
52
- // clean styles
53
- const tagStyles = options.get('tagStyles');
54
- const splitTagStyles = {};
55
- for (const k in tagStyles) {
56
- const s = k.split('|');
57
- for (let i = 0, len = s.length, n; i < len; i++) {
58
- n = s[i];
59
- if (!splitTagStyles[n]) splitTagStyles[n] = '';
60
- else splitTagStyles[n] += '|';
61
- splitTagStyles[n] += tagStyles[k];
62
- }
63
- }
64
- for (const k in splitTagStyles) {
65
- splitTagStyles[k] = new RegExp(`\\s*[^-a-zA-Z](${splitTagStyles[k]})\\s*:[^;]+(?!;)*`, 'gi');
66
- }
67
-
68
- const stylesMap = new Map();
69
- const stylesObj = {
70
- ...splitTagStyles,
71
- line: options.get('_lineStylesRegExp')
72
- };
73
- this._textStyleTags.forEach((v) => {
74
- stylesObj[v] = options.get('_textStylesRegExp');
75
- });
76
-
77
- for (const key in stylesObj) {
78
- stylesMap.set(new RegExp(`^(${key})$`), stylesObj[key]);
79
- }
80
- this._cleanStyleTagKeyRegExp = new RegExp(`^(${Object.keys(stylesObj).join('|')})$`, 'i');
81
- this._cleanStyleRegExpMap = stylesMap;
82
-
83
- // font size unit
84
- this.fontSizeUnitRegExp = new RegExp('\\d+(' + options.get('fontSizeUnits').join('|') + ')$', 'i');
85
-
86
- // extra tags
87
- const allowedExtraTags = options.get('_allowedExtraTag');
88
- const disallowedExtraTags = options.get('_disallowedExtraTag');
89
- this.__disallowedTagsRegExp = new RegExp(`<(${disallowedExtraTags})[^>]*>([\\s\\S]*?)<\\/\\1>|<(${disallowedExtraTags})[^>]*\\/?>`, 'gi');
90
- this.__disallowedTagNameRegExp = new RegExp(`^(${disallowedExtraTags})$`, 'i');
91
- this.__allowedTagNameRegExp = new RegExp(`^(${allowedExtraTags})$`, 'i');
92
-
93
- // set disallow text nodes
94
- const disallowStyleNodes = Object.keys(options.get('_defaultStyleTagMap'));
95
- const allowStyleNodes = !options.get('elementWhitelist')
96
- ? []
97
- : options
98
- .get('elementWhitelist')
99
- .split('|')
100
- .filter((v) => /b|i|ins|s|strike/i.test(v));
101
- for (let i = 0; i < allowStyleNodes.length; i++) {
102
- disallowStyleNodes.splice(disallowStyleNodes.indexOf(allowStyleNodes[i].toLowerCase()), 1);
103
- }
104
- this._disallowedStyleNodesRegExp = disallowStyleNodes.length === 0 ? null : new RegExp('(<\\/?)(' + disallowStyleNodes.join('|') + ')\\b\\s*([^>^<]+)?\\s*(?=>)', 'gi');
105
-
106
- // whitelist
107
- // tags
108
- const defaultAttr = options.get('__defaultAttributeWhitelist');
109
- this._allowHTMLComment = options.get('_editorElementWhitelist').includes('//') || options.get('_editorElementWhitelist') === '*';
110
- // html check
111
- this._htmlCheckWhitelistRegExp = new RegExp('^(' + GetRegList(options.get('_editorElementWhitelist').replace('|//', ''), '') + ')$', 'i');
112
- this._htmlCheckBlacklistRegExp = new RegExp('^(' + (options.get('elementBlacklist') || '^') + ')$', 'i');
113
- // elements
114
- this._elementWhitelistRegExp = converter.createElementWhitelist(GetRegList(options.get('_editorElementWhitelist').replace('|//', '|<!--|-->'), ''));
115
- this._elementBlacklistRegExp = converter.createElementBlacklist(options.get('elementBlacklist').replace('|//', '|<!--|-->'));
116
- // attributes
117
- const regEndStr = '\\s*=\\s*(")[^"]*\\1';
118
- const _wAttr = options.get('attributeWhitelist');
119
-
120
- /** @type {Object<string, RegExp>} */
121
- let tagsAttr = {};
122
- let allAttr = '';
123
- if (_wAttr) {
124
- for (const k in _wAttr) {
125
- if (/^on[a-z]+$/i.test(_wAttr[k])) continue;
126
- if (k === '*') {
127
- allAttr = GetRegList(_wAttr[k], defaultAttr);
128
- } else {
129
- tagsAttr[k] = new RegExp('\\s(?:' + GetRegList(_wAttr[k], '') + ')' + regEndStr, 'ig');
130
- }
131
- }
132
- }
133
-
134
- this._attributeWhitelistRegExp = new RegExp('\\s(?:' + (allAttr || defaultAttr) + '|' + REQUIRED_DATA_ATTRS + (options.get('v2Migration') ? V2_MIG_DATA_ATTRS : '') + ')' + regEndStr, 'ig');
135
- this._attributeWhitelist = tagsAttr;
136
-
137
- // blacklist
138
- const _bAttr = options.get('attributeBlacklist');
139
- tagsAttr = {};
140
- allAttr = '';
141
- if (_bAttr) {
142
- for (const k in _bAttr) {
143
- if (k === '*') {
144
- allAttr = GetRegList(_bAttr[k], '');
145
- } else {
146
- tagsAttr[k] = new RegExp('\\s(?:' + GetRegList(_bAttr[k], '') + ')' + regEndStr, 'ig');
147
- }
148
- }
149
- }
150
-
151
- this._attributeBlacklistRegExp = new RegExp('\\s(?:' + (allAttr || '^') + ')' + regEndStr, 'ig');
152
- this._attributeBlacklist = tagsAttr;
153
-
154
- // autoStyleify
155
- if (options.get('autoStyleify').length > 0) {
156
- const convertTextTags = options.get('convertTextTags');
157
- const styleToTag = {};
158
- options.get('autoStyleify').forEach((style) => {
159
- switch (style) {
160
- case 'bold':
161
- styleToTag.bold = { regex: /font-weight\s*:\s*bold/i, tag: convertTextTags.bold };
162
- break;
163
- case 'italic':
164
- styleToTag.italic = { regex: /font-style\s*:\s*italic/i, tag: convertTextTags.italic };
165
- break;
166
- case 'underline':
167
- styleToTag.underline = { regex: /text-decoration\s*:\s*underline/i, tag: convertTextTags.underline };
168
- break;
169
- case 'strike':
170
- styleToTag.strike = { regex: /text-decoration\s*:\s*line-through/i, tag: convertTextTags.strike };
171
- break;
172
- }
173
- });
174
- this._autoStyleify = styleToTag;
175
- }
176
- }
177
-
178
- HTML.prototype = {
179
- /**
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.
194
- */
195
- filter(html, { tagWhitelist, tagBlacklist, validate, validateAll }) {
196
- if (tagWhitelist) {
197
- html = html.replace(converter.createElementWhitelist(tagWhitelist), '');
198
- }
199
- if (tagBlacklist) {
200
- html = html.replace(converter.createElementBlacklist(tagBlacklist), '');
201
- }
202
- if (validate) {
203
- const parseDocument = new DOMParser().parseFromString(html, 'text/html');
204
- parseDocument.body.querySelectorAll('*').forEach((node) => {
205
- if (!node.closest('.se-component') && !node.closest('.se-flex-component')) {
206
- const result = validate(node);
207
- if (result === null) {
208
- node.remove();
209
- } else if (result instanceof Node) {
210
- node.replaceWith(result);
211
- } else if (typeof result === 'string') {
212
- node.outerHTML = result;
213
- }
214
- }
215
- });
216
- html = parseDocument.body.innerHTML;
217
- } else if (validateAll) {
218
- const parseDocument = new DOMParser().parseFromString(html, 'text/html');
219
- const compClass = ['.se-component', '.se-flex-component'];
220
- const closestAny = (element) => compClass.some((selector) => element.closest(selector));
221
- parseDocument.body.querySelectorAll('*').forEach((node) => {
222
- if (!closestAny(node)) {
223
- const result = validate(node);
224
- if (result === null) {
225
- node.remove();
226
- } else if (result instanceof Node) {
227
- node.replaceWith(result);
228
- } else if (typeof result === 'string') {
229
- node.outerHTML = result;
230
- }
231
- }
232
- });
233
- html = parseDocument.body.innerHTML;
234
- }
235
-
236
- return html;
237
- },
238
-
239
- /**
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
251
- */
252
- clean(html, { forceFormat, whitelist, blacklist, _freeCodeViewMode } = {}) {
253
- const { tagFilter, formatFilter, classFilter, styleNodeFilter, attrFilter, styleFilter } = this.options.get('strictMode');
254
- let cleanData = '';
255
-
256
- html = this.compress(html);
257
-
258
- if (tagFilter) {
259
- html = html.replace(this.__disallowedTagsRegExp, '');
260
- html = this._deleteDisallowedTags(html, this._elementWhitelistRegExp, this._elementBlacklistRegExp).replace(/<br\/?>$/i, '');
261
- }
262
-
263
- if (this._autoStyleify) {
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;
267
- }
268
-
269
- if (attrFilter || styleFilter) {
270
- html = html.replace(/(<[a-zA-Z0-9-]+)[^>]*(?=>)/g, CleanElements.bind(this, attrFilter, styleFilter));
271
- }
272
-
273
- // get dom tree
274
- const domParser = this._d.createRange().createContextualFragment(html);
275
-
276
- if (tagFilter) {
277
- try {
278
- this._consistencyCheckOfHTML(domParser, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, tagFilter, formatFilter, classFilter, _freeCodeViewMode);
279
- } catch (error) {
280
- console.warn('[SUNEDITOR.html.clean.fail]', error.message);
281
- }
282
- }
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
-
298
- if (this.options.get('__pluginRetainFilter')) {
299
- this.editor._MELInfo.forEach((method, query) => {
300
- const infoLst = domParser.querySelectorAll(query);
301
- for (let i = 0, len = infoLst.length; i < len; i++) {
302
- method(infoLst[i]);
303
- }
304
- });
305
- }
306
-
307
- if (formatFilter) {
308
- let domTree = domParser.childNodes;
309
- if (!forceFormat) forceFormat = this._isFormatData(domTree);
310
- if (forceFormat) domTree = this._editFormat(domParser).childNodes;
311
-
312
- for (let i = 0, len = domTree.length, t; i < len; i++) {
313
- t = domTree[i];
314
- if (this.__allowedTagNameRegExp.test(t.nodeName)) {
315
- cleanData += /** @type {HTMLElement} */ (t).outerHTML;
316
- continue;
317
- }
318
- cleanData += this._makeLine(t, forceFormat);
319
- }
320
- }
321
-
322
- // set clean data
323
- if (!cleanData) cleanData = html;
324
-
325
- // whitelist, blacklist
326
- if (tagFilter) {
327
- if (whitelist) cleanData = cleanData.replace(typeof whitelist === 'string' ? converter.createElementWhitelist(whitelist) : whitelist, '');
328
- if (blacklist) cleanData = cleanData.replace(typeof blacklist === 'string' ? converter.createElementBlacklist(blacklist) : blacklist, '');
329
- }
330
-
331
- if (styleNodeFilter) {
332
- cleanData = this._styleNodeConvertor(cleanData);
333
- }
334
-
335
- return cleanData;
336
- },
337
-
338
- /**
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
348
- */
349
- insert(html, { selectInserted, skipCharCount, skipCleaning } = {}) {
350
- if (!this.editor.frameContext.get('wysiwyg').contains(this.selection.get().focusNode)) this.editor.focus();
351
-
352
- if (typeof html === 'string') {
353
- if (!skipCleaning) html = this.clean(html, { forceFormat: false, whitelist: null, blacklist: null });
354
- try {
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;
358
- if (this._isFormatData(domTree)) html = this._convertListCell(domTree);
359
- }
360
-
361
- const domParser = this._d.createRange().createContextualFragment(html);
362
- const domTree = domParser.childNodes;
363
-
364
- if (!skipCharCount) {
365
- const type = this.editor.frameOptions.get('charCounter_type') === 'byte-html' ? 'outerHTML' : 'textContent';
366
- let checkHTML = '';
367
- for (let i = 0, len = domTree.length; i < len; i++) {
368
- checkHTML += domTree[i][type];
369
- }
370
- if (!this.char.check(checkHTML)) return;
371
- }
372
-
373
- let c, a, t, prev, firstCon;
374
- while ((c = domTree[0])) {
375
- if (prev?.nodeType === 3 && a?.nodeType === 1 && dom.check.isBreak(c)) {
376
- prev = c;
377
- dom.utils.removeItem(c);
378
- continue;
379
- }
380
- t = this.insertNode(c, { afterNode: a, skipCharCount: true });
381
- a = t.container || t;
382
- if (!firstCon) firstCon = t;
383
- prev = c;
384
- }
385
-
386
- if (prev?.nodeType === 3 && a?.nodeType === 1) a = prev;
387
- const offset = a.nodeType === 3 ? t.endOffset || a.textContent.length : a.childNodes.length;
388
-
389
- if (selectInserted) {
390
- this.selection.setRange(firstCon.container || firstCon, firstCon.startOffset || 0, a, offset);
391
- } else if (!this.component.is(a)) {
392
- this.selection.setRange(a, offset, a, offset);
393
- }
394
- } catch (error) {
395
- if (this.editor.frameContext.get('isReadOnly') || this.editor.frameContext.get('isDisabled')) return;
396
- throw Error(`[SUNEDITOR.html.insert.error] ${error.message}`);
397
- }
398
- } else {
399
- if (this.component.is(html)) {
400
- this.component.insert(html, { skipCharCount, skipSelection: false, skipHistory: false });
401
- } else {
402
- let afterNode = null;
403
- if (this.format.isLine(html) || dom.check.isMedia(html)) {
404
- afterNode = this.format.getLine(this.selection.getNode(), null);
405
- }
406
- this.insertNode(html, { afterNode, skipCharCount });
407
- }
408
- }
409
-
410
- this.editor.effectNode = null;
411
- this.editor.focus();
412
- this.history.push(false);
413
- },
414
-
415
- /**
416
- * @this {HTMLThis}
417
- * @description Delete selected node and insert argument value node and return.
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 }".
420
- * @param {Node} oNode Node to be inserted
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.
424
- * @returns {Object|Node|null}
425
- */
426
- insertNode(oNode, { afterNode, skipCharCount } = {}) {
427
- let result = null;
428
- if (this.editor.frameContext.get('isReadOnly') || (!skipCharCount && !this.char.check(oNode))) {
429
- return result;
430
- }
431
-
432
- let fNode = null;
433
- let range = this.selection.getRange();
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));
436
-
437
- let parentNode,
438
- originAfter,
439
- tempAfterNode,
440
- tempParentNode = null;
441
- const freeFormat = this.format.isBrLine(line);
442
- const isFormats = (!freeFormat && (this.format.isLine(oNode) || this.format.isBlock(oNode))) || this.component.isBasic(oNode);
443
-
444
- if (insertListCell) {
445
- tempAfterNode = afterNode || dom.check.isList(oNode) ? line.lastChild : line.nextElementSibling;
446
- tempParentNode = dom.check.isList(oNode) ? line : (tempAfterNode || line).parentNode;
447
- }
448
-
449
- if (!afterNode && (isFormats || this.component.isBasic(oNode) || dom.check.isMedia(oNode))) {
450
- const isEdge = dom.check.isEdgePoint(range.endContainer, range.endOffset, 'end');
451
- const r = this.remove();
452
- const container = r.container;
453
- const prevContainer = container === r.prevContainer && range.collapsed ? null : r.prevContainer;
454
-
455
- if (insertListCell && prevContainer) {
456
- tempParentNode = prevContainer.nodeType === 3 ? prevContainer.parentNode : prevContainer;
457
- if (tempParentNode.contains(container)) {
458
- let sameParent = true;
459
- tempAfterNode = container;
460
- while (tempAfterNode.parentNode && tempAfterNode.parentNode !== tempParentNode) {
461
- tempAfterNode = tempAfterNode.parentNode;
462
- sameParent = false;
463
- }
464
- if (sameParent && container === prevContainer) tempAfterNode = tempAfterNode.nextSibling;
465
- } else {
466
- tempAfterNode = null;
467
- }
468
- } else if (insertListCell && dom.check.isListCell(container) && !line.parentElement) {
469
- line = dom.utils.createElement('LI');
470
- tempParentNode.appendChild(line);
471
- container.appendChild(tempParentNode);
472
- tempAfterNode = null;
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);
476
- });
477
- afterNode = this.nodeTransform.split(container, r.offset, !depthFormat ? 0 : dom.query.getNodeDepth(depthFormat) + 1);
478
- if (!afterNode) {
479
- tempAfterNode = afterNode = line;
480
- } else if (insertListCell) {
481
- if (line.contains(container)) {
482
- const subList = dom.check.isList(line.lastElementChild);
483
- let newCell = null;
484
- if (!isEdge) {
485
- newCell = line.cloneNode(false);
486
- newCell.appendChild(afterNode.textContent.trim() ? afterNode : dom.utils.createTextNode(unicode.zeroWidthSpace));
487
- }
488
- if (subList) {
489
- if (!newCell) {
490
- newCell = line.cloneNode(false);
491
- newCell.appendChild(dom.utils.createTextNode(unicode.zeroWidthSpace));
492
- }
493
- newCell.appendChild(line.lastElementChild);
494
- }
495
- if (newCell) {
496
- line.parentNode.insertBefore(newCell, line.nextElementSibling);
497
- tempAfterNode = afterNode = newCell;
498
- }
499
- }
500
- } else {
501
- afterNode = afterNode.previousSibling;
502
- }
503
- }
504
- }
505
-
506
- range = !afterNode && !isFormats ? this.selection.getRangeAndAddLine(this.selection.getRange(), null) : this.selection.getRange();
507
- const commonCon = range.commonAncestorContainer;
508
- const startOff = range.startOffset;
509
- const endOff = range.endOffset;
510
- const formatRange = range.startContainer === commonCon && this.format.isLine(commonCon);
511
- const startCon = formatRange ? commonCon.childNodes[startOff] || commonCon.childNodes[0] || range.startContainer : range.startContainer;
512
- const endCon = formatRange ? commonCon.childNodes[endOff] || commonCon.childNodes[commonCon.childNodes.length - 1] || range.endContainer : range.endContainer;
513
-
514
- if (!insertListCell) {
515
- if (!afterNode) {
516
- parentNode = startCon;
517
- if (startCon.nodeType === 3) {
518
- parentNode = startCon.parentNode;
519
- }
520
-
521
- /** No Select range node */
522
- if (range.collapsed) {
523
- if (commonCon.nodeType === 3) {
524
- if (commonCon.textContent.length > endOff) afterNode = /** @type {Text} */ (commonCon).splitText(endOff);
525
- else afterNode = commonCon.nextSibling;
526
- } else {
527
- if (!dom.check.isBreak(parentNode)) {
528
- const c = parentNode.childNodes[startOff];
529
- const focusNode = c?.nodeType === 3 && dom.check.isZeroWidth(c) && dom.check.isBreak(c.nextSibling) ? c.nextSibling : c;
530
- if (focusNode) {
531
- if (!focusNode.nextSibling && dom.check.isBreak(focusNode)) {
532
- parentNode.removeChild(focusNode);
533
- afterNode = null;
534
- } else {
535
- afterNode = dom.check.isBreak(focusNode) && !dom.check.isBreak(oNode) ? focusNode : focusNode.nextSibling;
536
- }
537
- } else {
538
- afterNode = null;
539
- }
540
- } else {
541
- afterNode = parentNode;
542
- parentNode = parentNode.parentNode;
543
- }
544
- }
545
- } else {
546
- /** Select range nodes */
547
- const isSameContainer = startCon === endCon;
548
-
549
- if (isSameContainer) {
550
- if (dom.check.isEdgePoint(endCon, endOff)) afterNode = endCon.nextSibling;
551
- else afterNode = /** @type {Text} */ (endCon).splitText(endOff);
552
-
553
- let removeNode = startCon;
554
- if (!dom.check.isEdgePoint(startCon, startOff)) removeNode = /** @type {Text} */ (startCon).splitText(startOff);
555
-
556
- parentNode.removeChild(removeNode);
557
- if (parentNode.childNodes.length === 0 && isFormats) {
558
- /** @type {HTMLElement} */ (parentNode).innerHTML = '<br>';
559
- }
560
- } else {
561
- const removedTag = this.remove();
562
- const container = removedTag.container;
563
- const prevContainer = removedTag.prevContainer;
564
-
565
- if (container?.childNodes.length === 0 && isFormats) {
566
- if (this.format.isLine(container)) {
567
- container.innerHTML = '<br>';
568
- } else if (this.format.isBlock(container)) {
569
- container.innerHTML = '<' + this.options.get('defaultLine') + '><br></' + this.options.get('defaultLine') + '>';
570
- }
571
- }
572
-
573
- if (dom.check.isListCell(container) && oNode.nodeType === 3) {
574
- parentNode = container;
575
- afterNode = null;
576
- } else if (!isFormats && prevContainer) {
577
- parentNode = prevContainer.nodeType === 3 ? prevContainer.parentNode : prevContainer;
578
- if (parentNode.contains(container)) {
579
- let sameParent = true;
580
- afterNode = container;
581
- while (afterNode.parentNode && afterNode.parentNode !== parentNode) {
582
- afterNode = afterNode.parentNode;
583
- sameParent = false;
584
- }
585
- if (sameParent && container === prevContainer) afterNode = afterNode.nextSibling;
586
- } else {
587
- afterNode = null;
588
- }
589
- } else if (dom.check.isWysiwygFrame(container) && !this.format.isLine(oNode)) {
590
- parentNode = container.appendChild(dom.utils.createElement(this.options.get('defaultLine')));
591
- afterNode = null;
592
- } else {
593
- afterNode = isFormats ? endCon : container === prevContainer ? container.nextSibling : container;
594
- parentNode = !afterNode || !afterNode.parentNode ? commonCon : afterNode.parentNode;
595
- }
596
-
597
- while (afterNode && !this.format.isLine(afterNode) && afterNode.parentNode !== commonCon) {
598
- afterNode = afterNode.parentNode;
599
- }
600
- }
601
- }
602
- } else {
603
- // has afterNode
604
- parentNode = afterNode.parentNode;
605
- afterNode = afterNode.nextSibling;
606
- originAfter = true;
607
- }
608
- }
609
-
610
- try {
611
- // set node
612
- const wysiwyg = this.editor.frameContext.get('wysiwyg');
613
- if (!insertListCell) {
614
- if (dom.check.isWysiwygFrame(afterNode) || parentNode === wysiwyg.parentNode) {
615
- parentNode = wysiwyg;
616
- afterNode = null;
617
- }
618
-
619
- if (this.format.isLine(oNode) || this.format.isBlock(oNode) || (!dom.check.isListCell(parentNode) && this.component.isBasic(oNode))) {
620
- const oldParent = parentNode;
621
- if (dom.check.isList(afterNode)) {
622
- parentNode = afterNode;
623
- afterNode = null;
624
- } else if (dom.check.isListCell(afterNode)) {
625
- parentNode = afterNode.previousElementSibling || afterNode;
626
- } else if (!originAfter && !afterNode) {
627
- const r = this.remove();
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);
630
- parentNode = rangeCon ? container : container.parentNode;
631
- afterNode = rangeCon ? null : container.nextSibling;
632
- }
633
-
634
- if (oldParent.childNodes.length === 0 && parentNode !== oldParent) dom.utils.removeItem(oldParent);
635
- }
636
-
637
- if (isFormats && !freeFormat && !this.format.isBlock(parentNode) && !dom.check.isListCell(parentNode) && !dom.check.isWysiwygFrame(parentNode)) {
638
- afterNode = parentNode.nextElementSibling;
639
- parentNode = parentNode.parentNode;
640
- }
641
-
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;
646
- }
647
- }
648
-
649
- // insert--
650
- if (insertListCell) {
651
- if (!tempParentNode.parentNode) {
652
- parentNode = wysiwyg;
653
- afterNode = null;
654
- } else {
655
- parentNode = tempParentNode;
656
- afterNode = tempAfterNode;
657
- }
658
- } else {
659
- afterNode = parentNode === afterNode ? parentNode.lastChild : afterNode;
660
- }
661
-
662
- if (dom.check.isListCell(oNode) && !dom.check.isList(parentNode)) {
663
- if (dom.check.isListCell(parentNode)) {
664
- afterNode = parentNode.nextElementSibling;
665
- parentNode = parentNode.parentNode;
666
- } else {
667
- const ul = dom.utils.createElement('ol');
668
- parentNode.insertBefore(ul, afterNode);
669
- parentNode = ul;
670
- afterNode = null;
671
- }
672
- insertListCell = true;
673
- }
674
-
675
- this._checkDuplicateNode(oNode, parentNode);
676
- parentNode.insertBefore(oNode, afterNode);
677
-
678
- if (insertListCell) {
679
- if (dom.check.isZeroWidth(line.textContent.trim())) {
680
- dom.utils.removeItem(line);
681
- oNode = oNode.lastChild;
682
- } else {
683
- const chList = dom.utils.arrayFind(line.children, dom.check.isList);
684
- if (chList) {
685
- if (oNode !== chList) {
686
- oNode.appendChild(chList);
687
- oNode = chList.previousSibling;
688
- } else {
689
- parentNode.appendChild(oNode);
690
- oNode = parentNode;
691
- }
692
-
693
- if (dom.check.isZeroWidth(line.textContent.trim())) {
694
- dom.utils.removeItem(line);
695
- }
696
- }
697
- }
698
- }
699
- } catch (error) {
700
- parentNode.appendChild(oNode);
701
- console.warn('[SUNEDITOR.html.insertNode.warn]', error);
702
- } finally {
703
- if (fNode) oNode = fNode;
704
-
705
- const dupleNodes = parentNode.querySelectorAll('[data-duple]');
706
- if (dupleNodes.length > 0) {
707
- for (let i = 0, len = dupleNodes.length, d, c, ch, parent; i < len; i++) {
708
- d = dupleNodes[i];
709
- ch = d.childNodes;
710
- parent = d.parentNode;
711
-
712
- while (ch[0]) {
713
- c = ch[0];
714
- parent.insertBefore(c, d);
715
- }
716
-
717
- if (d === oNode) oNode = c;
718
- dom.utils.removeItem(d);
719
- }
720
- }
721
-
722
- if ((this.format.isLine(oNode) || this.component.isBasic(oNode)) && startCon === endCon) {
723
- const cItem = this.format.getLine(commonCon, null);
724
- if (cItem?.nodeType === 1 && dom.check.isEmptyLine(cItem)) {
725
- dom.utils.removeItem(cItem);
726
- }
727
- }
728
-
729
- if (freeFormat && (this.format.isLine(oNode) || this.format.isBlock(oNode))) {
730
- oNode = this._setIntoFreeFormat(oNode);
731
- }
732
-
733
- if (!this.component.isBasic(oNode)) {
734
- let offset = 1;
735
- if (oNode.nodeType === 3) {
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)) {
739
- let zeroWidth = null;
740
- if (!oNode.previousSibling || dom.check.isBreak(oNode.previousSibling)) {
741
- zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
742
- oNode.parentNode.insertBefore(zeroWidth, oNode);
743
- }
744
-
745
- if (!oNode.nextSibling || dom.check.isBreak(oNode.nextSibling)) {
746
- zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
747
- oNode.parentNode.insertBefore(zeroWidth, oNode.nextSibling);
748
- }
749
-
750
- if (this.format._isIgnoreNodeChange(oNode)) {
751
- oNode = oNode.nextSibling;
752
- offset = 0;
753
- }
754
-
755
- this.selection.setRange(oNode, offset, oNode, offset);
756
- }
757
- }
758
-
759
- result = oNode;
760
- }
761
-
762
- return result;
763
- },
764
-
765
- /**
766
- * @this {HTMLThis}
767
- * @description Delete the selected range.
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"
773
- */
774
- remove() {
775
- this.selection._resetRangeToTextNode();
776
-
777
- const range = this.selection.getRange();
778
- const isStartEdge = range.startOffset === 0;
779
- const isEndEdge = dom.check.isEdgePoint(range.endContainer, range.endOffset, 'end');
780
- let prevContainer = null;
781
- let startPrevEl = null;
782
- let endNextEl = null;
783
- if (isStartEdge) {
784
- startPrevEl = this.format.getLine(range.startContainer);
785
- prevContainer = startPrevEl ? startPrevEl.previousElementSibling : null;
786
- startPrevEl = startPrevEl ? prevContainer : startPrevEl;
787
- }
788
- if (isEndEdge) {
789
- endNextEl = this.format.getLine(range.endContainer);
790
- endNextEl = endNextEl ? endNextEl.nextElementSibling : endNextEl;
791
- }
792
-
793
- let container,
794
- offset = 0;
795
- let startCon = range.startContainer;
796
- let endCon = range.endContainer;
797
- let startOff = range.startOffset;
798
- let endOff = range.endOffset;
799
- const commonCon = /** @type {HTMLElement} */ (range.commonAncestorContainer.nodeType === 3 && range.commonAncestorContainer.parentNode === startCon.parentNode ? startCon.parentNode : range.commonAncestorContainer);
800
- if (commonCon === startCon && commonCon === endCon) {
801
- if (this.component.isBasic(commonCon)) {
802
- const compInfo = this.component.get(commonCon);
803
- const compContainer = compInfo.container;
804
- const parent = compContainer.parentElement;
805
-
806
- const next = compContainer.nextSibling || compContainer.previousSibling;
807
- const nextOffset = next === compContainer.previousSibling ? next?.textContent?.length || 1 : 0;
808
- const parentNext = parent.nextElementSibling || parent.previousElementSibling;
809
- const parentNextOffset = parentNext === parent.previousElementSibling ? parentNext?.textContent?.length || 1 : 0;
810
-
811
- dom.utils.removeItem(compContainer);
812
-
813
- if (this.format.isLine(parent)) {
814
- if (parent.childNodes.length === 0) {
815
- dom.utils.removeItem(parent);
816
- return {
817
- container: parentNext,
818
- offset: parentNextOffset,
819
- commonCon
820
- };
821
- } else {
822
- return {
823
- container: next,
824
- offset: nextOffset,
825
- commonCon
826
- };
827
- }
828
- } else {
829
- return {
830
- container: parentNext,
831
- offset: parentNextOffset,
832
- commonCon
833
- };
834
- }
835
- } else {
836
- if ((commonCon.nodeType === 1 && startOff === 0 && endOff === 1) || (commonCon.nodeType === 3 && startOff === 0 && endOff === commonCon.textContent.length)) {
837
- const nextEl = dom.query.getNextDeepestNode(commonCon, this.editor.frameContext.get('wysiwyg'));
838
- const prevEl = dom.query.getPreviousDeepestNode(commonCon, this.editor.frameContext.get('wysiwyg'));
839
- const line = this.format.getLine(commonCon);
840
- dom.utils.removeItem(commonCon);
841
-
842
- let rEl = nextEl || prevEl;
843
- let rOffset = nextEl ? 0 : rEl?.nodeType === 3 ? rEl.textContent.length : 1;
844
-
845
- const npEl = this.format.getLine(rEl) || this.component.get(rEl);
846
- if (line !== npEl) {
847
- rEl = /** @type {Node} */ (npEl);
848
- rOffset = rOffset === 0 ? 0 : 1;
849
- }
850
-
851
- if (dom.check.isZeroWidth(line) && !line.contains(rEl)) {
852
- dom.utils.removeItem(line);
853
- }
854
-
855
- return {
856
- container: rEl,
857
- offset: rOffset,
858
- commonCon
859
- };
860
- }
861
-
862
- startCon = commonCon.children[startOff];
863
- endCon = commonCon.children[endOff];
864
- startOff = endOff = 0;
865
- }
866
- }
867
-
868
- if (!startCon || !endCon)
869
- return {
870
- container: commonCon,
871
- offset: 0,
872
- commonCon
873
- };
874
-
875
- if (startCon === endCon && range.collapsed) {
876
- if (dom.check.isZeroWidth(startCon.textContent?.substring(startOff))) {
877
- return {
878
- container: startCon,
879
- offset: startOff,
880
- prevContainer: startCon && startCon.parentNode ? startCon : null,
881
- commonCon
882
- };
883
- }
884
- }
885
-
886
- let beforeNode = null;
887
- let afterNode = null;
888
-
889
- const childNodes = dom.query.getListChildNodes(commonCon, null);
890
- let startIndex = dom.utils.getArrayIndex(childNodes, startCon);
891
- let endIndex = dom.utils.getArrayIndex(childNodes, endCon);
892
-
893
- if (childNodes.length > 0 && startIndex > -1 && endIndex > -1) {
894
- for (let i = startIndex + 1, startNode = startCon; i >= 0; i--) {
895
- if (childNodes[i] === startNode.parentNode && childNodes[i].firstChild === startNode && startOff === 0) {
896
- startIndex = i;
897
- startNode = startNode.parentNode;
898
- }
899
- }
900
-
901
- for (let i = endIndex - 1, endNode = endCon; i > startIndex; i--) {
902
- if (childNodes[i] === endNode.parentNode && childNodes[i].nodeType === 1) {
903
- childNodes.splice(i, 1);
904
- endNode = endNode.parentNode;
905
- --endIndex;
906
- }
907
- }
908
- } else {
909
- if (childNodes.length === 0) {
910
- if (this.format.isLine(commonCon) || this.format.isBlock(commonCon) || dom.check.isWysiwygFrame(commonCon) || dom.check.isBreak(commonCon) || dom.check.isMedia(commonCon)) {
911
- return {
912
- container: commonCon,
913
- offset: 0,
914
- commonCon
915
- };
916
- } else if (dom.check.isText(commonCon)) {
917
- return {
918
- container: commonCon,
919
- offset: endOff,
920
- commonCon
921
- };
922
- }
923
- childNodes.push(commonCon);
924
- startCon = endCon = commonCon;
925
- } else {
926
- startCon = endCon = childNodes[0];
927
- if (dom.check.isBreak(startCon) || dom.check.isZeroWidth(startCon)) {
928
- return {
929
- container: dom.check.isMedia(commonCon) ? commonCon : startCon,
930
- offset: 0,
931
- commonCon
932
- };
933
- }
934
- }
935
-
936
- startIndex = endIndex = 0;
937
- }
938
-
939
- const _isText = dom.check.isText;
940
- const _isElement = dom.check.isElement;
941
- for (let i = startIndex; i <= endIndex; i++) {
942
- const item = /** @type {Text} */ (childNodes[i]);
943
-
944
- if (_isText(item) && (item.data === undefined || item.length === 0)) {
945
- this._nodeRemoveListItem(item);
946
- continue;
947
- }
948
-
949
- if (item === startCon) {
950
- if (_isElement(startCon)) {
951
- if (this.component.is(startCon)) continue;
952
- else beforeNode = dom.utils.createTextNode(startCon.textContent);
953
- } else {
954
- const sc = /** @type {Text} */ (startCon);
955
- const ec = /** @type {Text} */ (endCon);
956
- if (item === endCon) {
957
- beforeNode = dom.utils.createTextNode(sc.substringData(0, startOff) + ec.substringData(endOff, ec.length - endOff));
958
- offset = startOff;
959
- } else {
960
- beforeNode = dom.utils.createTextNode(sc.substringData(0, startOff));
961
- }
962
- }
963
-
964
- if (beforeNode.length > 0) {
965
- /** @type {Text} */ (startCon).data = beforeNode.data;
966
- } else {
967
- this._nodeRemoveListItem(startCon);
968
- }
969
-
970
- if (item === endCon) break;
971
- continue;
972
- }
973
-
974
- if (item === endCon) {
975
- if (_isText(endCon)) {
976
- afterNode = dom.utils.createTextNode(endCon.substringData(endOff, endCon.length - endOff));
977
- } else {
978
- if (this.component.is(endCon)) continue;
979
- else afterNode = dom.utils.createTextNode(endCon.textContent);
980
- }
981
-
982
- if (afterNode.length > 0) {
983
- /** @type {Text} */ (endCon).data = afterNode.data;
984
- } else {
985
- this._nodeRemoveListItem(endCon);
986
- }
987
-
988
- continue;
989
- }
990
-
991
- this._nodeRemoveListItem(item);
992
- }
993
-
994
- const endUl = dom.query.getParentElement(endCon, 'ul');
995
- const startLi = dom.query.getParentElement(startCon, 'li');
996
- if (endUl && startLi && startLi.contains(endUl)) {
997
- container = endUl.previousSibling;
998
- offset = container.textContent.length;
999
- } else {
1000
- container = endCon && endCon.parentNode ? endCon : startCon && startCon.parentNode ? startCon : range.endContainer || range.startContainer;
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
- }
1013
- }
1014
-
1015
- if (!this.format.getLine(container) && !(startCon && startCon.parentNode)) {
1016
- if (endNextEl) {
1017
- container = endNextEl;
1018
- offset = 0;
1019
- } else if (startPrevEl) {
1020
- container = startPrevEl;
1021
- offset = 1;
1022
- }
1023
- }
1024
-
1025
- if (!dom.check.isWysiwygFrame(container) && container.childNodes.length === 0) {
1026
- const rc = this.nodeTransform.removeAllParents(container, null, null);
1027
- if (rc) container = rc.sc || rc.ec || this.editor.frameContext.get('wysiwyg');
1028
- }
1029
-
1030
- // set range
1031
- this.selection.setRange(container, offset, container, offset);
1032
-
1033
- return {
1034
- container,
1035
- offset,
1036
- prevContainer,
1037
- commonCon
1038
- };
1039
- },
1040
-
1041
- /**
1042
- * @this {HTMLThis}
1043
- * @description Gets the current content
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>).
1046
- * Ignored for targetOptions.get('iframe_fullPage') is true.
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>}
1050
- */
1051
- get({ withFrame, includeFullPage, rootKey } = {}) {
1052
- if (!rootKey) rootKey = [this.status.rootKey];
1053
- else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1054
-
1055
- const prevrootKey = this.status.rootKey;
1056
- const resultValue = {};
1057
- for (let i = 0, len = rootKey.length, r; i < len; i++) {
1058
- this.editor.changeFrameContext(rootKey[i]);
1059
-
1060
- const fc = this.editor.frameContext;
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'));
1063
-
1064
- for (let j = 0, jlen = editableEls.length; j < jlen; j++) {
1065
- editableEls[j].removeAttribute('contenteditable');
1066
- }
1067
-
1068
- const content = renderHTML.innerHTML;
1069
- if (this.editor.frameOptions.get('iframe_fullPage')) {
1070
- if (includeFullPage) {
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>`;
1073
- } else {
1074
- r = content;
1075
- }
1076
- } else {
1077
- r = withFrame ? `<div class="${this.options.get('_editableClass') + '' + (this.options.get('_rtl') ? ' se-rtl' : '')}">${content}</div>` : renderHTML.innerHTML;
1078
- }
1079
-
1080
- resultValue[rootKey[i]] = r;
1081
- }
1082
-
1083
- this.editor.changeFrameContext(prevrootKey);
1084
- return rootKey.length > 1 ? resultValue : resultValue[rootKey[0]];
1085
- },
1086
-
1087
- /**
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
1093
- */
1094
- set(html, { rootKey } = {}) {
1095
- this.selection.removeRange();
1096
- const convertValue = html === null || html === undefined ? '' : this.clean(html, { forceFormat: true, whitelist: null, blacklist: null });
1097
-
1098
- if (!rootKey) rootKey = [this.status.rootKey];
1099
- else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1100
-
1101
- for (let i = 0; i < rootKey.length; i++) {
1102
- this.editor.changeFrameContext(rootKey[i]);
1103
-
1104
- if (!this.editor.frameContext.get('isCodeView')) {
1105
- this.editor.frameContext.get('wysiwyg').innerHTML = convertValue;
1106
- this.editor._resetComponents();
1107
- this.history.push(false, rootKey[i]);
1108
- } else {
1109
- const value = this._convertToCode(convertValue, false);
1110
- this.viewer._setCodeView(value);
1111
- }
1112
- }
1113
- },
1114
-
1115
- /**
1116
- * @this {HTMLThis}
1117
- * @description Add content to the end of content.
1118
- * @param {string} html Content to Input
1119
- * @param {Object} [options] Options
1120
- * @param {number|Array<number>} [options.rootKey=null] Root index
1121
- */
1122
- add(html, { rootKey } = {}) {
1123
- if (!rootKey) rootKey = [this.status.rootKey];
1124
- else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1125
-
1126
- for (let i = 0; i < rootKey.length; i++) {
1127
- this.editor.changeFrameContext(rootKey[i]);
1128
- const convertValue = this.clean(html, { forceFormat: true, whitelist: null, blacklist: null });
1129
-
1130
- if (!this.editor.frameContext.get('isCodeView')) {
1131
- const temp = dom.utils.createElement('DIV', null, convertValue);
1132
- const children = temp.children;
1133
- const len = children.length;
1134
- for (let j = 0; j < len; j++) {
1135
- if (!children[j]) continue;
1136
- this.editor.frameContext.get('wysiwyg').appendChild(children[j]);
1137
- }
1138
- this.history.push(false, rootKey[i]);
1139
- this.selection.scrollTo(children[len - 1]);
1140
- } else {
1141
- this.viewer._setCodeView(this.viewer._getCodeView() + '\n' + this._convertToCode(convertValue, false));
1142
- }
1143
- }
1144
- },
1145
-
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}
1189
- * @description Sets the content of the iframe's head tag and body tag when using the "iframe" or "iframe_fullPage" option.
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
1193
- */
1194
- setFullPage(ctx, { rootKey } = {}) {
1195
- if (!this.editor.frameOptions.get('iframe')) return false;
1196
-
1197
- if (!rootKey) rootKey = [this.status.rootKey];
1198
- else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1199
-
1200
- for (let i = 0; i < rootKey.length; i++) {
1201
- this.editor.changeFrameContext(rootKey[i]);
1202
- if (ctx.head) this.editor.frameContext.get('_wd').head.innerHTML = ctx.head.replace(this.__disallowedTagsRegExp, '');
1203
- if (ctx.body) this.editor.frameContext.get('_wd').body.innerHTML = this.clean(ctx.body, { forceFormat: true, whitelist: null, blacklist: null });
1204
- this.editor._resetComponents();
1205
- }
1206
- },
1207
-
1208
- /**
1209
- * @this {HTMLThis}
1210
- * @description HTML code compression
1211
- * @param {string} html HTML string
1212
- * @returns {string} HTML string
1213
- */
1214
- compress(html) {
1215
- return html
1216
- .replace(/\n/g, '')
1217
- .replace(/(>)(?:\s+)(<)/g, '$1$2')
1218
- .trim();
1219
- },
1220
-
1221
- /**
1222
- * @private
1223
- * @this {HTMLThis}
1224
- * @description construct wysiwyg area element to html string
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.
1227
- * @returns {string}
1228
- */
1229
- _convertToCode(html, comp) {
1230
- let returnHTML = '';
1231
- const wRegExp = RegExp;
1232
- const brReg = new wRegExp('^(BLOCKQUOTE|PRE|TABLE|THEAD|TBODY|TR|TH|TD|OL|UL|IMG|IFRAME|VIDEO|AUDIO|FIGURE|FIGCAPTION|HR|BR|CANVAS|SELECT)$', 'i');
1233
- const wDoc = typeof html === 'string' ? this._d.createRange().createContextualFragment(html) : html;
1234
- const isFormat = (current) => {
1235
- return this.format.isLine(current) || this.component.is(current);
1236
- };
1237
- const brChar = comp ? '' : '\n';
1238
-
1239
- const codeSize = comp ? 0 : this.status.codeIndentSize * 1;
1240
- const indentSize = codeSize > 0 ? new Array(codeSize + 1).join(' ') : '';
1241
-
1242
- (function recursionFunc(element, indent) {
1243
- const children = element.childNodes;
1244
- const elementRegTest = brReg.test(element.nodeName);
1245
- const elementIndent = elementRegTest ? indent : '';
1246
-
1247
- for (let i = 0, len = children.length, node, br, lineBR, nodeRegTest, tag, tagIndent; i < len; i++) {
1248
- node = children[i];
1249
- nodeRegTest = brReg.test(node.nodeName);
1250
- br = nodeRegTest ? brChar : '';
1251
- lineBR = isFormat(node) && !elementRegTest && !/^(TH|TD)$/i.test(element.nodeName) ? brChar : '';
1252
-
1253
- if (node.nodeType === 8) {
1254
- returnHTML += '\n<!-- ' + node.textContent.trim() + ' -->' + br;
1255
- continue;
1256
- }
1257
- if (node.nodeType === 3) {
1258
- if (!dom.check.isList(node.parentElement)) returnHTML += converter.htmlToEntity(/^\n+$/.test(/** @type {Text} */ (node).data) ? '' : /** @type {Text} */ (node).data);
1259
- continue;
1260
- }
1261
- if (node.childNodes.length === 0) {
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;
1263
- continue;
1264
- }
1265
-
1266
- if (!(/** @type {HTMLElement} */ (node).outerHTML)) {
1267
- returnHTML += new XMLSerializer().serializeToString(node);
1268
- } else {
1269
- tag = node.nodeName.toLowerCase();
1270
- tagIndent = elementIndent || nodeRegTest ? indent : '';
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 : '');
1274
- }
1275
- }
1276
- })(wDoc, '');
1277
-
1278
- return returnHTML.trim() + brChar;
1279
- },
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
- */
1287
- _nodeRemoveListItem(item) {
1288
- const line = this.format.getLine(item, null);
1289
- dom.utils.removeItem(item);
1290
-
1291
- if (!dom.check.isListCell(line)) return;
1292
-
1293
- this.nodeTransform.removeAllParents(line, null, null);
1294
-
1295
- if (dom.check.isList(line?.firstChild)) {
1296
- line.insertBefore(dom.utils.createTextNode(unicode.zeroWidthSpace), line.firstChild);
1297
- }
1298
- },
1299
-
1300
- /**
1301
- * @private
1302
- * @this {HTMLThis}
1303
- * @description Recursive function when used to place a node in "BrLine" in "html.insertNode"
1304
- * @param {Node} oNode Node to be inserted
1305
- * @returns {Node} "oNode"
1306
- */
1307
- _setIntoFreeFormat(oNode) {
1308
- const parentNode = oNode.parentNode;
1309
- let oNodeChildren, lastONode;
1310
-
1311
- while (this.format.isLine(oNode) || this.format.isBlock(oNode)) {
1312
- oNodeChildren = oNode.childNodes;
1313
- lastONode = null;
1314
-
1315
- while (oNodeChildren[0]) {
1316
- lastONode = oNodeChildren[0];
1317
- if (this.format.isLine(lastONode) || this.format.isBlock(lastONode)) {
1318
- this._setIntoFreeFormat(lastONode);
1319
- if (!oNode.parentNode) break;
1320
- oNodeChildren = oNode.childNodes;
1321
- continue;
1322
- }
1323
-
1324
- parentNode.insertBefore(lastONode, oNode);
1325
- }
1326
-
1327
- if (oNode.childNodes.length === 0) dom.utils.removeItem(oNode);
1328
- oNode = dom.utils.createElement('BR');
1329
- parentNode.insertBefore(oNode, lastONode.nextSibling);
1330
- }
1331
-
1332
- return oNode;
1333
- },
1334
-
1335
- /**
1336
- * @private
1337
- * @this {HTMLThis}
1338
- * @description Returns HTML string according to tag type and configurati isExcludeFormat.
1339
- * @param {Node} node Node
1340
- * @param {boolean} forceFormat If true, text nodes that do not have a format node is wrapped with the format tag.
1341
- */
1342
- _makeLine(node, forceFormat) {
1343
- const defaultLine = this.options.get('defaultLine');
1344
- // element
1345
- if (node.nodeType === 1) {
1346
- if (this.__disallowedTagNameRegExp.test(node.nodeName)) return '';
1347
- if (dom.check.isExcludeFormat(node)) return node.outerHTML;
1348
-
1349
- const ch =
1350
- dom.query.getListChildNodes(node, (current) => {
1351
- return dom.check.isSpanWithoutAttr(current) && !dom.query.getParentElement(current, dom.check.isExcludeFormat);
1352
- }) || [];
1353
- for (let i = ch.length - 1, c; i >= 0; i--) {
1354
- c = /** @type {HTMLElement} */ (ch[i]);
1355
- c.outerHTML = c.innerHTML;
1356
- }
1357
-
1358
- if (
1359
- !forceFormat ||
1360
- this.format.isLine(node) ||
1361
- this.format.isBlock(node) ||
1362
- this.component.is(node) ||
1363
- dom.check.isMedia(node) ||
1364
- dom.check.isFigure(node) ||
1365
- (dom.check.isAnchor(node) && dom.check.isMedia(node.firstElementChild))
1366
- ) {
1367
- const n = /** @type {HTMLElement} */ (node);
1368
- return dom.check.isSpanWithoutAttr(node) ? n.innerHTML : n.outerHTML;
1369
- } else {
1370
- const n = /** @type {HTMLElement} */ (node);
1371
- return '<' + defaultLine + '>' + (dom.check.isSpanWithoutAttr(node) ? n.innerHTML : n.outerHTML) + '</' + defaultLine + '>';
1372
- }
1373
- }
1374
- // text
1375
- if (node.nodeType === 3) {
1376
- if (!forceFormat) return converter.htmlToEntity(node.textContent);
1377
- const textArray = node.textContent.split(/\n/g);
1378
- let html = '';
1379
- for (let i = 0, tLen = textArray.length, text; i < tLen; i++) {
1380
- text = textArray[i].trim();
1381
- if (text.length > 0) html += '<' + defaultLine + '>' + converter.htmlToEntity(text) + '</' + defaultLine + '>';
1382
- }
1383
- return html;
1384
- }
1385
- // comments
1386
- if (node.nodeType === 8 && this._allowHTMLComment) {
1387
- return '<!--' + node.textContent.trim() + '-->';
1388
- }
1389
-
1390
- return '';
1391
- },
1392
-
1393
- /**
1394
- * @private
1395
- * @this {HTMLThis}
1396
- * @description Fix tags that do not fit the editor format.
1397
- * @param {DocumentFragment} documentFragment Document fragment "DOCUMENT_FRAGMENT_NODE" (nodeType === 11)
1398
- * @param {RegExp} htmlCheckWhitelistRegExp Editor tags whitelist
1399
- * @param {RegExp} htmlCheckBlacklistRegExp Editor tags blacklist
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
1404
- */
1405
- _consistencyCheckOfHTML(documentFragment, htmlCheckWhitelistRegExp, htmlCheckBlacklistRegExp, tagFilter, formatFilter, classFilter, _freeCodeViewMode) {
1406
- const removeTags = [],
1407
- emptyTags = [],
1408
- wrongList = [],
1409
- withoutFormatCells = [];
1410
-
1411
- // wrong position
1412
- const wrongTags = dom.query.getListChildNodes(documentFragment, (current) => {
1413
- if (formatFilter && current.nodeType !== 1) {
1414
- if (dom.check.isList(current.parentElement)) removeTags.push(current);
1415
- return false;
1416
- }
1417
-
1418
- // tag filter
1419
- if (tagFilter) {
1420
- // white list
1421
- if (htmlCheckBlacklistRegExp.test(current.nodeName) || (!htmlCheckWhitelistRegExp.test(current.nodeName) && current.childNodes.length === 0 && dom.check.isExcludeFormat(current))) {
1422
- removeTags.push(current);
1423
- return false;
1424
- }
1425
- }
1426
-
1427
- const nrtag = !dom.query.getParentElement(current, dom.check.isExcludeFormat);
1428
-
1429
- // formatFilter
1430
- if (formatFilter) {
1431
- // empty tags
1432
- if (
1433
- !dom.check.isTableElements(current) &&
1434
- !dom.check.isListCell(current) &&
1435
- !dom.check.isAnchor(current) &&
1436
- (this.format.isLine(current) || this.format.isBlock(current) || this.format.isTextStyleNode(current)) &&
1437
- current.childNodes.length === 0 &&
1438
- nrtag
1439
- ) {
1440
- emptyTags.push(current);
1441
- return false;
1442
- }
1443
-
1444
- // wrong list
1445
- if (dom.check.isList(current.parentNode) && !dom.check.isList(current) && !dom.check.isListCell(current)) {
1446
- wrongList.push(current);
1447
- return false;
1448
- }
1449
-
1450
- // table cells
1451
- if (dom.check.isTableCell(current)) {
1452
- const fel = current.firstElementChild;
1453
- if (!this.format.isLine(fel) && !this.format.isBlock(fel) && !this.component.is(fel)) {
1454
- withoutFormatCells.push(current);
1455
- return false;
1456
- }
1457
- }
1458
- }
1459
-
1460
- // class filter
1461
- if (classFilter) {
1462
- if (nrtag && current.className) {
1463
- const className = new Array(current.classList).map(this._isAllowedClassName).join(' ').trim();
1464
- if (className) current.className = className;
1465
- else current.removeAttribute('class');
1466
- }
1467
- }
1468
-
1469
- // format filter
1470
- if (!formatFilter) {
1471
- return false;
1472
- }
1473
-
1474
- const result =
1475
- !_freeCodeViewMode &&
1476
- current.parentNode !== documentFragment &&
1477
- nrtag &&
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))));
1480
-
1481
- return result;
1482
- });
1483
-
1484
- for (let i = 0, len = removeTags.length; i < len; i++) {
1485
- dom.utils.removeItem(removeTags[i]);
1486
- }
1487
-
1488
- const checkTags = [];
1489
- for (let i = 0, len = wrongTags.length, t, p; i < len; i++) {
1490
- t = wrongTags[i];
1491
- p = t.parentNode;
1492
- if (!p || !p.parentNode) continue;
1493
-
1494
- if (dom.query.getParentElement(t, dom.check.isListCell)) {
1495
- const cellChildren = t.childNodes;
1496
- for (let j = cellChildren.length - 1; len >= 0; j--) {
1497
- p.insertBefore(t, cellChildren[j]);
1498
- }
1499
- checkTags.push(t);
1500
- } else {
1501
- p.parentNode.insertBefore(t, p);
1502
- checkTags.push(p);
1503
- }
1504
- }
1505
-
1506
- for (let i = 0, len = checkTags.length, t; i < len; i++) {
1507
- t = checkTags[i];
1508
- if (dom.check.isZeroWidth(t.textContent.trim())) {
1509
- dom.utils.removeItem(t);
1510
- }
1511
- }
1512
-
1513
- for (let i = 0, len = emptyTags.length; i < len; i++) {
1514
- dom.utils.removeItem(emptyTags[i]);
1515
- }
1516
-
1517
- for (let i = 0, len = wrongList.length, t, tp, children, p; i < len; i++) {
1518
- t = wrongList[i];
1519
- p = t.parentNode;
1520
- if (!p) continue;
1521
-
1522
- tp = dom.utils.createElement('LI');
1523
-
1524
- if (this.format.isLine(t)) {
1525
- children = t.childNodes;
1526
- while (children[0]) {
1527
- tp.appendChild(children[0]);
1528
- }
1529
- p.insertBefore(tp, t);
1530
- dom.utils.removeItem(t);
1531
- } else {
1532
- t = t.nextSibling;
1533
- tp.appendChild(wrongList[i]);
1534
- p.insertBefore(tp, t);
1535
- }
1536
- }
1537
-
1538
- for (let i = 0, len = withoutFormatCells.length, t, f; i < len; i++) {
1539
- t = withoutFormatCells[i];
1540
- f = dom.utils.createElement('DIV');
1541
- f.innerHTML = t.textContent.trim().length === 0 && t.children.length === 0 ? '<br>' : t.innerHTML;
1542
- t.innerHTML = f.outerHTML;
1543
- }
1544
- },
1545
-
1546
- /**
1547
- * @private
1548
- * @this {HTMLThis}
1549
- * @description Removes attribute values such as style and converts tags that do not conform to the "html5" standard.
1550
- * @param {string} html HTML string
1551
- * @returns {string} HTML string
1552
- */
1553
- _styleNodeConvertor(html) {
1554
- if (!this._disallowedStyleNodesRegExp) return html;
1555
-
1556
- const ec = this.options.get('_defaultStyleTagMap');
1557
- return html.replace(this._disallowedStyleNodesRegExp, (m, t, n, p) => {
1558
- return t + (typeof ec[n] === 'string' ? ec[n] : n) + (p ? ' ' + p : '');
1559
- });
1560
- },
1561
-
1562
- /**
1563
- * @private
1564
- * @this {HTMLThis}
1565
- * @description Determines if formatting is required and returns a domTree
1566
- * @param {DocumentFragment} domFrag documentFragment
1567
- * @returns {DocumentFragment}
1568
- */
1569
- _editFormat(domFrag) {
1570
- let value = '',
1571
- f;
1572
- const tempTree = domFrag.childNodes;
1573
-
1574
- for (let i = 0, len = tempTree.length, n; i < len; i++) {
1575
- n = /** @type {HTMLElement} */ (tempTree[i]);
1576
- if (this.__allowedTagNameRegExp.test(n.nodeName)) {
1577
- value += n.outerHTML;
1578
- continue;
1579
- }
1580
-
1581
- if (n.nodeType === 8) {
1582
- value += '<!-- ' + n.textContent + ' -->';
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'));
1585
- f.appendChild(n);
1586
- i--;
1587
- len--;
1588
- } else {
1589
- if (f) {
1590
- value += f.outerHTML;
1591
- f = null;
1592
- }
1593
- value += n.outerHTML;
1594
- }
1595
- }
1596
-
1597
- if (f) value += f.outerHTML;
1598
-
1599
- return this._d.createRange().createContextualFragment(value);
1600
- },
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
- */
1611
- _convertListCell(domTree) {
1612
- let html = '';
1613
-
1614
- for (let i = 0, len = domTree.length, node; i < len; i++) {
1615
- node = domTree[i];
1616
- if (node.nodeType === 1) {
1617
- if (dom.check.isList(node)) {
1618
- html += node.innerHTML;
1619
- } else if (dom.check.isListCell(node)) {
1620
- html += node.outerHTML;
1621
- } else if (this.format.isLine(node)) {
1622
- html += '<li>' + (node.innerHTML.trim() || '<br>') + '</li>';
1623
- } else if (this.format.isBlock(node) && !dom.check.isTableElements(node)) {
1624
- html += this._convertListCell(node.children);
1625
- } else {
1626
- html += '<li>' + /** @type {HTMLElement} */ (node).outerHTML + '</li>';
1627
- }
1628
- } else {
1629
- html += '<li>' + (node.textContent || '<br>') + '</li>';
1630
- }
1631
- }
1632
-
1633
- return html;
1634
- },
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
- */
1643
- _isFormatData(domTree) {
1644
- let requireFormat = false;
1645
-
1646
- for (let i = 0, len = domTree.length, t; i < len; i++) {
1647
- t = domTree[i];
1648
- if (t.nodeType === 1 && !this.format.isTextStyleNode(t) && !dom.check.isBreak(t) && !this.__disallowedTagNameRegExp.test(t.nodeName)) {
1649
- requireFormat = true;
1650
- break;
1651
- }
1652
- }
1653
-
1654
- return requireFormat;
1655
- },
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
- */
1667
- _cleanStyle(m, v, name) {
1668
- let sv = (m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/) || [])[0];
1669
- if (this._textStyleTags.includes(name) && !sv && (m.match(/<[^\s]+\s(.+)/) || [])[1]) {
1670
- const size = (m.match(/\ssize="([^"]+)"/i) || [])[1];
1671
- const face = (m.match(/\sface="([^"]+)"/i) || [])[1];
1672
- const color = (m.match(/\scolor="([^"]+)"/i) || [])[1];
1673
- if (size || face || color) {
1674
- sv = 'style="' + (size ? 'font-size:' + numbers.get(Number(size) / 3.333, 1) + 'rem;' : '') + (face ? 'font-family:' + face + ';' : '') + (color ? 'color:' + color + ';' : '') + '"';
1675
- }
1676
- }
1677
-
1678
- if (sv) {
1679
- if (!v) v = [];
1680
-
1681
- let mv;
1682
- for (const [key, value] of this._cleanStyleRegExpMap) {
1683
- if (key.test(name)) {
1684
- mv = value;
1685
- break;
1686
- }
1687
- }
1688
- if (!mv) return v;
1689
-
1690
- const style = sv.replace(/&quot;/g, '').match(mv);
1691
- if (!style) return v;
1692
-
1693
- const allowedStyle = [];
1694
- for (let i = 0, len = style.length, r; i < len; i++) {
1695
- r = style[i].match(/([a-zA-Z0-9-]+)(:)([^"']+)/);
1696
- if (r && !/inherit|initial|revert|unset/i.test(r[3])) {
1697
- const k = converter.kebabToCamelCase(r[1].trim());
1698
- const cs = this.editor.frameContext.get('wwComputedStyle')[k]?.replace(/"/g, '');
1699
- const c = r[3].trim();
1700
- switch (k) {
1701
- case 'fontFamily':
1702
- if (!this.plugins.font || !this.plugins.font.fontArray.includes(c)) continue;
1703
- break;
1704
- case 'fontSize':
1705
- if (!this.plugins.fontSize) continue;
1706
- if (!this.fontSizeUnitRegExp.test(r[0])) {
1707
- r[0] = r[0].replace((r[0].match(/:\s*([^;]+)/) || [])[1], converter.toFontUnit.bind(null, this.options.get('fontSizeUnits')[0]));
1708
- }
1709
- break;
1710
- case 'color':
1711
- if (!this.plugins.fontColor || /rgba\(([0-9]+\s*,\s*){3}0\)|windowtext/i.test(c)) continue;
1712
- break;
1713
- case 'backgroundColor':
1714
- if (!this.plugins.backgroundColor || /rgba\(([0-9]+\s*,\s*){3}0\)|windowtext/i.test(c)) continue;
1715
- break;
1716
- }
1717
-
1718
- if (cs !== c) {
1719
- allowedStyle.push(r[0]);
1720
- }
1721
- }
1722
- }
1723
- if (allowedStyle.length > 0) v.push('style="' + allowedStyle.join(';') + '"');
1724
- }
1725
-
1726
- return v;
1727
- },
1728
-
1729
- /**
1730
- * @private
1731
- * @this {HTMLThis}
1732
- * @description Delete disallowed tags
1733
- * @param {string} html HTML string
1734
- * @returns {string}
1735
- */
1736
- _deleteDisallowedTags(html, whitelistRegExp, blacklistRegExp) {
1737
- if (whitelistRegExp.test('<font>')) {
1738
- html = html.replace(/(<\/?)font(\s?)/gi, '$1span$2');
1739
- }
1740
-
1741
- return html.replace(whitelistRegExp, '').replace(blacklistRegExp, '');
1742
- },
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
- */
1751
- _checkDuplicateNode(oNode, parentNode) {
1752
- // eslint-disable-next-line @typescript-eslint/no-this-alias
1753
- const inst = this;
1754
- (function recursionFunc(current) {
1755
- inst._dupleCheck(current, parentNode);
1756
- const childNodes = current.childNodes;
1757
- for (let i = 0, len = childNodes.length; i < len; i++) {
1758
- recursionFunc(childNodes[i]);
1759
- }
1760
- })(oNode);
1761
- },
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
- */
1772
- _dupleCheck(oNode, parentNode) {
1773
- if (!this.format.isTextStyleNode(oNode)) return;
1774
-
1775
- const oStyles = (oNode.style.cssText.match(/[^;]+;/g) || []).map(function (v) {
1776
- return v.trim();
1777
- });
1778
- const nodeName = oNode.nodeName;
1779
- if (/^span$/i.test(nodeName) && oStyles.length === 0) return oNode;
1780
-
1781
- const inst = this.format;
1782
- let duple = false;
1783
- (function recursionFunc(ancestor) {
1784
- if (dom.check.isWysiwygFrame(ancestor) || !inst.isTextStyleNode(ancestor)) return;
1785
- if (ancestor.nodeName === nodeName) {
1786
- duple = true;
1787
- const styles = ancestor.style.cssText.match(/[^;]+;/g) || [];
1788
- for (let i = 0, len = styles.length, j; i < len; i++) {
1789
- if ((j = oStyles.indexOf(styles[i].trim())) > -1) {
1790
- oStyles.splice(j, 1);
1791
- }
1792
- }
1793
- for (let i = 0, len = ancestor.classList.length; i < len; i++) {
1794
- oNode.classList.remove(ancestor.classList[i]);
1795
- }
1796
- }
1797
-
1798
- recursionFunc(ancestor.parentElement);
1799
- })(parentNode);
1800
-
1801
- if (duple) {
1802
- if (!(oNode.style.cssText = oStyles.join(' '))) {
1803
- oNode.setAttribute('style', '');
1804
- oNode.removeAttribute('style');
1805
- }
1806
- if (!oNode.attributes.length) {
1807
- oNode.setAttribute('data-duple', 'true');
1808
- }
1809
- }
1810
-
1811
- return oNode;
1812
- },
1813
-
1814
- constructor: HTML
1815
- };
1816
-
1817
- /**
1818
- * @private
1819
- * @this {HTMLThis}
1820
- * @description Tag and tag attribute check RegExp function.
1821
- * @param {string} m RegExp value
1822
- * @param {string} t RegExp value
1823
- * @returns {string}
1824
- */
1825
- function CleanElements(attrFilter, styleFilter, m, t) {
1826
- if (/^<[a-z0-9]+:[a-z0-9]+/i.test(m)) return m;
1827
-
1828
- let v = null;
1829
- const tagName = t.match(/(?!<)[a-zA-Z0-9-]+/)[0].toLowerCase();
1830
-
1831
- if (attrFilter) {
1832
- // blacklist
1833
- const bAttr = this._attributeBlacklist[tagName];
1834
- m = m.replace(/\s(?:on[a-z]+)\s*=\s*(")[^"]*\1/gi, '');
1835
- if (bAttr) m = m.replace(bAttr, '');
1836
- else m = m.replace(this._attributeBlacklistRegExp, '');
1837
-
1838
- // whitelist
1839
- const wAttr = this._attributeWhitelist[tagName];
1840
- if (wAttr) v = m.match(wAttr);
1841
- else v = m.match(this._attributeWhitelistRegExp);
1842
- }
1843
-
1844
- if (!styleFilter) return m;
1845
-
1846
- // attribute
1847
- if (tagName === 'a') {
1848
- const sv = m.match(/(?:(?:id|name)\s*=\s*(?:"|')[^"']*(?:"|'))/g);
1849
- if (sv) {
1850
- if (!v) v = [];
1851
- v.push(sv[0]);
1852
- }
1853
- } else if (!v || !/style=/i.test(v.toString())) {
1854
- if (this._textStyleTags.includes(tagName)) {
1855
- v = this._cleanStyle(m, v, tagName);
1856
- } else if (this.format.isLine(tagName)) {
1857
- v = this._cleanStyle(m, v, 'line');
1858
- } else if (this._cleanStyleTagKeyRegExp.test(tagName)) {
1859
- v = this._cleanStyle(m, v, tagName);
1860
- }
1861
- }
1862
-
1863
- // figure
1864
- if (dom.check.isMedia(tagName) || dom.check.isFigure(tagName)) {
1865
- const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
1866
- if (!v) v = [];
1867
- if (sv) v.push(sv[0]);
1868
- }
1869
-
1870
- if (v) {
1871
- for (let i = 0, len = v.length, a; i < len; i++) {
1872
- a = /^(?:href|src)\s*=\s*('|"|\s)*javascript\s*:/i.test(v[i].trim()) ? '' : v[i];
1873
- t += (/^\s/.test(a) ? '' : ' ') + a;
1874
- }
1875
- }
1876
-
1877
- return t;
1878
- }
1879
-
1880
- /**
1881
- * @private
1882
- * @description Get related list
1883
- * @param {string} str Regular expression string
1884
- * @param {string} str2 Regular expression string
1885
- */
1886
- function GetRegList(str, str2) {
1887
- return !str ? '^' : str === '*' ? '[a-z-]+' : !str2 ? str : str + '|' + str2;
1888
- }
1889
-
1890
- export default HTML;
1
+ /**
2
+ * @fileoverview Char class
3
+ */
4
+
5
+ import CoreInjector from '../../editorInjector/_core';
6
+ import { dom, converter, numbers, unicode, clipboard } from '../../helper';
7
+
8
+ const REQUIRED_DATA_ATTRS = 'data-se-[^\\s]+';
9
+ const V2_MIG_DATA_ATTRS = '|data-index|data-file-size|data-file-name|data-exp|data-font-size';
10
+
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) {
22
+ CoreInjector.call(this, editor);
23
+ const options = this.options;
24
+
25
+ // members
26
+ this.fontSizeUnitRegExp = null;
27
+
28
+ this._isAllowedClassName = function (v) {
29
+ return this.test(v) ? v : '';
30
+ }.bind(options.get('allowedClassName'));
31
+ this._allowHTMLComment = null;
32
+ this._disallowedStyleNodesRegExp = null;
33
+ this._htmlCheckWhitelistRegExp = null;
34
+ this._htmlCheckBlacklistRegExp = null;
35
+ this._elementWhitelistRegExp = null;
36
+ this._elementBlacklistRegExp = null;
37
+ /** @type {Object<string, RegExp>} */
38
+ this._attributeWhitelist = null;
39
+ /** @type {Object<string, RegExp>} */
40
+ this._attributeBlacklist = null;
41
+ this._attributeWhitelistRegExp = null;
42
+ this._attributeBlacklistRegExp = null;
43
+ this._cleanStyleTagKeyRegExp = null;
44
+ this._cleanStyleRegExpMap = null;
45
+ this._textStyleTags = options.get('_textStyleTags');
46
+ /** @type {Object<string, *>} */
47
+ this._autoStyleify = null;
48
+ this.__disallowedTagsRegExp = null;
49
+ this.__disallowedTagNameRegExp = null;
50
+ this.__allowedTagNameRegExp = null;
51
+
52
+ // clean styles
53
+ const tagStyles = options.get('tagStyles');
54
+ const splitTagStyles = {};
55
+ for (const k in tagStyles) {
56
+ const s = k.split('|');
57
+ for (let i = 0, len = s.length, n; i < len; i++) {
58
+ n = s[i];
59
+ if (!splitTagStyles[n]) splitTagStyles[n] = '';
60
+ else splitTagStyles[n] += '|';
61
+ splitTagStyles[n] += tagStyles[k];
62
+ }
63
+ }
64
+ for (const k in splitTagStyles) {
65
+ splitTagStyles[k] = new RegExp(`\\s*[^-a-zA-Z](${splitTagStyles[k]})\\s*:[^;]+(?!;)*`, 'gi');
66
+ }
67
+
68
+ const stylesMap = new Map();
69
+ const stylesObj = {
70
+ ...splitTagStyles,
71
+ line: options.get('_lineStylesRegExp')
72
+ };
73
+ this._textStyleTags.forEach((v) => {
74
+ stylesObj[v] = options.get('_textStylesRegExp');
75
+ });
76
+
77
+ for (const key in stylesObj) {
78
+ stylesMap.set(new RegExp(`^(${key})$`), stylesObj[key]);
79
+ }
80
+ this._cleanStyleTagKeyRegExp = new RegExp(`^(${Object.keys(stylesObj).join('|')})$`, 'i');
81
+ this._cleanStyleRegExpMap = stylesMap;
82
+
83
+ // font size unit
84
+ this.fontSizeUnitRegExp = new RegExp('\\d+(' + options.get('fontSizeUnits').join('|') + ')$', 'i');
85
+
86
+ // extra tags
87
+ const allowedExtraTags = options.get('_allowedExtraTag');
88
+ const disallowedExtraTags = options.get('_disallowedExtraTag');
89
+ this.__disallowedTagsRegExp = new RegExp(`<(${disallowedExtraTags})[^>]*>([\\s\\S]*?)<\\/\\1>|<(${disallowedExtraTags})[^>]*\\/?>`, 'gi');
90
+ this.__disallowedTagNameRegExp = new RegExp(`^(${disallowedExtraTags})$`, 'i');
91
+ this.__allowedTagNameRegExp = new RegExp(`^(${allowedExtraTags})$`, 'i');
92
+
93
+ // set disallow text nodes
94
+ const disallowStyleNodes = Object.keys(options.get('_defaultStyleTagMap'));
95
+ const allowStyleNodes = !options.get('elementWhitelist')
96
+ ? []
97
+ : options
98
+ .get('elementWhitelist')
99
+ .split('|')
100
+ .filter((v) => /b|i|ins|s|strike/i.test(v));
101
+ for (let i = 0; i < allowStyleNodes.length; i++) {
102
+ disallowStyleNodes.splice(disallowStyleNodes.indexOf(allowStyleNodes[i].toLowerCase()), 1);
103
+ }
104
+ this._disallowedStyleNodesRegExp = disallowStyleNodes.length === 0 ? null : new RegExp('(<\\/?)(' + disallowStyleNodes.join('|') + ')\\b\\s*([^>^<]+)?\\s*(?=>)', 'gi');
105
+
106
+ // whitelist
107
+ // tags
108
+ const defaultAttr = options.get('__defaultAttributeWhitelist');
109
+ this._allowHTMLComment = options.get('_editorElementWhitelist').includes('//') || options.get('_editorElementWhitelist') === '*';
110
+ // html check
111
+ this._htmlCheckWhitelistRegExp = new RegExp('^(' + GetRegList(options.get('_editorElementWhitelist').replace('|//', ''), '') + ')$', 'i');
112
+ this._htmlCheckBlacklistRegExp = new RegExp('^(' + (options.get('elementBlacklist') || '^') + ')$', 'i');
113
+ // elements
114
+ this._elementWhitelistRegExp = converter.createElementWhitelist(GetRegList(options.get('_editorElementWhitelist').replace('|//', '|<!--|-->'), ''));
115
+ this._elementBlacklistRegExp = converter.createElementBlacklist(options.get('elementBlacklist').replace('|//', '|<!--|-->'));
116
+ // attributes
117
+ const regEndStr = '\\s*=\\s*(")[^"]*\\1';
118
+ const _wAttr = options.get('attributeWhitelist');
119
+
120
+ /** @type {Object<string, RegExp>} */
121
+ let tagsAttr = {};
122
+ let allAttr = '';
123
+ if (_wAttr) {
124
+ for (const k in _wAttr) {
125
+ if (/^on[a-z]+$/i.test(_wAttr[k])) continue;
126
+ if (k === '*') {
127
+ allAttr = GetRegList(_wAttr[k], defaultAttr);
128
+ } else {
129
+ tagsAttr[k] = new RegExp('\\s(?:' + GetRegList(_wAttr[k], defaultAttr) + ')' + regEndStr, 'ig');
130
+ }
131
+ }
132
+ }
133
+
134
+ this._attributeWhitelistRegExp = new RegExp('\\s(?:' + (allAttr || defaultAttr) + '|' + REQUIRED_DATA_ATTRS + (options.get('v2Migration') ? V2_MIG_DATA_ATTRS : '') + ')' + regEndStr, 'ig');
135
+ this._attributeWhitelist = tagsAttr;
136
+
137
+ // blacklist
138
+ const _bAttr = options.get('attributeBlacklist');
139
+ tagsAttr = {};
140
+ allAttr = '';
141
+ if (_bAttr) {
142
+ for (const k in _bAttr) {
143
+ if (k === '*') {
144
+ allAttr = GetRegList(_bAttr[k], '');
145
+ } else {
146
+ tagsAttr[k] = new RegExp('\\s(?:' + GetRegList(_bAttr[k], '') + ')' + regEndStr, 'ig');
147
+ }
148
+ }
149
+ }
150
+
151
+ this._attributeBlacklistRegExp = new RegExp('\\s(?:' + (allAttr || '^') + ')' + regEndStr, 'ig');
152
+ this._attributeBlacklist = tagsAttr;
153
+
154
+ // autoStyleify
155
+ this.__resetAutoStyleify(options.get('autoStyleify'));
156
+ }
157
+
158
+ HTML.prototype = {
159
+ /**
160
+ * @this {HTMLThis}
161
+ * @description Filters an HTML string based on allowed and disallowed tags, with optional custom validation.
162
+ * - Removes blacklisted tags and keeps only whitelisted tags.
163
+ * - Allows custom validation functions to replace, modify, or remove elements.
164
+ * @param {string} html - The HTML string to be filtered.
165
+ * @param {Object} params - Filtering parameters.
166
+ * @param {string} [params.tagWhitelist] - Allowed tags, specified as a string with tags separated by '|'. (e.g. "div|p|span").
167
+ * @param {string} [params.tagBlacklist] - Disallowed tags, specified as a string with tags separated by '|'. (e.g. "script|iframe").
168
+ * @param {(node: Node) => Node | string | null} [params.validate] - Function to validate and modify individual nodes.
169
+ * - Return `null` to remove the node.
170
+ * - Return a `Node` to replace the current node.
171
+ * - Return a `string` to replace the node's outerHTML.
172
+ * @param {boolean} [params.validateAll] - Whether to apply validation to all nodes.
173
+ * @returns {string} - The filtered HTML string.
174
+ */
175
+ filter(html, { tagWhitelist, tagBlacklist, validate, validateAll }) {
176
+ if (tagWhitelist) {
177
+ html = html.replace(converter.createElementWhitelist(tagWhitelist), '');
178
+ }
179
+ if (tagBlacklist) {
180
+ html = html.replace(converter.createElementBlacklist(tagBlacklist), '');
181
+ }
182
+ if (validate) {
183
+ const parseDocument = new DOMParser().parseFromString(html, 'text/html');
184
+ parseDocument.body.querySelectorAll('*').forEach((node) => {
185
+ if (!node.closest('.se-component') && !node.closest('.se-flex-component')) {
186
+ const result = validate(node);
187
+ if (result === null) {
188
+ node.remove();
189
+ } else if (this.instanceCheck.isNode(result)) {
190
+ node.replaceWith(result);
191
+ } else if (typeof result === 'string') {
192
+ node.outerHTML = result;
193
+ }
194
+ }
195
+ });
196
+ html = parseDocument.body.innerHTML;
197
+ } else if (validateAll) {
198
+ const parseDocument = new DOMParser().parseFromString(html, 'text/html');
199
+ const compClass = ['.se-component', '.se-flex-component'];
200
+ const closestAny = (element) => compClass.some((selector) => element.closest(selector));
201
+ parseDocument.body.querySelectorAll('*').forEach((node) => {
202
+ if (!closestAny(node)) {
203
+ const result = validate(node);
204
+ if (result === null) {
205
+ node.remove();
206
+ } else if (this.instanceCheck.isNode(result)) {
207
+ node.replaceWith(result);
208
+ } else if (typeof result === 'string') {
209
+ node.outerHTML = result;
210
+ }
211
+ }
212
+ });
213
+ html = parseDocument.body.innerHTML;
214
+ }
215
+
216
+ return html;
217
+ },
218
+
219
+ /**
220
+ * @this {HTMLThis}
221
+ * @description Cleans and compresses HTML code to suit the editor format.
222
+ * @param {string} html HTML string to clean and compress
223
+ * @param {Object} [options] Cleaning options
224
+ * @param {boolean} [options.forceFormat=false] If true, wraps text nodes without a format node in the format tag.
225
+ * @param {string|RegExp|null} [options.whitelist=null] Regular expression of allowed tags.
226
+ * Create RegExp object using helper.converter.createElementWhitelist method.
227
+ * @param {string|RegExp|null} [options.blacklist=null] Regular expression of disallowed tags.
228
+ * Create RegExp object using helper.converter.createElementBlacklist method.
229
+ * @param {boolean} [options._freeCodeViewMode=false] If true, the free code view mode is enabled.
230
+ * @returns {string} Cleaned and compressed HTML string
231
+ */
232
+ clean(html, { forceFormat, whitelist, blacklist, _freeCodeViewMode } = {}) {
233
+ const { tagFilter, formatFilter, classFilter, textStyleTagFilter, attrFilter, styleFilter } = this.options.get('strictMode');
234
+ let cleanData = '';
235
+
236
+ html = this.compress(html);
237
+
238
+ if (tagFilter) {
239
+ html = html.replace(this.__disallowedTagsRegExp, '');
240
+ html = this._deleteDisallowedTags(html, this._elementWhitelistRegExp, this._elementBlacklistRegExp).replace(/<br\/?>$/i, '');
241
+ }
242
+
243
+ if (this._autoStyleify) {
244
+ const domParser = new DOMParser().parseFromString(html, 'text/html');
245
+ dom.query.getListChildNodes(domParser.body, converter.spanToStyleNode.bind(null, this._autoStyleify));
246
+ html = domParser.body.innerHTML;
247
+ }
248
+
249
+ if (attrFilter || styleFilter) {
250
+ html = html.replace(/(<[a-zA-Z0-9-]+)[^>]*(?=>)/g, CleanElements.bind(this, attrFilter, styleFilter));
251
+ }
252
+
253
+ // get dom tree
254
+ const domParser = this._d.createRange().createContextualFragment(html);
255
+
256
+ if (tagFilter) {
257
+ try {
258
+ this._consistencyCheckOfHTML(domParser, this._htmlCheckWhitelistRegExp, this._htmlCheckBlacklistRegExp, tagFilter, formatFilter, classFilter, _freeCodeViewMode);
259
+ } catch (error) {
260
+ console.warn('[SUNEDITOR.html.clean.fail]', error.message);
261
+ }
262
+ }
263
+
264
+ // iframe placeholder parsing
265
+ const iframePlaceholders = domParser.querySelectorAll('[data-se-iframe-holder]');
266
+ for (let i = 0, len = iframePlaceholders.length; i < len; i++) {
267
+ /** @type {HTMLIFrameElement} */
268
+ const iframe = dom.utils.createElement('iframe');
269
+
270
+ const attrs = JSON.parse(iframePlaceholders[i].getAttribute('data-se-iframe-holder-attrs'));
271
+ for (const [key, value] of Object.entries(attrs)) {
272
+ iframe.setAttribute(key, value);
273
+ }
274
+
275
+ iframePlaceholders[i].replaceWith(iframe);
276
+ }
277
+
278
+ let retainFilter;
279
+ if ((retainFilter = this.options.get('__pluginRetainFilter'))) {
280
+ this.editor._MELInfo.forEach((plugin, query) => {
281
+ const infoLst = domParser.querySelectorAll(query);
282
+ for (let i = 0, len = infoLst.length; i < len; i++) {
283
+ if (retainFilter === true || retainFilter[plugin.key] !== false) plugin.method(infoLst[i]);
284
+ }
285
+ });
286
+ }
287
+
288
+ if (formatFilter) {
289
+ let domTree = domParser.childNodes;
290
+ if (!forceFormat) forceFormat = this._isFormatData(domTree);
291
+ if (forceFormat) domTree = this._editFormat(domParser).childNodes;
292
+
293
+ for (let i = 0, len = domTree.length, t; i < len; i++) {
294
+ t = domTree[i];
295
+ if (this.__allowedTagNameRegExp.test(t.nodeName)) {
296
+ cleanData += /** @type {HTMLElement} */ (t).outerHTML;
297
+ continue;
298
+ }
299
+ cleanData += this._makeLine(t, forceFormat);
300
+ }
301
+ }
302
+
303
+ // set clean data
304
+ if (!cleanData) cleanData = html;
305
+
306
+ // whitelist, blacklist
307
+ if (tagFilter) {
308
+ if (whitelist) cleanData = cleanData.replace(typeof whitelist === 'string' ? converter.createElementWhitelist(whitelist) : whitelist, '');
309
+ if (blacklist) cleanData = cleanData.replace(typeof blacklist === 'string' ? converter.createElementBlacklist(blacklist) : blacklist, '');
310
+ }
311
+
312
+ if (textStyleTagFilter) {
313
+ cleanData = this._styleNodeConvertor(cleanData);
314
+ }
315
+
316
+ return cleanData;
317
+ },
318
+
319
+ /**
320
+ * @this {HTMLThis}
321
+ * @description Inserts an (HTML element / HTML string / plain string) at the selection range.
322
+ * - If "frameOptions.get('charCounter_max')" is exceeded when "html" is added, null is returned without addition.
323
+ * @param {Node|string} html HTML Element or HTML string or plain string
324
+ * @param {Object} [options] Options
325
+ * @param {boolean} [options.selectInserted=false] If true, selects the range of the inserted node.
326
+ * @param {boolean} [options.skipCharCount=false] If true, inserts even if "frameOptions.get('charCounter_max')" is exceeded.
327
+ * @param {boolean} [options.skipCleaning=false] If true, inserts the HTML string without refining it with html.clean.
328
+ * @returns {HTMLElement|null} The inserted element or null if insertion failed
329
+ */
330
+ insert(html, { selectInserted, skipCharCount, skipCleaning } = {}) {
331
+ if (!this.editor.frameContext.get('wysiwyg').contains(this.selection.get().focusNode)) this.editor.focus();
332
+
333
+ if (typeof html === 'string') {
334
+ if (!skipCleaning) html = this.clean(html, { forceFormat: false, whitelist: null, blacklist: null });
335
+ try {
336
+ if (dom.check.isListCell(this.format.getLine(this.selection.getNode(), null))) {
337
+ const domParser = this._d.createRange().createContextualFragment(html);
338
+ const domTree = domParser.childNodes;
339
+ if (this._isFormatData(domTree)) html = this._convertListCell(domTree);
340
+ }
341
+
342
+ const domParser = this._d.createRange().createContextualFragment(html);
343
+ const domTree = domParser.childNodes;
344
+
345
+ if (!skipCharCount) {
346
+ const type = this.editor.frameOptions.get('charCounter_type') === 'byte-html' ? 'outerHTML' : 'textContent';
347
+ let checkHTML = '';
348
+ for (let i = 0, len = domTree.length; i < len; i++) {
349
+ checkHTML += domTree[i][type];
350
+ }
351
+ if (!this.char.check(checkHTML)) return;
352
+ }
353
+
354
+ let c, a, t, prev, firstCon;
355
+ while ((c = domTree[0])) {
356
+ if (prev?.nodeType === 3 && a?.nodeType === 1 && dom.check.isBreak(c)) {
357
+ prev = c;
358
+ dom.utils.removeItem(c);
359
+ continue;
360
+ }
361
+ t = this.insertNode(c, { afterNode: a, skipCharCount: true });
362
+ a = t.container || t;
363
+ if (!firstCon) firstCon = t;
364
+ prev = c;
365
+ }
366
+
367
+ if (prev?.nodeType === 3 && a?.nodeType === 1) a = prev;
368
+ const offset = a.nodeType === 3 ? t.endOffset || a.textContent.length : a.childNodes.length;
369
+
370
+ if (selectInserted) {
371
+ this.selection.setRange(firstCon.container || firstCon, firstCon.startOffset || 0, a, offset);
372
+ } else if (!this.component.is(a)) {
373
+ this.selection.setRange(a, offset, a, offset);
374
+ }
375
+ } catch (error) {
376
+ if (this.editor.frameContext.get('isReadOnly') || this.editor.frameContext.get('isDisabled')) return;
377
+ throw Error(`[SUNEDITOR.html.insert.error] ${error.message}`);
378
+ }
379
+ } else {
380
+ if (this.component.is(html)) {
381
+ this.component.insert(html, { skipCharCount, skipSelection: false, skipHistory: false });
382
+ } else {
383
+ let afterNode = null;
384
+ if (this.format.isLine(html) || dom.check.isMedia(html)) {
385
+ afterNode = this.format.getLine(this.selection.getNode(), null);
386
+ }
387
+ this.insertNode(html, { afterNode, skipCharCount });
388
+ }
389
+ }
390
+
391
+ this.editor.effectNode = null;
392
+ this.editor.focus();
393
+ this.history.push(false);
394
+ },
395
+
396
+ /**
397
+ * @this {HTMLThis}
398
+ * @description Delete selected node and insert argument value node and return.
399
+ * - If the "afterNode" exists, it is inserted after the "afterNode"
400
+ * - Inserting a text node merges with both text nodes on both sides and returns a new "{ container, startOffset, endOffset }".
401
+ * @param {Node} oNode Node to be inserted
402
+ * @param {Object} [options] Options
403
+ * @param {Node} [options.afterNode=null] If the node exists, it is inserted after the node
404
+ * @param {boolean} [options.skipCharCount=null] If true, it will be inserted even if "frameOptions.get('charCounter_max')" is exceeded.
405
+ * @returns {Object|Node|null}
406
+ */
407
+ insertNode(oNode, { afterNode, skipCharCount } = {}) {
408
+ let result = null;
409
+ if (this.editor.frameContext.get('isReadOnly') || (!skipCharCount && !this.char.check(oNode))) {
410
+ return result;
411
+ }
412
+
413
+ let fNode = null;
414
+ let range = this.selection.getRange();
415
+ let line = dom.check.isListCell(range.commonAncestorContainer) ? range.commonAncestorContainer : this.format.getLine(this.selection.getNode(), null);
416
+ let insertListCell = dom.check.isListCell(line) && (dom.check.isListCell(oNode) || dom.check.isList(oNode));
417
+
418
+ let parentNode,
419
+ originAfter,
420
+ tempAfterNode,
421
+ tempParentNode = null;
422
+ const freeFormat = this.format.isBrLine(line);
423
+ const isFormats = (!freeFormat && (this.format.isLine(oNode) || this.format.isBlock(oNode))) || this.component.isBasic(oNode);
424
+
425
+ if (insertListCell) {
426
+ tempAfterNode = afterNode || dom.check.isList(oNode) ? line.lastChild : line.nextElementSibling;
427
+ tempParentNode = dom.check.isList(oNode) ? line : (tempAfterNode || line).parentNode;
428
+ }
429
+
430
+ if (!afterNode && (isFormats || this.component.isBasic(oNode) || dom.check.isMedia(oNode))) {
431
+ const isEdge = dom.check.isEdgePoint(range.endContainer, range.endOffset, 'end');
432
+ const r = this.remove();
433
+ const container = r.container;
434
+ const prevContainer = container === r.prevContainer && range.collapsed ? null : r.prevContainer;
435
+
436
+ if (insertListCell && prevContainer) {
437
+ tempParentNode = prevContainer.nodeType === 3 ? prevContainer.parentNode : prevContainer;
438
+ if (tempParentNode.contains(container)) {
439
+ let sameParent = true;
440
+ tempAfterNode = container;
441
+ while (tempAfterNode.parentNode && tempAfterNode.parentNode !== tempParentNode) {
442
+ tempAfterNode = tempAfterNode.parentNode;
443
+ sameParent = false;
444
+ }
445
+ if (sameParent && container === prevContainer) tempAfterNode = tempAfterNode.nextSibling;
446
+ } else {
447
+ tempAfterNode = null;
448
+ }
449
+ } else if (insertListCell && dom.check.isListCell(container) && !line.parentElement) {
450
+ line = dom.utils.createElement('LI');
451
+ tempParentNode.appendChild(line);
452
+ container.appendChild(tempParentNode);
453
+ tempAfterNode = null;
454
+ } else if (container.nodeType === 3 || dom.check.isBreak(container) || insertListCell) {
455
+ const depthFormat = dom.query.getParentElement(container, (current) => {
456
+ return this.format.isBlock(current) || dom.check.isListCell(current);
457
+ });
458
+ afterNode = this.nodeTransform.split(container, r.offset, !depthFormat ? 0 : dom.query.getNodeDepth(depthFormat) + 1);
459
+ if (!afterNode) {
460
+ tempAfterNode = afterNode = line;
461
+ } else if (insertListCell) {
462
+ if (line.contains(container)) {
463
+ const subList = dom.check.isList(line.lastElementChild);
464
+ let newCell = null;
465
+ if (!isEdge) {
466
+ newCell = line.cloneNode(false);
467
+ newCell.appendChild(afterNode.textContent.trim() ? afterNode : dom.utils.createTextNode(unicode.zeroWidthSpace));
468
+ }
469
+ if (subList) {
470
+ if (!newCell) {
471
+ newCell = line.cloneNode(false);
472
+ newCell.appendChild(dom.utils.createTextNode(unicode.zeroWidthSpace));
473
+ }
474
+ newCell.appendChild(line.lastElementChild);
475
+ }
476
+ if (newCell) {
477
+ line.parentNode.insertBefore(newCell, line.nextElementSibling);
478
+ tempAfterNode = afterNode = newCell;
479
+ }
480
+ }
481
+ } else {
482
+ afterNode = afterNode.previousSibling;
483
+ }
484
+ }
485
+ }
486
+
487
+ range = !afterNode && !isFormats ? this.selection.getRangeAndAddLine(this.selection.getRange(), null) : this.selection.getRange();
488
+ const commonCon = range.commonAncestorContainer;
489
+ const startOff = range.startOffset;
490
+ const endOff = range.endOffset;
491
+ const formatRange = range.startContainer === commonCon && this.format.isLine(commonCon);
492
+ const startCon = formatRange ? commonCon.childNodes[startOff] || commonCon.childNodes[0] || range.startContainer : range.startContainer;
493
+ const endCon = formatRange ? commonCon.childNodes[endOff] || commonCon.childNodes[commonCon.childNodes.length - 1] || range.endContainer : range.endContainer;
494
+
495
+ if (!insertListCell) {
496
+ if (!afterNode) {
497
+ parentNode = startCon;
498
+ if (startCon.nodeType === 3) {
499
+ parentNode = startCon.parentNode;
500
+ }
501
+
502
+ /** No Select range node */
503
+ if (range.collapsed) {
504
+ if (commonCon.nodeType === 3) {
505
+ if (commonCon.textContent.length > endOff) afterNode = /** @type {Text} */ (commonCon).splitText(endOff);
506
+ else afterNode = commonCon.nextSibling;
507
+ } else {
508
+ if (!dom.check.isBreak(parentNode)) {
509
+ const c = parentNode.childNodes[startOff];
510
+ const focusNode = c?.nodeType === 3 && dom.check.isZeroWidth(c) && dom.check.isBreak(c.nextSibling) ? c.nextSibling : c;
511
+ if (focusNode) {
512
+ if (!focusNode.nextSibling && dom.check.isBreak(focusNode)) {
513
+ parentNode.removeChild(focusNode);
514
+ afterNode = null;
515
+ } else {
516
+ afterNode = dom.check.isBreak(focusNode) && !dom.check.isBreak(oNode) ? focusNode : focusNode.nextSibling;
517
+ }
518
+ } else {
519
+ afterNode = null;
520
+ }
521
+ } else {
522
+ afterNode = parentNode;
523
+ parentNode = parentNode.parentNode;
524
+ }
525
+ }
526
+ } else {
527
+ /** Select range nodes */
528
+ const isSameContainer = startCon === endCon;
529
+
530
+ if (isSameContainer) {
531
+ if (dom.check.isEdgePoint(endCon, endOff)) afterNode = endCon.nextSibling;
532
+ else afterNode = /** @type {Text} */ (endCon).splitText(endOff);
533
+
534
+ let removeNode = startCon;
535
+ if (!dom.check.isEdgePoint(startCon, startOff)) removeNode = /** @type {Text} */ (startCon).splitText(startOff);
536
+
537
+ parentNode.removeChild(removeNode);
538
+ if (parentNode.childNodes.length === 0 && isFormats) {
539
+ /** @type {HTMLElement} */ (parentNode).innerHTML = '<br>';
540
+ }
541
+ } else {
542
+ const removedTag = this.remove();
543
+ const container = removedTag.container;
544
+ const prevContainer = removedTag.prevContainer;
545
+
546
+ if (container?.childNodes.length === 0 && isFormats) {
547
+ if (this.format.isLine(container)) {
548
+ container.innerHTML = '<br>';
549
+ } else if (this.format.isBlock(container)) {
550
+ container.innerHTML = '<' + this.options.get('defaultLine') + '><br></' + this.options.get('defaultLine') + '>';
551
+ }
552
+ }
553
+
554
+ if (dom.check.isListCell(container) && oNode.nodeType === 3) {
555
+ parentNode = container;
556
+ afterNode = null;
557
+ } else if (!isFormats && prevContainer) {
558
+ parentNode = prevContainer.nodeType === 3 ? prevContainer.parentNode : prevContainer;
559
+ if (parentNode.contains(container)) {
560
+ let sameParent = true;
561
+ afterNode = container;
562
+ while (afterNode.parentNode && afterNode.parentNode !== parentNode) {
563
+ afterNode = afterNode.parentNode;
564
+ sameParent = false;
565
+ }
566
+ if (sameParent && container === prevContainer) afterNode = afterNode.nextSibling;
567
+ } else {
568
+ afterNode = null;
569
+ }
570
+ } else if (dom.check.isWysiwygFrame(container) && !this.format.isLine(oNode)) {
571
+ parentNode = container.appendChild(dom.utils.createElement(this.options.get('defaultLine')));
572
+ afterNode = null;
573
+ } else {
574
+ afterNode = isFormats ? endCon : container === prevContainer ? container.nextSibling : container;
575
+ parentNode = !afterNode || !afterNode.parentNode ? commonCon : afterNode.parentNode;
576
+ }
577
+
578
+ while (afterNode && !this.format.isLine(afterNode) && afterNode.parentNode !== commonCon) {
579
+ afterNode = afterNode.parentNode;
580
+ }
581
+ }
582
+ }
583
+ } else {
584
+ // has afterNode
585
+ parentNode = afterNode.parentNode;
586
+ afterNode = afterNode.nextSibling;
587
+ originAfter = true;
588
+ }
589
+ }
590
+
591
+ try {
592
+ // set node
593
+ const wysiwyg = this.editor.frameContext.get('wysiwyg');
594
+ if (!insertListCell) {
595
+ if (dom.check.isWysiwygFrame(afterNode) || parentNode === wysiwyg.parentNode) {
596
+ parentNode = wysiwyg;
597
+ afterNode = null;
598
+ }
599
+
600
+ if (this.format.isLine(oNode) || this.format.isBlock(oNode) || (!dom.check.isListCell(parentNode) && this.component.isBasic(oNode))) {
601
+ const oldParent = parentNode;
602
+ if (dom.check.isList(afterNode)) {
603
+ parentNode = afterNode;
604
+ afterNode = null;
605
+ } else if (dom.check.isListCell(afterNode)) {
606
+ parentNode = afterNode.previousElementSibling || afterNode;
607
+ } else if (!originAfter && !afterNode) {
608
+ const r = this.remove();
609
+ 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;
610
+ const rangeCon = dom.check.isWysiwygFrame(container) || this.format.isBlock(container);
611
+ parentNode = rangeCon ? container : container.parentNode;
612
+ afterNode = rangeCon ? null : container.nextSibling;
613
+ }
614
+
615
+ if (oldParent.childNodes.length === 0 && parentNode !== oldParent) dom.utils.removeItem(oldParent);
616
+ }
617
+
618
+ if (isFormats && !freeFormat && !this.format.isBlock(parentNode) && !dom.check.isListCell(parentNode) && !dom.check.isWysiwygFrame(parentNode)) {
619
+ afterNode = parentNode.nextElementSibling;
620
+ parentNode = parentNode.parentNode;
621
+ }
622
+
623
+ if (dom.check.isWysiwygFrame(parentNode) && (oNode.nodeType === 3 || dom.check.isBreak(oNode))) {
624
+ const formatNode = dom.utils.createElement(this.options.get('defaultLine'), null, oNode);
625
+ fNode = oNode;
626
+ oNode = formatNode;
627
+ }
628
+ }
629
+
630
+ // insert--
631
+ if (insertListCell) {
632
+ if (!tempParentNode.parentNode) {
633
+ parentNode = wysiwyg;
634
+ afterNode = null;
635
+ } else {
636
+ parentNode = tempParentNode;
637
+ afterNode = tempAfterNode;
638
+ }
639
+ } else {
640
+ afterNode = parentNode === afterNode ? parentNode.lastChild : afterNode;
641
+ }
642
+
643
+ if (dom.check.isListCell(oNode) && !dom.check.isList(parentNode)) {
644
+ if (dom.check.isListCell(parentNode)) {
645
+ afterNode = parentNode.nextElementSibling;
646
+ parentNode = parentNode.parentNode;
647
+ } else {
648
+ const ul = dom.utils.createElement('ol');
649
+ parentNode.insertBefore(ul, afterNode);
650
+ parentNode = ul;
651
+ afterNode = null;
652
+ }
653
+ insertListCell = true;
654
+ }
655
+
656
+ this._checkDuplicateNode(oNode, parentNode);
657
+ parentNode.insertBefore(oNode, afterNode);
658
+
659
+ if (insertListCell) {
660
+ if (dom.check.isZeroWidth(line.textContent.trim())) {
661
+ dom.utils.removeItem(line);
662
+ oNode = oNode.lastChild;
663
+ } else {
664
+ const chList = dom.utils.arrayFind(line.children, dom.check.isList);
665
+ if (chList) {
666
+ if (oNode !== chList) {
667
+ oNode.appendChild(chList);
668
+ oNode = chList.previousSibling;
669
+ } else {
670
+ parentNode.appendChild(oNode);
671
+ oNode = parentNode;
672
+ }
673
+
674
+ if (dom.check.isZeroWidth(line.textContent.trim())) {
675
+ dom.utils.removeItem(line);
676
+ }
677
+ }
678
+ }
679
+ }
680
+ } catch (error) {
681
+ parentNode.appendChild(oNode);
682
+ console.warn('[SUNEDITOR.html.insertNode.warn]', error);
683
+ } finally {
684
+ if (fNode) oNode = fNode;
685
+
686
+ const dupleNodes = parentNode.querySelectorAll('[data-duple]');
687
+ if (dupleNodes.length > 0) {
688
+ for (let i = 0, len = dupleNodes.length, d, c, ch, parent; i < len; i++) {
689
+ d = dupleNodes[i];
690
+ ch = d.childNodes;
691
+ parent = d.parentNode;
692
+
693
+ while (ch[0]) {
694
+ c = ch[0];
695
+ parent.insertBefore(c, d);
696
+ }
697
+
698
+ if (d === oNode) oNode = c;
699
+ dom.utils.removeItem(d);
700
+ }
701
+ }
702
+
703
+ if ((this.format.isLine(oNode) || this.component.isBasic(oNode)) && startCon === endCon) {
704
+ const cItem = this.format.getLine(commonCon, null);
705
+ if (cItem?.nodeType === 1 && dom.check.isEmptyLine(cItem)) {
706
+ dom.utils.removeItem(cItem);
707
+ }
708
+ }
709
+
710
+ if (freeFormat && (this.format.isLine(oNode) || this.format.isBlock(oNode))) {
711
+ oNode = this._setIntoFreeFormat(oNode);
712
+ }
713
+
714
+ if (!this.component.isBasic(oNode)) {
715
+ let offset = 1;
716
+ if (oNode.nodeType === 3) {
717
+ offset = oNode.textContent.length;
718
+ this.selection.setRange(oNode, offset, oNode, offset);
719
+ } else if (!dom.check.isBreak(oNode) && !dom.check.isListCell(oNode) && this.format.isLine(parentNode)) {
720
+ let zeroWidth = null;
721
+ if (!oNode.previousSibling || dom.check.isBreak(oNode.previousSibling)) {
722
+ zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
723
+ oNode.parentNode.insertBefore(zeroWidth, oNode);
724
+ }
725
+
726
+ if (!oNode.nextSibling || dom.check.isBreak(oNode.nextSibling)) {
727
+ zeroWidth = dom.utils.createTextNode(unicode.zeroWidthSpace);
728
+ oNode.parentNode.insertBefore(zeroWidth, oNode.nextSibling);
729
+ }
730
+
731
+ if (this.format._isIgnoreNodeChange(oNode)) {
732
+ oNode = oNode.nextSibling;
733
+ offset = 0;
734
+ }
735
+
736
+ this.selection.setRange(oNode, offset, oNode, offset);
737
+ }
738
+ }
739
+
740
+ result = oNode;
741
+ }
742
+
743
+ return result;
744
+ },
745
+
746
+ /**
747
+ * @this {HTMLThis}
748
+ * @description Delete the selected range.
749
+ * @returns {{container: Node, offset: number, commonCon?: Node|null, prevContainer?: Node|null}}
750
+ * - container: "the last element after deletion"
751
+ * - offset: "offset"
752
+ * - commonCon: "commonAncestorContainer"
753
+ * - prevContainer: "previousElementSibling Of the deleted area"
754
+ */
755
+ remove() {
756
+ this.selection._resetRangeToTextNode();
757
+
758
+ const range = this.selection.getRange();
759
+ const isStartEdge = range.startOffset === 0;
760
+ const isEndEdge = dom.check.isEdgePoint(range.endContainer, range.endOffset, 'end');
761
+ let prevContainer = null;
762
+ let startPrevEl = null;
763
+ let endNextEl = null;
764
+ if (isStartEdge) {
765
+ startPrevEl = this.format.getLine(range.startContainer);
766
+ prevContainer = startPrevEl ? startPrevEl.previousElementSibling : null;
767
+ startPrevEl = startPrevEl ? prevContainer : startPrevEl;
768
+ }
769
+ if (isEndEdge) {
770
+ endNextEl = this.format.getLine(range.endContainer);
771
+ endNextEl = endNextEl ? endNextEl.nextElementSibling : endNextEl;
772
+ }
773
+
774
+ let container,
775
+ offset = 0;
776
+ let startCon = range.startContainer;
777
+ let endCon = range.endContainer;
778
+ let startOff = range.startOffset;
779
+ let endOff = range.endOffset;
780
+ const commonCon = /** @type {HTMLElement} */ (range.commonAncestorContainer.nodeType === 3 && range.commonAncestorContainer.parentNode === startCon.parentNode ? startCon.parentNode : range.commonAncestorContainer);
781
+ if (commonCon === startCon && commonCon === endCon) {
782
+ if (this.component.isBasic(commonCon)) {
783
+ const compInfo = this.component.get(commonCon);
784
+ const compContainer = compInfo.container;
785
+ const parent = compContainer.parentElement;
786
+
787
+ const next = compContainer.nextSibling || compContainer.previousSibling;
788
+ const nextOffset = next === compContainer.previousSibling ? next?.textContent?.length || 1 : 0;
789
+ const parentNext = parent.nextElementSibling || parent.previousElementSibling;
790
+ const parentNextOffset = parentNext === parent.previousElementSibling ? parentNext?.textContent?.length || 1 : 0;
791
+
792
+ dom.utils.removeItem(compContainer);
793
+
794
+ if (this.format.isLine(parent)) {
795
+ if (parent.childNodes.length === 0) {
796
+ dom.utils.removeItem(parent);
797
+ return {
798
+ container: parentNext,
799
+ offset: parentNextOffset,
800
+ commonCon
801
+ };
802
+ } else {
803
+ return {
804
+ container: next,
805
+ offset: nextOffset,
806
+ commonCon
807
+ };
808
+ }
809
+ } else {
810
+ return {
811
+ container: parentNext,
812
+ offset: parentNextOffset,
813
+ commonCon
814
+ };
815
+ }
816
+ } else {
817
+ if ((commonCon.nodeType === 1 && startOff === 0 && endOff === 1) || (commonCon.nodeType === 3 && startOff === 0 && endOff === commonCon.textContent.length)) {
818
+ const nextEl = dom.query.getNextDeepestNode(commonCon, this.editor.frameContext.get('wysiwyg'));
819
+ const prevEl = dom.query.getPreviousDeepestNode(commonCon, this.editor.frameContext.get('wysiwyg'));
820
+ const line = this.format.getLine(commonCon);
821
+ dom.utils.removeItem(commonCon);
822
+
823
+ let rEl = nextEl || prevEl;
824
+ let rOffset = nextEl ? 0 : rEl?.nodeType === 3 ? rEl.textContent.length : 1;
825
+
826
+ const npEl = this.format.getLine(rEl) || this.component.get(rEl);
827
+ if (line !== npEl) {
828
+ rEl = /** @type {Node} */ (npEl);
829
+ rOffset = rOffset === 0 ? 0 : 1;
830
+ }
831
+
832
+ if (dom.check.isZeroWidth(line) && !line.contains(rEl)) {
833
+ dom.utils.removeItem(line);
834
+ }
835
+
836
+ return {
837
+ container: rEl,
838
+ offset: rOffset,
839
+ commonCon
840
+ };
841
+ }
842
+
843
+ startCon = commonCon.children[startOff];
844
+ endCon = commonCon.children[endOff];
845
+ startOff = endOff = 0;
846
+ }
847
+ }
848
+
849
+ if (!startCon || !endCon)
850
+ return {
851
+ container: commonCon,
852
+ offset: 0,
853
+ commonCon
854
+ };
855
+
856
+ if (startCon === endCon && range.collapsed) {
857
+ if (dom.check.isZeroWidth(startCon.textContent?.substring(startOff))) {
858
+ return {
859
+ container: startCon,
860
+ offset: startOff,
861
+ prevContainer: startCon && startCon.parentNode ? startCon : null,
862
+ commonCon
863
+ };
864
+ }
865
+ }
866
+
867
+ let beforeNode = null;
868
+ let afterNode = null;
869
+
870
+ const childNodes = dom.query.getListChildNodes(commonCon, null);
871
+ let startIndex = dom.utils.getArrayIndex(childNodes, startCon);
872
+ let endIndex = dom.utils.getArrayIndex(childNodes, endCon);
873
+
874
+ if (childNodes.length > 0 && startIndex > -1 && endIndex > -1) {
875
+ for (let i = startIndex + 1, startNode = startCon; i >= 0; i--) {
876
+ if (childNodes[i] === startNode.parentNode && childNodes[i].firstChild === startNode && startOff === 0) {
877
+ startIndex = i;
878
+ startNode = startNode.parentNode;
879
+ }
880
+ }
881
+
882
+ for (let i = endIndex - 1, endNode = endCon; i > startIndex; i--) {
883
+ if (childNodes[i] === endNode.parentNode && childNodes[i].nodeType === 1) {
884
+ childNodes.splice(i, 1);
885
+ endNode = endNode.parentNode;
886
+ --endIndex;
887
+ }
888
+ }
889
+ } else {
890
+ if (childNodes.length === 0) {
891
+ if (this.format.isLine(commonCon) || this.format.isBlock(commonCon) || dom.check.isWysiwygFrame(commonCon) || dom.check.isBreak(commonCon) || dom.check.isMedia(commonCon)) {
892
+ return {
893
+ container: commonCon,
894
+ offset: 0,
895
+ commonCon
896
+ };
897
+ } else if (dom.check.isText(commonCon)) {
898
+ return {
899
+ container: commonCon,
900
+ offset: endOff,
901
+ commonCon
902
+ };
903
+ }
904
+ childNodes.push(commonCon);
905
+ startCon = endCon = commonCon;
906
+ } else {
907
+ startCon = endCon = childNodes[0];
908
+ if (dom.check.isBreak(startCon) || dom.check.isZeroWidth(startCon)) {
909
+ return {
910
+ container: dom.check.isMedia(commonCon) ? commonCon : startCon,
911
+ offset: 0,
912
+ commonCon
913
+ };
914
+ }
915
+ }
916
+
917
+ startIndex = endIndex = 0;
918
+ }
919
+
920
+ const _isText = dom.check.isText;
921
+ const _isElement = dom.check.isElement;
922
+ for (let i = startIndex; i <= endIndex; i++) {
923
+ const item = /** @type {Text} */ (childNodes[i]);
924
+
925
+ if (_isText(item) && (item.data === undefined || item.length === 0)) {
926
+ this._nodeRemoveListItem(item);
927
+ continue;
928
+ }
929
+
930
+ if (item === startCon) {
931
+ if (_isElement(startCon)) {
932
+ if (this.component.is(startCon)) continue;
933
+ else beforeNode = dom.utils.createTextNode(startCon.textContent);
934
+ } else {
935
+ const sc = /** @type {Text} */ (startCon);
936
+ const ec = /** @type {Text} */ (endCon);
937
+ if (item === endCon) {
938
+ beforeNode = dom.utils.createTextNode(sc.substringData(0, startOff) + ec.substringData(endOff, ec.length - endOff));
939
+ offset = startOff;
940
+ } else {
941
+ beforeNode = dom.utils.createTextNode(sc.substringData(0, startOff));
942
+ }
943
+ }
944
+
945
+ if (beforeNode.length > 0) {
946
+ /** @type {Text} */ (startCon).data = beforeNode.data;
947
+ } else {
948
+ this._nodeRemoveListItem(startCon);
949
+ }
950
+
951
+ if (item === endCon) break;
952
+ continue;
953
+ }
954
+
955
+ if (item === endCon) {
956
+ if (_isText(endCon)) {
957
+ afterNode = dom.utils.createTextNode(endCon.substringData(endOff, endCon.length - endOff));
958
+ } else {
959
+ if (this.component.is(endCon)) continue;
960
+ else afterNode = dom.utils.createTextNode(endCon.textContent);
961
+ }
962
+
963
+ if (afterNode.length > 0) {
964
+ /** @type {Text} */ (endCon).data = afterNode.data;
965
+ } else {
966
+ this._nodeRemoveListItem(endCon);
967
+ }
968
+
969
+ continue;
970
+ }
971
+
972
+ this._nodeRemoveListItem(item);
973
+ }
974
+
975
+ const endUl = dom.query.getParentElement(endCon, 'ul');
976
+ const startLi = dom.query.getParentElement(startCon, 'li');
977
+ if (endUl && startLi && startLi.contains(endUl)) {
978
+ container = endUl.previousSibling;
979
+ offset = container.textContent.length;
980
+ } else {
981
+ container = endCon && endCon.parentNode ? endCon : startCon && startCon.parentNode ? startCon : range.endContainer || range.startContainer;
982
+ if (isStartEdge || isEndEdge) {
983
+ if (isEndEdge) {
984
+ if (container.nodeType === 1 && container.childNodes.length === 0) {
985
+ container.appendChild(dom.utils.createElement('BR'));
986
+ offset = 1;
987
+ } else {
988
+ offset = container.textContent.length;
989
+ }
990
+ } else {
991
+ offset = 0;
992
+ }
993
+ }
994
+ }
995
+
996
+ if (!this.format.getLine(container) && !(startCon && startCon.parentNode)) {
997
+ if (endNextEl) {
998
+ container = endNextEl;
999
+ offset = 0;
1000
+ } else if (startPrevEl) {
1001
+ container = startPrevEl;
1002
+ offset = 1;
1003
+ }
1004
+ }
1005
+
1006
+ if (!dom.check.isWysiwygFrame(container) && container.childNodes.length === 0) {
1007
+ const rc = this.nodeTransform.removeAllParents(container, null, null);
1008
+ if (rc) container = rc.sc || rc.ec || this.editor.frameContext.get('wysiwyg');
1009
+ }
1010
+
1011
+ // set range
1012
+ this.selection.setRange(container, offset, container, offset);
1013
+
1014
+ return {
1015
+ container,
1016
+ offset,
1017
+ prevContainer,
1018
+ commonCon
1019
+ };
1020
+ },
1021
+
1022
+ /**
1023
+ * @this {HTMLThis}
1024
+ * @description Gets the current content
1025
+ * @param {Object} [options] Options
1026
+ * @param {boolean} [options.withFrame=false] Gets the current content with containing parent div.sun-editor-editable (<div class="sun-editor-editable">{content}</div>).
1027
+ * Ignored for targetOptions.get('iframe_fullPage') is true.
1028
+ * @param {boolean} [options.includeFullPage=false] Return only the content of the body without headers when the "iframe_fullPage" option is true
1029
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1030
+ * @returns {string|Object<*, string>}
1031
+ */
1032
+ get({ withFrame, includeFullPage, rootKey } = {}) {
1033
+ if (!rootKey) rootKey = [this.status.rootKey];
1034
+ else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1035
+
1036
+ const prevrootKey = this.status.rootKey;
1037
+ const resultValue = {};
1038
+ for (let i = 0, len = rootKey.length, r; i < len; i++) {
1039
+ this.editor.changeFrameContext(rootKey[i]);
1040
+
1041
+ const fc = this.editor.frameContext;
1042
+ const renderHTML = dom.utils.createElement('DIV', null, this._convertToCode(fc.get('wysiwyg'), true));
1043
+
1044
+ const isTableCell = dom.check.isTableCell;
1045
+ const isEmptyLine = dom.check.isEmptyLine;
1046
+ const editableEls = [];
1047
+ const emptyCells = [];
1048
+ dom.query.getListChildren(renderHTML, (current) => {
1049
+ if (current.hasAttribute('contenteditable')) {
1050
+ editableEls.push(current);
1051
+ }
1052
+
1053
+ const parent = current.parentElement;
1054
+ if (isTableCell(parent) && parent.children.length <= 1 && isEmptyLine(current)) {
1055
+ emptyCells.push(parent);
1056
+ }
1057
+ return false;
1058
+ });
1059
+
1060
+ for (let j = 0, jlen = editableEls.length; j < jlen; j++) {
1061
+ editableEls[j].removeAttribute('contenteditable');
1062
+ }
1063
+ for (let j = 0, jlen = emptyCells.length; j < jlen; j++) {
1064
+ emptyCells[j].innerHTML = '<br>';
1065
+ }
1066
+
1067
+ const content = renderHTML.innerHTML;
1068
+ if (this.editor.frameOptions.get('iframe_fullPage')) {
1069
+ if (includeFullPage) {
1070
+ const attrs = dom.utils.getAttributesToString(fc.get('_wd').body, ['contenteditable']);
1071
+ r = `<!DOCTYPE html><html>${fc.get('_wd').head.outerHTML}<body ${attrs}>${content}</body></html>`;
1072
+ } else {
1073
+ r = content;
1074
+ }
1075
+ } else {
1076
+ r = withFrame ? `<div class="${this.options.get('_editableClass') + '' + (this.options.get('_rtl') ? ' se-rtl' : '')}">${content}</div>` : renderHTML.innerHTML;
1077
+ }
1078
+
1079
+ resultValue[rootKey[i]] = r;
1080
+ }
1081
+
1082
+ this.editor.changeFrameContext(prevrootKey);
1083
+ return rootKey.length > 1 ? resultValue : resultValue[rootKey[0]];
1084
+ },
1085
+
1086
+ /**
1087
+ * @this {HTMLThis}
1088
+ * @description Sets the HTML string to the editor content
1089
+ * @param {string} html HTML string
1090
+ * @param {Object} [options] Options
1091
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1092
+ */
1093
+ set(html, { rootKey } = {}) {
1094
+ this.ui._offCurrentController();
1095
+ this.selection.removeRange();
1096
+ const convertValue = html === null || html === undefined ? '' : this.clean(html, { forceFormat: true, whitelist: null, blacklist: null });
1097
+
1098
+ if (!rootKey) rootKey = [this.status.rootKey];
1099
+ else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1100
+
1101
+ for (let i = 0; i < rootKey.length; i++) {
1102
+ this.editor.changeFrameContext(rootKey[i]);
1103
+
1104
+ if (!this.editor.frameContext.get('isCodeView')) {
1105
+ this.editor.frameContext.get('wysiwyg').innerHTML = convertValue;
1106
+ this.editor._resetComponents();
1107
+ this.history.push(false, rootKey[i]);
1108
+ } else {
1109
+ const value = this._convertToCode(convertValue, false);
1110
+ this.viewer._setCodeView(value);
1111
+ }
1112
+ }
1113
+ },
1114
+
1115
+ /**
1116
+ * @this {HTMLThis}
1117
+ * @description Add content to the end of content.
1118
+ * @param {string} html Content to Input
1119
+ * @param {Object} [options] Options
1120
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1121
+ */
1122
+ add(html, { rootKey } = {}) {
1123
+ this.ui._offCurrentController();
1124
+
1125
+ if (!rootKey) rootKey = [this.status.rootKey];
1126
+ else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1127
+
1128
+ for (let i = 0; i < rootKey.length; i++) {
1129
+ this.editor.changeFrameContext(rootKey[i]);
1130
+ const convertValue = this.clean(html, { forceFormat: true, whitelist: null, blacklist: null });
1131
+
1132
+ if (!this.editor.frameContext.get('isCodeView')) {
1133
+ const temp = dom.utils.createElement('DIV', null, convertValue);
1134
+ const children = temp.children;
1135
+ const len = children.length;
1136
+ for (let j = 0; j < len; j++) {
1137
+ if (!children[j]) continue;
1138
+ this.editor.frameContext.get('wysiwyg').appendChild(children[j]);
1139
+ }
1140
+ this.history.push(false, rootKey[i]);
1141
+ this.selection.scrollTo(children[len - 1]);
1142
+ } else {
1143
+ this.viewer._setCodeView(this.viewer._getCodeView() + '\n' + this._convertToCode(convertValue, false));
1144
+ }
1145
+ }
1146
+ },
1147
+
1148
+ /**
1149
+ * @this {HTMLThis}
1150
+ * @description Gets the current content to JSON data
1151
+ * @param {Object} [options] Options
1152
+ * @param {boolean} [options.withFrame=false] Gets the current content with containing parent div.sun-editor-editable (<div class="sun-editor-editable">{content}</div>).
1153
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1154
+ * @returns {Object<string, *>} JSON data
1155
+ */
1156
+ getJson({ withFrame, rootKey } = {}) {
1157
+ return converter.htmlToJson(this.get({ withFrame, rootKey }));
1158
+ },
1159
+
1160
+ /**
1161
+ * @this {HTMLThis}
1162
+ * @description Sets the JSON data to the editor content
1163
+ * @param {Object<string, *>} jsdonData HTML string
1164
+ * @param {Object} [options] Options
1165
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1166
+ */
1167
+ setJson(jsdonData, { rootKey } = {}) {
1168
+ this.set(converter.jsonToHtml(jsdonData), { rootKey });
1169
+ },
1170
+
1171
+ /**
1172
+ * @this {HTMLThis}
1173
+ * @description Call "clipboard.write" to copy the contents and display a success/failure toast message.
1174
+ * @param {Element|Text|string} content Content to be copied to the clipboard
1175
+ * @returns {Promise<boolean>} Success or failure
1176
+ */
1177
+ async copy(content) {
1178
+ try {
1179
+ await clipboard.write(content);
1180
+ this.editor.ui.showToast(this.lang.message_copy_success, this.options.get('toastMessageTime').copy);
1181
+ return true;
1182
+ } catch (err) {
1183
+ console.error('[SUNEDITOR.html.copy.fail] :', err);
1184
+ this.editor.ui.showToast(this.lang.message_copy_fail, this.options.get('toastMessageTime').copy, 'error');
1185
+ return false;
1186
+ }
1187
+ },
1188
+
1189
+ /**
1190
+ * @this {HTMLThis}
1191
+ * @description Sets the content of the iframe's head tag and body tag when using the "iframe" or "iframe_fullPage" option.
1192
+ * @param {{head: string, body: string}} ctx { head: HTML string, body: HTML string}
1193
+ * @param {Object} [options] Options
1194
+ * @param {number|Array<number>} [options.rootKey=null] Root index
1195
+ */
1196
+ setFullPage(ctx, { rootKey } = {}) {
1197
+ if (!this.editor.frameOptions.get('iframe')) return false;
1198
+
1199
+ if (!rootKey) rootKey = [this.status.rootKey];
1200
+ else if (!Array.isArray(rootKey)) rootKey = [rootKey];
1201
+
1202
+ for (let i = 0; i < rootKey.length; i++) {
1203
+ this.editor.changeFrameContext(rootKey[i]);
1204
+ if (ctx.head) this.editor.frameContext.get('_wd').head.innerHTML = ctx.head.replace(this.__disallowedTagsRegExp, '');
1205
+ if (ctx.body) this.editor.frameContext.get('_wd').body.innerHTML = this.clean(ctx.body, { forceFormat: true, whitelist: null, blacklist: null });
1206
+ this.editor._resetComponents();
1207
+ }
1208
+ },
1209
+
1210
+ /**
1211
+ * @this {HTMLThis}
1212
+ * @description HTML code compression
1213
+ * @param {string} html HTML string
1214
+ * @returns {string} HTML string
1215
+ */
1216
+ compress(html) {
1217
+ return html.replace(/>\s+</g, '> <').replace(/\n/g, '').trim();
1218
+ },
1219
+
1220
+ /**
1221
+ * @private
1222
+ * @this {HTMLThis}
1223
+ * @description construct wysiwyg area element to html string
1224
+ * @param {Node|string} html WYSIWYG element (this.editor.frameContext.get('wysiwyg')) or HTML string.
1225
+ * @param {boolean} comp If true, does not line break and indentation of tags.
1226
+ * @returns {string}
1227
+ */
1228
+ _convertToCode(html, comp) {
1229
+ let returnHTML = '';
1230
+ const wRegExp = RegExp;
1231
+ const brReg = new wRegExp('^(BLOCKQUOTE|PRE|TABLE|THEAD|TBODY|TR|TH|TD|OL|UL|IMG|IFRAME|VIDEO|AUDIO|FIGURE|FIGCAPTION|HR|BR|CANVAS|SELECT)$', 'i');
1232
+ const wDoc = typeof html === 'string' ? this._d.createRange().createContextualFragment(html) : html;
1233
+ const isFormat = (current) => {
1234
+ return this.format.isLine(current) || this.component.is(current);
1235
+ };
1236
+ const brChar = comp ? '' : '\n';
1237
+
1238
+ const codeSize = comp ? 0 : this.status.codeIndentSize * 1;
1239
+ const indentSize = codeSize > 0 ? new Array(codeSize + 1).join(' ') : '';
1240
+
1241
+ (function recursionFunc(element, indent) {
1242
+ const children = element.childNodes;
1243
+ const elementRegTest = brReg.test(element.nodeName);
1244
+ const elementIndent = elementRegTest ? indent : '';
1245
+
1246
+ for (let i = 0, len = children.length, node, br, lineBR, nodeRegTest, tag, tagIndent; i < len; i++) {
1247
+ node = children[i];
1248
+ nodeRegTest = brReg.test(node.nodeName);
1249
+ br = nodeRegTest ? brChar : '';
1250
+ lineBR = isFormat(node) && !elementRegTest && !/^(TH|TD)$/i.test(element.nodeName) ? brChar : '';
1251
+
1252
+ if (node.nodeType === 8) {
1253
+ returnHTML += '\n<!-- ' + node.textContent.trim() + ' -->' + br;
1254
+ continue;
1255
+ }
1256
+ if (node.nodeType === 3) {
1257
+ if (!dom.check.isList(node.parentElement)) returnHTML += converter.htmlToEntity(/^\n+$/.test(/** @type {Text} */ (node).data) ? '' : /** @type {Text} */ (node).data);
1258
+ continue;
1259
+ }
1260
+ if (node.childNodes.length === 0) {
1261
+ returnHTML += (/^HR$/i.test(node.nodeName) ? brChar : '') + (/^PRE$/i.test(node.parentElement.nodeName) && /^BR$/i.test(node.nodeName) ? '' : elementIndent) + /** @type {HTMLElement} */ (node).outerHTML + br;
1262
+ continue;
1263
+ }
1264
+
1265
+ if (!(/** @type {HTMLElement} */ (node).outerHTML)) {
1266
+ returnHTML += new XMLSerializer().serializeToString(node);
1267
+ } else {
1268
+ tag = node.nodeName.toLowerCase();
1269
+ tagIndent = elementIndent || nodeRegTest ? indent : '';
1270
+ returnHTML += (lineBR || (elementRegTest ? '' : br)) + tagIndent + /** @type {HTMLElement} */ (node).outerHTML.match(wRegExp('<' + tag + '[^>]*>', 'i'))[0] + br;
1271
+ recursionFunc(node, indent + indentSize + '');
1272
+ returnHTML += (/\n$/.test(returnHTML) ? tagIndent : '') + '</' + tag + '>' + (lineBR || br || elementRegTest ? brChar : /^(TH|TD)$/i.test(node.nodeName) ? brChar : '');
1273
+ }
1274
+ }
1275
+ })(wDoc, '');
1276
+
1277
+ return returnHTML.trim() + brChar;
1278
+ },
1279
+
1280
+ /**
1281
+ * @private
1282
+ * @this {HTMLThis}
1283
+ * @description Checks whether the given list item node should be removed and handles necessary clean-up.
1284
+ * @param {Node} item The list item node to be checked.
1285
+ */
1286
+ _nodeRemoveListItem(item) {
1287
+ const line = this.format.getLine(item, null);
1288
+ dom.utils.removeItem(item);
1289
+
1290
+ if (!dom.check.isListCell(line)) return;
1291
+
1292
+ this.nodeTransform.removeAllParents(line, null, null);
1293
+
1294
+ if (dom.check.isList(line?.firstChild)) {
1295
+ line.insertBefore(dom.utils.createTextNode(unicode.zeroWidthSpace), line.firstChild);
1296
+ }
1297
+ },
1298
+
1299
+ /**
1300
+ * @private
1301
+ * @this {HTMLThis}
1302
+ * @description Recursive function when used to place a node in "BrLine" in "html.insertNode"
1303
+ * @param {Node} oNode Node to be inserted
1304
+ * @returns {Node} "oNode"
1305
+ */
1306
+ _setIntoFreeFormat(oNode) {
1307
+ const parentNode = oNode.parentNode;
1308
+ let oNodeChildren, lastONode;
1309
+
1310
+ while (this.format.isLine(oNode) || this.format.isBlock(oNode)) {
1311
+ oNodeChildren = oNode.childNodes;
1312
+ lastONode = null;
1313
+
1314
+ while (oNodeChildren[0]) {
1315
+ lastONode = oNodeChildren[0];
1316
+ if (this.format.isLine(lastONode) || this.format.isBlock(lastONode)) {
1317
+ this._setIntoFreeFormat(lastONode);
1318
+ if (!oNode.parentNode) break;
1319
+ oNodeChildren = oNode.childNodes;
1320
+ continue;
1321
+ }
1322
+
1323
+ parentNode.insertBefore(lastONode, oNode);
1324
+ }
1325
+
1326
+ if (oNode.childNodes.length === 0) dom.utils.removeItem(oNode);
1327
+ oNode = dom.utils.createElement('BR');
1328
+ parentNode.insertBefore(oNode, lastONode.nextSibling);
1329
+ }
1330
+
1331
+ return oNode;
1332
+ },
1333
+
1334
+ /**
1335
+ * @private
1336
+ * @this {HTMLThis}
1337
+ * @description Returns HTML string according to tag type and configurati isExcludeFormat.
1338
+ * @param {Node} node Node
1339
+ * @param {boolean} forceFormat If true, text nodes that do not have a format node is wrapped with the format tag.
1340
+ */
1341
+ _makeLine(node, forceFormat) {
1342
+ const defaultLine = this.options.get('defaultLine');
1343
+ // element
1344
+ if (node.nodeType === 1) {
1345
+ if (this.__disallowedTagNameRegExp.test(node.nodeName)) return '';
1346
+ if (dom.check.isExcludeFormat(node)) return node.outerHTML;
1347
+
1348
+ const ch =
1349
+ dom.query.getListChildNodes(node, (current) => {
1350
+ return dom.check.isSpanWithoutAttr(current) && !dom.query.getParentElement(current, dom.check.isExcludeFormat);
1351
+ }) || [];
1352
+ for (let i = ch.length - 1, c; i >= 0; i--) {
1353
+ c = /** @type {HTMLElement} */ (ch[i]);
1354
+ c.outerHTML = c.innerHTML;
1355
+ }
1356
+
1357
+ if (
1358
+ !forceFormat ||
1359
+ this.format.isLine(node) ||
1360
+ this.format.isBlock(node) ||
1361
+ this.component.is(node) ||
1362
+ dom.check.isMedia(node) ||
1363
+ dom.check.isFigure(node) ||
1364
+ (dom.check.isAnchor(node) && dom.check.isMedia(node.firstElementChild))
1365
+ ) {
1366
+ const n = /** @type {HTMLElement} */ (node);
1367
+ return dom.check.isSpanWithoutAttr(node) ? n.innerHTML : n.outerHTML;
1368
+ } else {
1369
+ const n = /** @type {HTMLElement} */ (node);
1370
+ return '<' + defaultLine + '>' + (dom.check.isSpanWithoutAttr(node) ? n.innerHTML : n.outerHTML) + '</' + defaultLine + '>';
1371
+ }
1372
+ }
1373
+ // text
1374
+ if (node.nodeType === 3) {
1375
+ if (!forceFormat) return converter.htmlToEntity(node.textContent);
1376
+ const textArray = node.textContent.split(/\n/g);
1377
+ let html = '';
1378
+ for (let i = 0, tLen = textArray.length, text; i < tLen; i++) {
1379
+ text = textArray[i].trim();
1380
+ if (text.length > 0) html += '<' + defaultLine + '>' + converter.htmlToEntity(text) + '</' + defaultLine + '>';
1381
+ }
1382
+ return html;
1383
+ }
1384
+ // comments
1385
+ if (node.nodeType === 8 && this._allowHTMLComment) {
1386
+ return '<!--' + node.textContent.trim() + '-->';
1387
+ }
1388
+
1389
+ return '';
1390
+ },
1391
+
1392
+ /**
1393
+ * @private
1394
+ * @this {HTMLThis}
1395
+ * @description Fix tags that do not fit the editor format.
1396
+ * @param {DocumentFragment} documentFragment Document fragment "DOCUMENT_FRAGMENT_NODE" (nodeType === 11)
1397
+ * @param {RegExp} htmlCheckWhitelistRegExp Editor tags whitelist
1398
+ * @param {RegExp} htmlCheckBlacklistRegExp Editor tags blacklist
1399
+ * @param {boolean} tagFilter Tag filter option
1400
+ * @param {boolean} formatFilter Format filter option
1401
+ * @param {boolean} classFilter Class name filter option
1402
+ * @param {boolean} _freeCodeViewMode Enforces strict HTML validation based on the editor`s policy
1403
+ */
1404
+ _consistencyCheckOfHTML(documentFragment, htmlCheckWhitelistRegExp, htmlCheckBlacklistRegExp, tagFilter, formatFilter, classFilter, _freeCodeViewMode) {
1405
+ const removeTags = [],
1406
+ emptyTags = [],
1407
+ wrongList = [],
1408
+ withoutFormatCells = [];
1409
+
1410
+ // wrong position
1411
+ const wrongTags = dom.query.getListChildNodes(documentFragment, (current) => {
1412
+ if (current.nodeType !== 1) {
1413
+ if (formatFilter && dom.check.isList(current.parentElement)) removeTags.push(current);
1414
+ if (current.nodeType === 3 && !current.textContent.trim()) removeTags.push(current);
1415
+ return false;
1416
+ }
1417
+
1418
+ // tag filter
1419
+ if (tagFilter) {
1420
+ // white list
1421
+ if (htmlCheckBlacklistRegExp.test(current.nodeName) || (!htmlCheckWhitelistRegExp.test(current.nodeName) && current.childNodes.length === 0 && dom.check.isExcludeFormat(current))) {
1422
+ removeTags.push(current);
1423
+ return false;
1424
+ }
1425
+ }
1426
+
1427
+ const nrtag = !dom.query.getParentElement(current, dom.check.isExcludeFormat);
1428
+
1429
+ // formatFilter
1430
+ if (formatFilter) {
1431
+ // empty tags
1432
+ if (
1433
+ !dom.check.isTableElements(current) &&
1434
+ !dom.check.isListCell(current) &&
1435
+ !dom.check.isAnchor(current) &&
1436
+ (this.format.isLine(current) || this.format.isBlock(current) || this.format.isTextStyleNode(current)) &&
1437
+ current.childNodes.length === 0 &&
1438
+ nrtag
1439
+ ) {
1440
+ emptyTags.push(current);
1441
+ return false;
1442
+ }
1443
+
1444
+ // wrong list
1445
+ if (dom.check.isList(current.parentNode) && !dom.check.isList(current) && !dom.check.isListCell(current)) {
1446
+ wrongList.push(current);
1447
+ return false;
1448
+ }
1449
+
1450
+ // table cells
1451
+ if (dom.check.isTableCell(current)) {
1452
+ const fel = current.firstElementChild;
1453
+ if (!this.format.isLine(fel) && !this.format.isBlock(fel) && !this.component.is(fel)) {
1454
+ withoutFormatCells.push(current);
1455
+ return false;
1456
+ }
1457
+ }
1458
+ }
1459
+
1460
+ // class filter
1461
+ if (classFilter) {
1462
+ if (nrtag && current.className) {
1463
+ const className = new Array(current.classList).map(this._isAllowedClassName).join(' ').trim();
1464
+ if (className) current.className = className;
1465
+ else current.removeAttribute('class');
1466
+ }
1467
+ }
1468
+
1469
+ // format filter
1470
+ if (!formatFilter) {
1471
+ return false;
1472
+ }
1473
+
1474
+ const result =
1475
+ !_freeCodeViewMode &&
1476
+ current.parentNode !== documentFragment &&
1477
+ nrtag &&
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))));
1480
+
1481
+ return result;
1482
+ });
1483
+
1484
+ for (let i = 0, len = removeTags.length; i < len; i++) {
1485
+ dom.utils.removeItem(removeTags[i]);
1486
+ }
1487
+
1488
+ const checkTags = [];
1489
+ for (let i = 0, len = wrongTags.length, t, p; i < len; i++) {
1490
+ t = wrongTags[i];
1491
+ p = t.parentNode;
1492
+ if (!p || !p.parentNode) continue;
1493
+
1494
+ if (dom.query.getParentElement(t, dom.check.isListCell)) {
1495
+ const cellChildren = t.childNodes;
1496
+ for (let j = cellChildren.length - 1; len >= 0; j--) {
1497
+ p.insertBefore(t, cellChildren[j]);
1498
+ }
1499
+ checkTags.push(t);
1500
+ } else {
1501
+ p.parentNode.insertBefore(t, p);
1502
+ checkTags.push(p);
1503
+ }
1504
+ }
1505
+
1506
+ for (let i = 0, len = checkTags.length, t; i < len; i++) {
1507
+ t = checkTags[i];
1508
+ if (dom.check.isZeroWidth(t.textContent.trim())) {
1509
+ dom.utils.removeItem(t);
1510
+ }
1511
+ }
1512
+
1513
+ for (let i = 0, len = emptyTags.length; i < len; i++) {
1514
+ dom.utils.removeItem(emptyTags[i]);
1515
+ }
1516
+
1517
+ for (let i = 0, len = wrongList.length, t, tp, children, p; i < len; i++) {
1518
+ t = wrongList[i];
1519
+ p = t.parentNode;
1520
+ if (!p) continue;
1521
+
1522
+ tp = dom.utils.createElement('LI');
1523
+
1524
+ if (this.format.isLine(t)) {
1525
+ children = t.childNodes;
1526
+ while (children[0]) {
1527
+ tp.appendChild(children[0]);
1528
+ }
1529
+ p.insertBefore(tp, t);
1530
+ dom.utils.removeItem(t);
1531
+ } else {
1532
+ t = t.nextSibling;
1533
+ tp.appendChild(wrongList[i]);
1534
+ p.insertBefore(tp, t);
1535
+ }
1536
+ }
1537
+
1538
+ for (let i = 0, len = withoutFormatCells.length, t, f; i < len; i++) {
1539
+ t = withoutFormatCells[i];
1540
+ f = dom.utils.createElement('DIV');
1541
+ f.innerHTML = t.textContent.trim().length === 0 && t.children.length === 0 ? '<br>' : t.innerHTML;
1542
+ t.innerHTML = f.outerHTML;
1543
+ }
1544
+ },
1545
+
1546
+ /**
1547
+ * @private
1548
+ * @this {HTMLThis}
1549
+ * @description Removes attribute values such as style and converts tags that do not conform to the "html5" standard.
1550
+ * @param {string} html HTML string
1551
+ * @returns {string} HTML string
1552
+ */
1553
+ _styleNodeConvertor(html) {
1554
+ if (!this._disallowedStyleNodesRegExp) return html;
1555
+
1556
+ const ec = this.options.get('_defaultStyleTagMap');
1557
+ return html.replace(this._disallowedStyleNodesRegExp, (m, t, n, p) => {
1558
+ return t + (typeof ec[n] === 'string' ? ec[n] : n) + (p ? ' ' + p : '');
1559
+ });
1560
+ },
1561
+
1562
+ /**
1563
+ * @private
1564
+ * @this {HTMLThis}
1565
+ * @description Determines if formatting is required and returns a domTree
1566
+ * @param {DocumentFragment} domFrag documentFragment
1567
+ * @returns {DocumentFragment}
1568
+ */
1569
+ _editFormat(domFrag) {
1570
+ let value = '',
1571
+ f;
1572
+ const tempTree = domFrag.childNodes;
1573
+
1574
+ for (let i = 0, len = tempTree.length, n; i < len; i++) {
1575
+ n = /** @type {HTMLElement} */ (tempTree[i]);
1576
+ if (this.__allowedTagNameRegExp.test(n.nodeName)) {
1577
+ value += n.outerHTML;
1578
+ continue;
1579
+ }
1580
+
1581
+ if (n.nodeType === 8) {
1582
+ value += '<!-- ' + n.textContent + ' -->';
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'));
1585
+ if (this.format.isTextStyleNode(n)) {
1586
+ /** @type {HTMLElement} */
1587
+ (n).removeAttribute('style');
1588
+ }
1589
+ f.appendChild(n);
1590
+ i--;
1591
+ len--;
1592
+ } else {
1593
+ if (f) {
1594
+ value += f.outerHTML;
1595
+ f = null;
1596
+ }
1597
+ value += n.outerHTML;
1598
+ }
1599
+ }
1600
+
1601
+ if (f) value += f.outerHTML;
1602
+
1603
+ return this._d.createRange().createContextualFragment(value);
1604
+ },
1605
+
1606
+ /**
1607
+ * @private
1608
+ * @this {HTMLThis}
1609
+ * @description Converts a list of DOM nodes into an HTML list structure.
1610
+ * - If the node is already a list, its innerHTML is used. If it is a block element,
1611
+ * - the function is called recursively.
1612
+ * @param {__se__NodeCollection} domTree List of DOM nodes to be converted.
1613
+ * @returns {string} The generated HTML list.
1614
+ */
1615
+ _convertListCell(domTree) {
1616
+ let html = '';
1617
+
1618
+ for (let i = 0, len = domTree.length, node; i < len; i++) {
1619
+ node = domTree[i];
1620
+ if (node.nodeType === 1) {
1621
+ if (dom.check.isList(node)) {
1622
+ html += node.innerHTML;
1623
+ } else if (dom.check.isListCell(node)) {
1624
+ html += node.outerHTML;
1625
+ } else if (this.format.isLine(node)) {
1626
+ html += '<li>' + (node.innerHTML.trim() || '<br>') + '</li>';
1627
+ } else if (this.format.isBlock(node) && !dom.check.isTableElements(node)) {
1628
+ html += this._convertListCell(node.children);
1629
+ } else {
1630
+ html += '<li>' + /** @type {HTMLElement} */ (node).outerHTML + '</li>';
1631
+ }
1632
+ } else {
1633
+ html += '<li>' + (node.textContent || '<br>') + '</li>';
1634
+ }
1635
+ }
1636
+
1637
+ return html;
1638
+ },
1639
+
1640
+ /**
1641
+ * @private
1642
+ * @this {HTMLThis}
1643
+ * @description Checks whether the provided DOM nodes require formatting.
1644
+ * @param {NodeList} domTree List of DOM nodes to check.
1645
+ * @returns {boolean} True if formatting is required, otherwise false.
1646
+ */
1647
+ _isFormatData(domTree) {
1648
+ let requireFormat = false;
1649
+
1650
+ for (let i = 0, len = domTree.length, t; i < len; i++) {
1651
+ t = domTree[i];
1652
+ if (t.nodeType === 1 && !this.format.isTextStyleNode(t) && !dom.check.isBreak(t) && !this.__disallowedTagNameRegExp.test(t.nodeName)) {
1653
+ requireFormat = true;
1654
+ break;
1655
+ }
1656
+ }
1657
+
1658
+ return requireFormat;
1659
+ },
1660
+
1661
+ /**
1662
+ * @private
1663
+ * @this {HTMLThis}
1664
+ * @description Cleans the inline style attributes of an HTML element.
1665
+ * - Extracts allowed styles and removes disallowed ones based on editor settings.
1666
+ * @param {string} m The full matched string from a regular expression.
1667
+ * @param {Array|null} v The list of allowed attributes.
1668
+ * @param {string} name The tag name of the element being cleaned.
1669
+ * @returns {Array} The updated list of allowed attributes including cleaned styles.
1670
+ */
1671
+ _cleanStyle(m, v, name) {
1672
+ let sv = (m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/) || [])[0];
1673
+ if (this._textStyleTags.includes(name) && !sv && (m.match(/<[^\s]+\s(.+)/) || [])[1]) {
1674
+ const size = (m.match(/\ssize="([^"]+)"/i) || [])[1];
1675
+ const face = (m.match(/\sface="([^"]+)"/i) || [])[1];
1676
+ const color = (m.match(/\scolor="([^"]+)"/i) || [])[1];
1677
+ if (size || face || color) {
1678
+ sv = 'style="' + (size ? 'font-size:' + numbers.get(Number(size) / 3.333, 1) + 'rem;' : '') + (face ? 'font-family:' + face + ';' : '') + (color ? 'color:' + color + ';' : '') + '"';
1679
+ }
1680
+ }
1681
+
1682
+ if (sv) {
1683
+ if (!v) v = [];
1684
+
1685
+ let mv;
1686
+ for (const [key, value] of this._cleanStyleRegExpMap) {
1687
+ if (key.test(name)) {
1688
+ mv = value;
1689
+ break;
1690
+ }
1691
+ }
1692
+ if (!mv) return v;
1693
+
1694
+ const style = sv.replace(/&quot;/g, '').match(mv);
1695
+ if (!style) return v;
1696
+
1697
+ const allowedStyle = [];
1698
+ for (let i = 0, len = style.length, r; i < len; i++) {
1699
+ r = style[i].match(/([a-zA-Z0-9-]+)(:)([^"']+)/);
1700
+ if (r && !/inherit|initial|revert|unset/i.test(r[3])) {
1701
+ const k = converter.kebabToCamelCase(r[1].trim());
1702
+ const cs = this.editor.frameContext.get('wwComputedStyle')[k]?.replace(/"/g, '');
1703
+ const c = r[3].trim();
1704
+ switch (k) {
1705
+ case 'fontFamily':
1706
+ if (!this.plugins.font || !this.plugins.font.fontArray.includes(c)) continue;
1707
+ break;
1708
+ case 'fontSize':
1709
+ if (!this.plugins.fontSize) continue;
1710
+ if (!this.fontSizeUnitRegExp.test(r[0])) {
1711
+ r[0] = r[0].replace((r[0].match(/:\s*([^;]+)/) || [])[1], converter.toFontUnit.bind(null, this.options.get('fontSizeUnits')[0]));
1712
+ }
1713
+ break;
1714
+ case 'color':
1715
+ if (!this.plugins.fontColor || /rgba\(([0-9]+\s*,\s*){3}0\)|windowtext/i.test(c)) continue;
1716
+ break;
1717
+ case 'backgroundColor':
1718
+ if (!this.plugins.backgroundColor || /rgba\(([0-9]+\s*,\s*){3}0\)|windowtext/i.test(c)) continue;
1719
+ break;
1720
+ }
1721
+
1722
+ if (cs !== c) {
1723
+ allowedStyle.push(r[0]);
1724
+ }
1725
+ }
1726
+ }
1727
+ if (allowedStyle.length > 0) v.push('style="' + allowedStyle.join(';') + '"');
1728
+ }
1729
+
1730
+ return v;
1731
+ },
1732
+
1733
+ /**
1734
+ * @private
1735
+ * @this {HTMLThis}
1736
+ * @description Delete disallowed tags
1737
+ * @param {string} html HTML string
1738
+ * @returns {string}
1739
+ */
1740
+ _deleteDisallowedTags(html, whitelistRegExp, blacklistRegExp) {
1741
+ if (whitelistRegExp.test('<font>')) {
1742
+ html = html.replace(/(<\/?)font(\s?)/gi, '$1span$2');
1743
+ }
1744
+
1745
+ return html.replace(whitelistRegExp, '').replace(blacklistRegExp, '');
1746
+ },
1747
+
1748
+ /**
1749
+ * @private
1750
+ * @this {HTMLThis}
1751
+ * @description Recursively checks for duplicate text style nodes within a given parent node.
1752
+ * @param {Node} oNode The node to check for duplicate styles.
1753
+ * @param {Node} parentNode The parent node where the duplicate check occurs.
1754
+ */
1755
+ _checkDuplicateNode(oNode, parentNode) {
1756
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
1757
+ const inst = this;
1758
+ (function recursionFunc(current) {
1759
+ inst._dupleCheck(current, parentNode);
1760
+ const childNodes = current.childNodes;
1761
+ for (let i = 0, len = childNodes.length; i < len; i++) {
1762
+ recursionFunc(childNodes[i]);
1763
+ }
1764
+ })(oNode);
1765
+ },
1766
+
1767
+ /**
1768
+ * @private
1769
+ * @this {HTMLThis}
1770
+ * @description Recursively checks for duplicate text style nodes within a given parent node.
1771
+ * - If duplicate styles are found, redundant attributes are removed.
1772
+ * @param {Node} oNode The node to check for duplicate styles.
1773
+ * @param {Node} parentNode The parent node where the duplicate check occurs.
1774
+ * @returns {Node} The cleaned node with redundant styles removed.
1775
+ */
1776
+ _dupleCheck(oNode, parentNode) {
1777
+ if (!this.format.isTextStyleNode(oNode)) return;
1778
+
1779
+ const oStyles = (oNode.style.cssText.match(/[^;]+;/g) || []).map(function (v) {
1780
+ return v.trim();
1781
+ });
1782
+ const nodeName = oNode.nodeName;
1783
+ if (/^span$/i.test(nodeName) && oStyles.length === 0) return oNode;
1784
+
1785
+ const inst = this.format;
1786
+ let duple = false;
1787
+ (function recursionFunc(ancestor) {
1788
+ if (dom.check.isWysiwygFrame(ancestor) || !inst.isTextStyleNode(ancestor)) return;
1789
+ if (ancestor.nodeName === nodeName) {
1790
+ duple = true;
1791
+ const styles = ancestor.style.cssText.match(/[^;]+;/g) || [];
1792
+ for (let i = 0, len = styles.length, j; i < len; i++) {
1793
+ if ((j = oStyles.indexOf(styles[i].trim())) > -1) {
1794
+ oStyles.splice(j, 1);
1795
+ }
1796
+ }
1797
+ for (let i = 0, len = ancestor.classList.length; i < len; i++) {
1798
+ oNode.classList.remove(ancestor.classList[i]);
1799
+ }
1800
+ }
1801
+
1802
+ recursionFunc(ancestor.parentElement);
1803
+ })(parentNode);
1804
+
1805
+ if (duple) {
1806
+ if (!(oNode.style.cssText = oStyles.join(' '))) {
1807
+ oNode.setAttribute('style', '');
1808
+ oNode.removeAttribute('style');
1809
+ }
1810
+ if (!oNode.attributes.length) {
1811
+ oNode.setAttribute('data-duple', 'true');
1812
+ }
1813
+ }
1814
+
1815
+ return oNode;
1816
+ },
1817
+
1818
+ /**
1819
+ * @private
1820
+ * @this {HTMLThis}
1821
+ * @description Reset autoStyleify options.
1822
+ * @param {Array.<string>} autoStyleify Styles applied automatically on text input.
1823
+ * - ex ["bold", "underline", "italic", "strike"]
1824
+ */
1825
+ __resetAutoStyleify(autoStyleify) {
1826
+ if (autoStyleify.length > 0) {
1827
+ const convertTextTags = this.options.get('convertTextTags');
1828
+ const styleToTag = {};
1829
+ autoStyleify.forEach((style) => {
1830
+ switch (style) {
1831
+ case 'bold':
1832
+ styleToTag.bold = { regex: /font-weight\s*:\s*bold/i, tag: convertTextTags.bold };
1833
+ break;
1834
+ case 'italic':
1835
+ styleToTag.italic = { regex: /font-style\s*:\s*italic/i, tag: convertTextTags.italic };
1836
+ break;
1837
+ case 'underline':
1838
+ styleToTag.underline = { regex: /text-decoration\s*:\s*underline/i, tag: convertTextTags.underline };
1839
+ break;
1840
+ case 'strike':
1841
+ styleToTag.strike = { regex: /text-decoration\s*:\s*line-through/i, tag: convertTextTags.strike };
1842
+ break;
1843
+ }
1844
+ });
1845
+ this._autoStyleify = styleToTag;
1846
+ } else {
1847
+ this._autoStyleify = null;
1848
+ }
1849
+ },
1850
+
1851
+ constructor: HTML
1852
+ };
1853
+
1854
+ /**
1855
+ * @private
1856
+ * @this {HTMLThis}
1857
+ * @description Tag and tag attribute check RegExp function.
1858
+ * @param {string} m RegExp value
1859
+ * @param {string} t RegExp value
1860
+ * @returns {string}
1861
+ */
1862
+ function CleanElements(attrFilter, styleFilter, m, t) {
1863
+ if (/^<[a-z0-9]+:[a-z0-9]+/i.test(m)) return m;
1864
+
1865
+ let v = null;
1866
+ const tagName = t.match(/(?!<)[a-zA-Z0-9-]+/)[0].toLowerCase();
1867
+
1868
+ if (attrFilter) {
1869
+ // blacklist
1870
+ const bAttr = this._attributeBlacklist[tagName];
1871
+ m = m.replace(/\s(?:on[a-z]+)\s*=\s*(")[^"]*\1/gi, '');
1872
+ if (bAttr) m = m.replace(bAttr, '');
1873
+ else m = m.replace(this._attributeBlacklistRegExp, '');
1874
+
1875
+ // whitelist
1876
+ const wAttr = this._attributeWhitelist[tagName];
1877
+ if (wAttr) v = m.match(wAttr);
1878
+ else v = m.match(this._attributeWhitelistRegExp);
1879
+ }
1880
+
1881
+ if (!styleFilter) return m;
1882
+
1883
+ // attribute
1884
+ if (tagName === 'a') {
1885
+ const sv = m.match(/(?:(?:id|name)\s*=\s*(?:"|')[^"']*(?:"|'))/g);
1886
+ if (sv) {
1887
+ if (!v) v = [];
1888
+ v.push(sv[0]);
1889
+ }
1890
+ } else if (!v || !/style=/i.test(v.toString())) {
1891
+ if (this._textStyleTags.includes(tagName)) {
1892
+ v = this._cleanStyle(m, v, tagName);
1893
+ } else if (this.format.isLine(tagName)) {
1894
+ v = this._cleanStyle(m, v, 'line');
1895
+ } else if (this._cleanStyleTagKeyRegExp.test(tagName)) {
1896
+ v = this._cleanStyle(m, v, tagName);
1897
+ }
1898
+ }
1899
+
1900
+ // figure
1901
+ if (dom.check.isMedia(tagName) || dom.check.isFigure(tagName)) {
1902
+ const sv = m.match(/style\s*=\s*(?:"|')[^"']*(?:"|')/);
1903
+ if (!v) v = [];
1904
+ if (sv) v.push(sv[0]);
1905
+ }
1906
+
1907
+ if (v) {
1908
+ for (let i = 0, len = v.length, a; i < len; i++) {
1909
+ a = /^(?:href|src)\s*=\s*('|"|\s)*javascript\s*:/i.test(v[i].trim()) ? '' : v[i];
1910
+ t += (/^\s/.test(a) ? '' : ' ') + a;
1911
+ }
1912
+ }
1913
+
1914
+ return t;
1915
+ }
1916
+
1917
+ /**
1918
+ * @private
1919
+ * @description Get related list
1920
+ * @param {string} str Regular expression string
1921
+ * @param {string} str2 Regular expression string
1922
+ */
1923
+ function GetRegList(str, str2) {
1924
+ return !str ? '^' : str === '*' ? '[a-z-]+' : !str2 ? str : str + '|' + str2;
1925
+ }
1926
+
1927
+ export default HTML;