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,564 +1,586 @@
1
- import { _d, _w } from './env';
2
-
3
- const URLPattern = /https?:\/\/[^\s]+/g;
4
- const FONT_VALUES_MAP = {
5
- 'xx-small': 1,
6
- 'x-small': 2,
7
- small: 3,
8
- medium: 4,
9
- large: 5,
10
- 'x-large': 6,
11
- 'xx-large': 7,
12
- 'xxx-large': 8
13
- };
14
-
15
- function NodeToJson(node) {
16
- // text
17
- if (node.nodeType === 3) {
18
- const text = node.nodeValue.trim();
19
- if (text) return { type: 'text', content: text };
20
- return null;
21
- }
22
-
23
- // element
24
- if (node.nodeType === 1) {
25
- const jsonNode = {
26
- type: 'element',
27
- tag: node.tagName.toLowerCase(),
28
- attributes: {},
29
- children: []
30
- };
31
-
32
- // get attribute
33
- for (const attr of node.attributes) {
34
- jsonNode.attributes[attr.name] = attr.value;
35
- }
36
-
37
- // children
38
- for (const child of node.childNodes) {
39
- const childJson = NodeToJson(child);
40
- if (childJson) jsonNode.children.push(childJson);
41
- }
42
-
43
- return jsonNode;
44
- }
45
-
46
- return null;
47
- }
48
-
49
- /**
50
- * @description Parses an HTML string into a DOM tree, then recursively traverses the nodes to convert them into a structured JSON representation.
51
- * -Each element includes its tag name, attributes, and children.
52
- * -Text nodes are represented as { type: 'text', content: '...' }.
53
- * @param {string} content HTML string
54
- * @returns {Object<string, *>} JSON data
55
- */
56
- export function htmlToJson(content) {
57
- const parser = new DOMParser();
58
- const doc = parser.parseFromString(content, 'text/html');
59
- return NodeToJson(doc.body);
60
- }
61
-
62
- /**
63
- * @description Takes a JSON structure representing HTML elements and recursively serializes it into a valid HTML string.
64
- * -It rebuilds each tag with attributes and inner content.
65
- * Text content and attributes are safely escaped to prevent parsing issues or XSS.
66
- * Useful for restoring dynamic HTML from a data format.
67
- * @param {Object<string, *>} jsonData
68
- * @returns {string} HTML string
69
- */
70
- export function jsonToHtml(jsonData) {
71
- if (!jsonData) return '';
72
-
73
- if (jsonData.type === 'text') {
74
- return htmlToEntity(jsonData.content || '');
75
- }
76
-
77
- if (jsonData.type === 'element') {
78
- const { tag, attributes = {}, children = [] } = jsonData;
79
-
80
- // 속성 문자열 구성
81
- const attrString = Object.entries(attributes)
82
- .map(([key, value]) => `${key}="${htmlToEntity(value)}"`)
83
- .join(' ');
84
-
85
- const openTag = attrString ? `<${tag} ${attrString}>` : `<${tag}>`;
86
- const closeTag = `</${tag}>`;
87
-
88
- const childrenHtml = children.map(jsonToHtml).join('');
89
-
90
- return `${openTag}${childrenHtml}${closeTag}`;
91
- }
92
-
93
- return '';
94
- }
95
-
96
- /**
97
- * @description Convert HTML string to HTML Entity
98
- * @param {string} content
99
- * @returns {string} Content string
100
- * @private
101
- */
102
- export function htmlToEntity(content) {
103
- const ec = {
104
- '&': '&amp;',
105
- '\u00A0': '&nbsp;',
106
- "'": '&apos;',
107
- '"': '&quot;',
108
- '<': '&lt;',
109
- '>': '&gt;'
110
- };
111
- return content.replace(/&|\u00A0|'|"|<|>/g, (m) => {
112
- return typeof ec[m] === 'string' ? ec[m] : m;
113
- });
114
- }
115
-
116
- /**
117
- * @description Convert HTML Entity to HTML string
118
- * @param {string} content Content string
119
- * @returns {string}
120
- */
121
- export function entityToHTML(content) {
122
- const ec = {
123
- '&amp;': '&',
124
- '&nbsp;': '\u00A0',
125
- '&apos;': "'",
126
- '&quot;': '"',
127
- '&lt;': '<',
128
- '&gt;': '>'
129
- };
130
- return content.replace(/&amp;|&nbsp;|&apos;|&quot;|\$lt;|\$gt;/g, (m) => {
131
- return typeof ec[m] === 'string' ? ec[m] : m;
132
- });
133
- }
134
-
135
- /**
136
- * @description Debounce function
137
- * @param {(...args: *) => void} func function
138
- * @param {number} wait delay ms
139
- * @returns {*} executedFunction
140
- */
141
- export function debounce(func, wait) {
142
- let timeout;
143
-
144
- return function executedFunction(...args) {
145
- const later = () => {
146
- _w.clearTimeout(timeout);
147
- func(...args);
148
- };
149
-
150
- _w.clearTimeout(timeout);
151
- timeout = _w.setTimeout(later, wait);
152
- };
153
- }
154
-
155
- /**
156
- * @description Synchronizes two Map objects by updating the first Map with the values from the second,
157
- * - and deleting any keys in the first Map that are not present in the second.
158
- * @param {Map<*, *>} targetMap The Map to update (target).
159
- * @param {Map<*, *>} referenceMap The Map providing the reference values (source).
160
- */
161
- export function syncMaps(targetMap, referenceMap) {
162
- referenceMap.forEach((value, key) => {
163
- targetMap.set(key, value);
164
- });
165
-
166
- targetMap.forEach((_value, key) => {
167
- if (!referenceMap.has(key)) {
168
- targetMap.delete(key);
169
- }
170
- });
171
- }
172
-
173
- /**
174
- * @description Object.values
175
- * @param {Object<*, *>} obj Object parameter.
176
- * @returns {Array<*>}
177
- */
178
- export function getValues(obj) {
179
- return !obj
180
- ? []
181
- : Object.keys(obj).map(function (i) {
182
- return obj[i];
183
- });
184
- }
185
-
186
- /**
187
- * @description Convert the CamelCase To the KebabCase.
188
- * @param {string|Array<string>} param [Camel string]
189
- */
190
- export function camelToKebabCase(param) {
191
- if (typeof param === 'string') {
192
- return param.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase());
193
- } else {
194
- return param.map(function (str) {
195
- return camelToKebabCase(str);
196
- });
197
- }
198
- }
199
-
200
- /**
201
- * @overload
202
- * @param {string} param - Kebab-case string.
203
- * @returns {string} CamelCase string.
204
- */
205
- /**
206
- * @overload
207
- * @param {Array<string>} param - Array of Kebab-case strings.
208
- * @returns {Array<string>} Array of CamelCase strings.
209
- */
210
- export function kebabToCamelCase(param) {
211
- if (typeof param === 'string') {
212
- return param.replace(/-[a-zA-Z]/g, (letter) => letter.replace('-', '').toUpperCase());
213
- } else {
214
- return param.map(function (str) {
215
- return camelToKebabCase(str);
216
- });
217
- }
218
- }
219
-
220
- /**
221
- *
222
- * @param {"em"|"rem"|"%"|"pt"|"px"} to Size units to be converted
223
- * @param {string} size siSize to convert with units (ex: "15rem")
224
- * @returns {string}
225
- */
226
- export function toFontUnit(to, size) {
227
- const value = size.match(/(\d+(?:\.\d+)?)(.+)/);
228
- const sizeNum = value ? Number(value[1]) : FONT_VALUES_MAP[size];
229
- const from = value ? value[2] : 'rem';
230
- let pxSize = sizeNum;
231
-
232
- if (/em/.test(from)) {
233
- pxSize = Math.round(sizeNum / 0.0625);
234
- } else if (from === 'pt') {
235
- pxSize = Math.round(sizeNum * 1.333);
236
- } else if (from === '%') {
237
- pxSize = sizeNum / 100;
238
- }
239
-
240
- switch (to) {
241
- case 'em':
242
- case 'rem':
243
- return (pxSize * 0.0625).toFixed(2) + to;
244
- case '%':
245
- return Number((pxSize * 0.0625).toFixed(2)) * 100 + to;
246
- case 'pt':
247
- return Math.round(pxSize / 1.333) + to;
248
- default:
249
- // px
250
- return pxSize + to;
251
- }
252
- }
253
-
254
- /**
255
- * @description Convert the node list to an array. If not, returns an empty array.
256
- * @param {?__se__NodeCollection} nodeList
257
- * @returns Array
258
- */
259
- export function nodeListToArray(nodeList) {
260
- if (!nodeList) return [];
261
- return Array.prototype.slice.call(nodeList);
262
- }
263
-
264
- /**
265
- * @description Returns a new object with keys and values swapped.
266
- * @param {Object<*, *>} obj object
267
- * @returns {Object<*, *>}
268
- */
269
- export function swapKeyValue(obj) {
270
- const swappedObj = {};
271
-
272
- for (const key in obj) {
273
- if (Object.prototype.hasOwnProperty.call(obj, key)) {
274
- swappedObj[obj[key]] = key;
275
- }
276
- }
277
-
278
- return swappedObj;
279
- }
280
-
281
- /**
282
- * @description Create whitelist RegExp object.
283
- * @param {string} list Tags list ("br|p|div|pre...")
284
- * @returns {RegExp} Return RegExp format: new RegExp("<\\/?\\b(?!" + list + ")\\b[^>^<]*+>", "gi")
285
- */
286
- export function createElementWhitelist(list) {
287
- return new RegExp(`<\\/?\\b(?!\\b${(list || '').replace(/\|/g, '\\b|\\b')}\\b)[^>]*>`, 'gi');
288
- }
289
-
290
- /**
291
- * @description Create blacklist RegExp object.
292
- * @param {string} list Tags list ("br|p|div|pre...")
293
- * @returns {RegExp} Return RegExp format: new RegExp("<\\/?\\b(?:" + list + ")\\b[^>^<]*+>", "gi")
294
- */
295
- export function createElementBlacklist(list) {
296
- return new RegExp(`<\\/?\\b(?:\\b${(list || '^').replace(/\|/g, '\\b|\\b')}\\b)[^>]*>`, 'gi');
297
- }
298
-
299
- /**
300
- * @description Function to check hex format color
301
- * @param {string} str Color value
302
- */
303
- export function isHexColor(str) {
304
- return /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(str);
305
- }
306
-
307
- /**
308
- * @description Function to convert hex format to a rgb color
309
- * @param {string} rgba RGBA color format
310
- * @returns {string}
311
- */
312
- export function rgb2hex(rgba) {
313
- if (isHexColor(rgba) || !rgba) return rgba;
314
-
315
- const rgbaMatch = rgba.match(/^rgba?[\s+]?\(([\d]+)[\s+]?,[\s+]?([\d]+)[\s+]?,[\s+]?([\d]+)[\s+]?/i);
316
-
317
- if (rgbaMatch && rgbaMatch.length >= 4) {
318
- const r = ('0' + parseInt(rgbaMatch[1], 10).toString(16)).slice(-2);
319
- const g = ('0' + parseInt(rgbaMatch[2], 10).toString(16)).slice(-2);
320
- const b = ('0' + parseInt(rgbaMatch[3], 10).toString(16)).slice(-2);
321
-
322
- let a = '';
323
- if (rgba.includes('rgba')) {
324
- const alphaMatch = rgba.match(/[\s+]?([\d]+\.?[\d]*)[\s+]?/i);
325
- if (alphaMatch) {
326
- a = ('0' + Math.round(parseFloat(alphaMatch[1]) * 255).toString(16)).slice(-2);
327
- }
328
- }
329
-
330
- return `#${r}${g}${b}${a}`;
331
- } else {
332
- return '';
333
- }
334
- }
335
-
336
- /**
337
- * @description Computes the width as a percentage of the parent's width, and returns this value rounded to two decimal places.
338
- * @param {HTMLElement} target The target element for which to calculate the width percentage.
339
- * @param {?HTMLElement=} parentTarget The parent element to use as the reference for the width calculation. If not provided, the target's parent element is used.
340
- * @returns {number}
341
- */
342
- export function getWidthInPercentage(target, parentTarget) {
343
- const parent = /** @type {HTMLElement} */ (parentTarget || target.parentElement);
344
- const parentStyle = _w.getComputedStyle(parent);
345
- const parentPaddingLeft = parseFloat(parentStyle.paddingLeft);
346
- const parentPaddingRight = parseFloat(parentStyle.paddingRight);
347
- const scrollbarWidth = parent.offsetWidth - parent.clientWidth;
348
- const parentWidth = parent.offsetWidth - parentPaddingLeft - parentPaddingRight - scrollbarWidth;
349
- const widthInPercentage = (target.offsetWidth / parentWidth) * 100;
350
- return widthInPercentage;
351
- }
352
-
353
- /**
354
- * @description Convert url pattern text node to anchor node
355
- * @param {Node} node Text node
356
- * @returns {boolean} Return true if the text node is converted to an anchor node
357
- */
358
- export function textToAnchor(node) {
359
- if (node.nodeType === 3 && URLPattern.test(node.textContent) && !/^A$/i.test(node.parentNode?.nodeName)) {
360
- const textContent = node.textContent;
361
- const fragment = _d.createDocumentFragment();
362
-
363
- let lastIndex = 0;
364
- textContent.replace(URLPattern, (match, offset) => {
365
- if (offset > 0) {
366
- fragment.appendChild(_d.createTextNode(textContent.slice(0, offset)));
367
- }
368
- const anchor = _d.createElement('a');
369
- anchor.href = match;
370
- anchor.target = '_blank';
371
- anchor.textContent = match;
372
- fragment.appendChild(anchor);
373
- lastIndex = offset + match.length;
374
- if (lastIndex < textContent.length) {
375
- fragment.appendChild(_d.createTextNode(textContent.slice(lastIndex)));
376
- }
377
- return match;
378
- });
379
-
380
- node.parentNode.replaceChild(fragment, node);
381
- return true;
382
- }
383
-
384
- return false;
385
- }
386
-
387
- /**
388
- * Converts styles within a <span> tag to corresponding HTML tags (e.g., <strong>, <em>, <u>, <s>).
389
- * Maintains the original <span> tag and wraps its content with the new tags.
390
- * @param {{ regex: RegExp, tag: string }} styleToTag An object mapping style properties to HTML tags. ex) {bold: { regex: /font-weight\s*:\s*bold/i, tag: 'strong' },}
391
- * @param {Node} node Node
392
- */
393
- export function spanToStyleNode(styleToTag, node) {
394
- if (node.nodeType === 1 && /^SPAN$/i.test(node.nodeName) && /** @type {HTMLElement} */ (node).hasAttribute('style')) {
395
- const style = /** @type {HTMLElement} */ (node).getAttribute('style');
396
- const tags = [];
397
- Object.keys(styleToTag).forEach((key) => {
398
- if (styleToTag[key].regex.test(style)) {
399
- const tag = _d.createElement(styleToTag[key].tag);
400
- tags.push(tag);
401
- }
402
- });
403
-
404
- if (tags.length > 0) {
405
- const temp = _d.createElement('span');
406
- let currentNode = node.firstChild;
407
-
408
- tags.forEach((tag, index) => {
409
- if (index === 0) {
410
- temp.appendChild(tag);
411
- } else {
412
- tags[index - 1].appendChild(tag);
413
- }
414
- });
415
-
416
- const parent = tags[tags.length - 1];
417
- while (currentNode) {
418
- const nextNode = currentNode.nextSibling;
419
- parent.appendChild(currentNode);
420
- currentNode = nextNode;
421
- }
422
-
423
- while (node.firstChild) {
424
- node.removeChild(node.firstChild);
425
- }
426
-
427
- node.appendChild(temp);
428
- }
429
- }
430
- }
431
-
432
- /**
433
- * Adds a query string to a URL. If the URL already contains a query string, the new query is appended to the existing one.
434
- * @param {string} url The original URL to which the query string will be added.
435
- * @param {string} query The query string to be added to the URL.
436
- * @returns {string} The updated URL with the query string appended.
437
- */
438
- export function addUrlQuery(url, query) {
439
- if (query.length > 0) {
440
- if (/\?/.test(url)) {
441
- const splitUrl = url.split('?');
442
- url = splitUrl[0] + '?' + query + '&' + splitUrl[1];
443
- } else {
444
- url += '?' + query;
445
- }
446
- }
447
- return url;
448
- }
449
-
450
- /**
451
- * @description Converts options-related styles and returns them for each frame.
452
- * @param {Map<string, *>} fo editor.frameOptions
453
- * @param {string} cssText Style string
454
- * @returns {{top: string, frame: string, editor: string}}
455
- * @private
456
- */
457
- export function _setDefaultOptionStyle(fo, cssText) {
458
- let optionStyle = '';
459
- if (fo.get('height')) optionStyle += 'height:' + fo.get('height') + ';';
460
- if (fo.get('minHeight')) optionStyle += 'min-height:' + fo.get('minHeight') + ';';
461
- if (fo.get('maxHeight')) optionStyle += 'max-height:' + fo.get('maxHeight') + ';';
462
- if (fo.get('width')) optionStyle += 'width:' + fo.get('width') + ';';
463
- if (fo.get('minWidth')) optionStyle += 'min-width:' + fo.get('minWidth') + ';';
464
- if (fo.get('maxWidth')) optionStyle += 'max-width:' + fo.get('maxWidth') + ';';
465
-
466
- let top = '',
467
- frame = '',
468
- editor = '';
469
- cssText = optionStyle + cssText;
470
- const styleArr = cssText.split(';');
471
- for (let i = 0, len = styleArr.length, s; i < len; i++) {
472
- s = styleArr[i].trim();
473
- if (!s) continue;
474
- if (/^(min-|max-)?width\s*:/.test(s) || /^(z-index|position|display)\s*:/.test(s)) {
475
- top += s + ';';
476
- continue;
477
- }
478
- if (/^(min-|max-)?height\s*:/.test(s)) {
479
- if (/^height/.test(s) && s.split(':')[1].trim() === 'auto') {
480
- fo.set('height', 'auto');
481
- }
482
- frame += s + ';';
483
- continue;
484
- }
485
- editor += s + ';';
486
- }
487
-
488
- return {
489
- top: top,
490
- frame: frame,
491
- editor: editor
492
- };
493
- }
494
-
495
- /**
496
- * @description Set default style tag of the iframe
497
- * @param {Array<string>} linkNames link names array of CSS files
498
- * @returns {string} "<link rel="stylesheet" href=".." />.."
499
- */
500
- export function _setIframeStyleLinks(linkNames) {
501
- let tagString = '';
502
-
503
- if (linkNames) {
504
- for (let f = 0, len = linkNames.length, path; f < len; f++) {
505
- path = [];
506
-
507
- if (/(^https?:\/\/)|(^data:text\/css,)/.test(linkNames[f])) {
508
- path.push(linkNames[f]);
509
- } else {
510
- const CSSFileName = new RegExp(`(^|.*[\\/])${linkNames[f]}(\\..+)?.css((\\??.+?)|\\b)$`, 'i');
511
- for (let c = _d.getElementsByTagName('link'), i = 0, cLen = c.length, styleTag; i < cLen; i++) {
512
- styleTag = c[i].href.match(CSSFileName);
513
- if (styleTag) path.push(styleTag[0]);
514
- }
515
- }
516
-
517
- if (!path || path.length === 0)
518
- throw '[SUNEDITOR.constructor.iframe.fail] The suneditor CSS files installation path could not be automatically detected. Please set the option property "iframe_cssFileName" before creating editor instances.';
519
-
520
- for (let i = 0, pLen = path.length; i < pLen; i++) {
521
- tagString += '<link href="' + path[i] + '" rel="stylesheet">';
522
- }
523
- }
524
- }
525
-
526
- return tagString;
527
- }
528
-
529
- /**
530
- * @description When iframe height options is "auto" return "<style>" tag that required.
531
- * @param {string} frameHeight height
532
- * @returns {string} "<style>...</style>"
533
- */
534
- export function _setAutoHeightStyle(frameHeight) {
535
- return frameHeight === 'auto' ? '<style>\n/** Iframe height auto */\nbody{height: min-content; overflow: hidden;}\n</style>' : '';
536
- }
537
-
538
- const converter = {
539
- htmlToJson,
540
- jsonToHtml,
541
- htmlToEntity,
542
- entityToHTML,
543
- debounce,
544
- syncMaps,
545
- getValues,
546
- camelToKebabCase,
547
- kebabToCamelCase,
548
- toFontUnit,
549
- nodeListToArray,
550
- swapKeyValue,
551
- createElementWhitelist,
552
- createElementBlacklist,
553
- isHexColor,
554
- rgb2hex,
555
- getWidthInPercentage,
556
- textToAnchor,
557
- spanToStyleNode,
558
- addUrlQuery,
559
- _setDefaultOptionStyle,
560
- _setIframeStyleLinks,
561
- _setAutoHeightStyle
562
- };
563
-
564
- export default converter;
1
+ import { _d, _w } from './env';
2
+
3
+ const URLPattern = /https?:\/\/[^\s]+/g;
4
+ const FONT_VALUES_MAP = {
5
+ 'xx-small': 0.5625,
6
+ 'x-small': 0.625,
7
+ small: 0.8333,
8
+ medium: 1,
9
+ large: 1.125,
10
+ 'x-large': 1.5,
11
+ 'xx-large': 2,
12
+ 'xxx-large': 2.5
13
+ };
14
+
15
+ function NodeToJson(node) {
16
+ // text
17
+ if (node.nodeType === 3) {
18
+ const text = node.nodeValue.trim();
19
+ if (text) return { type: 'text', content: text };
20
+ return null;
21
+ }
22
+
23
+ // element
24
+ if (node.nodeType === 1) {
25
+ const jsonNode = {
26
+ type: 'element',
27
+ tag: node.tagName.toLowerCase(),
28
+ attributes: {},
29
+ children: []
30
+ };
31
+
32
+ // get attribute
33
+ for (const attr of node.attributes) {
34
+ jsonNode.attributes[attr.name] = attr.value;
35
+ }
36
+
37
+ // children
38
+ for (const child of node.childNodes) {
39
+ const childJson = NodeToJson(child);
40
+ if (childJson) jsonNode.children.push(childJson);
41
+ }
42
+
43
+ return jsonNode;
44
+ }
45
+
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * @description Parses an HTML string into a DOM tree, then recursively traverses the nodes to convert them into a structured JSON representation.
51
+ * -Each element includes its tag name, attributes, and children.
52
+ * -Text nodes are represented as { type: 'text', content: '...' }.
53
+ * @param {string} content HTML string
54
+ * @returns {Object<string, *>} JSON data
55
+ */
56
+ export function htmlToJson(content) {
57
+ const parser = new DOMParser();
58
+ const doc = parser.parseFromString(content, 'text/html');
59
+ return NodeToJson(doc.body);
60
+ }
61
+
62
+ /**
63
+ * @description Takes a JSON structure representing HTML elements and recursively serializes it into a valid HTML string.
64
+ * -It rebuilds each tag with attributes and inner content.
65
+ * Text content and attributes are safely escaped to prevent parsing issues or XSS.
66
+ * Useful for restoring dynamic HTML from a data format.
67
+ * @param {Object<string, *>} jsonData
68
+ * @returns {string} HTML string
69
+ */
70
+ export function jsonToHtml(jsonData) {
71
+ if (!jsonData) return '';
72
+
73
+ if (jsonData.type === 'text') {
74
+ return htmlToEntity(jsonData.content || '');
75
+ }
76
+
77
+ if (jsonData.type === 'element') {
78
+ const { tag, attributes = {}, children = [] } = jsonData;
79
+
80
+ // 속성 문자열 구성
81
+ const attrString = Object.entries(attributes)
82
+ .map(([key, value]) => `${key}="${htmlToEntity(value)}"`)
83
+ .join(' ');
84
+
85
+ const openTag = attrString ? `<${tag} ${attrString}>` : `<${tag}>`;
86
+ const closeTag = `</${tag}>`;
87
+
88
+ const childrenHtml = children.map(jsonToHtml).join('');
89
+
90
+ return `${openTag}${childrenHtml}${closeTag}`;
91
+ }
92
+
93
+ return '';
94
+ }
95
+
96
+ /**
97
+ * @description Convert HTML string to HTML Entity
98
+ * @param {string} content
99
+ * @returns {string} Content string
100
+ * @private
101
+ */
102
+ export function htmlToEntity(content) {
103
+ const ec = {
104
+ '&': '&amp;',
105
+ '\u00A0': '&nbsp;',
106
+ "'": '&apos;',
107
+ '"': '&quot;',
108
+ '<': '&lt;',
109
+ '>': '&gt;'
110
+ };
111
+ return content.replace(/&|\u00A0|'|"|<|>/g, (m) => {
112
+ return typeof ec[m] === 'string' ? ec[m] : m;
113
+ });
114
+ }
115
+
116
+ /**
117
+ * @description Convert HTML Entity to HTML string
118
+ * @param {string} content Content string
119
+ * @returns {string}
120
+ */
121
+ export function entityToHTML(content) {
122
+ const ec = {
123
+ '&amp;': '&',
124
+ '&nbsp;': '\u00A0',
125
+ '&apos;': "'",
126
+ '&quot;': '"',
127
+ '&lt;': '<',
128
+ '&gt;': '>'
129
+ };
130
+ return content.replace(/&amp;|&nbsp;|&apos;|&quot;|\$lt;|\$gt;/g, (m) => {
131
+ return typeof ec[m] === 'string' ? ec[m] : m;
132
+ });
133
+ }
134
+
135
+ /**
136
+ * @description Debounce function
137
+ * @param {(...args: *) => void} func function
138
+ * @param {number} wait delay ms
139
+ * @returns {*} executedFunction
140
+ */
141
+ export function debounce(func, wait) {
142
+ let timeout;
143
+
144
+ return function executedFunction(...args) {
145
+ const later = () => {
146
+ _w.clearTimeout(timeout);
147
+ func(...args);
148
+ };
149
+
150
+ _w.clearTimeout(timeout);
151
+ timeout = _w.setTimeout(later, wait);
152
+ };
153
+ }
154
+
155
+ /**
156
+ * @description Synchronizes two Map objects by updating the first Map with the values from the second,
157
+ * - and deleting any keys in the first Map that are not present in the second.
158
+ * @param {Map<*, *>} targetMap The Map to update (target).
159
+ * @param {Map<*, *>} referenceMap The Map providing the reference values (source).
160
+ */
161
+ export function syncMaps(targetMap, referenceMap) {
162
+ referenceMap.forEach((value, key) => {
163
+ targetMap.set(key, value);
164
+ });
165
+
166
+ targetMap.forEach((_value, key) => {
167
+ if (!referenceMap.has(key)) {
168
+ targetMap.delete(key);
169
+ }
170
+ });
171
+ }
172
+
173
+ /**
174
+ * @description Merges multiple Map objects into a new Map using spread syntax.
175
+ * - Entries from later maps in the arguments list will overwrite entries from earlier maps if keys conflict.
176
+ * - The original maps are not modified.
177
+ * @param {...Map<*, *>} mapsToMerge - An arbitrary number of Map objects to merge.
178
+ * @returns {Map<*, *>} A new Map containing all entries from the input maps.
179
+ */
180
+ export function mergeMaps(...mapsToMerge) {
181
+ const validMaps = mapsToMerge.filter((m) => {
182
+ if (!(m instanceof Map)) {
183
+ return false;
184
+ }
185
+ return true;
186
+ });
187
+
188
+ const allEntries = validMaps.flatMap((map) => [...map]);
189
+
190
+ return new Map(allEntries);
191
+ }
192
+
193
+ /**
194
+ * @description Object.values
195
+ * @param {Object<*, *>} obj Object parameter.
196
+ * @returns {Array<*>}
197
+ */
198
+ export function getValues(obj) {
199
+ return !obj
200
+ ? []
201
+ : Object.keys(obj).map(function (i) {
202
+ return obj[i];
203
+ });
204
+ }
205
+
206
+ /**
207
+ * @description Convert the CamelCase To the KebabCase.
208
+ * @param {string|Array<string>} param [Camel string]
209
+ */
210
+ export function camelToKebabCase(param) {
211
+ if (typeof param === 'string') {
212
+ return param.replace(/[A-Z]/g, (letter) => '-' + letter.toLowerCase());
213
+ } else {
214
+ return param.map(function (str) {
215
+ return camelToKebabCase(str);
216
+ });
217
+ }
218
+ }
219
+
220
+ /**
221
+ * @overload
222
+ * @param {string} param - Kebab-case string.
223
+ * @returns {string} CamelCase string.
224
+ */
225
+ /**
226
+ * @overload
227
+ * @param {Array<string>} param - Array of Kebab-case strings.
228
+ * @returns {Array<string>} Array of CamelCase strings.
229
+ */
230
+ export function kebabToCamelCase(param) {
231
+ if (typeof param === 'string') {
232
+ return param.replace(/-[a-zA-Z]/g, (letter) => letter.replace('-', '').toUpperCase());
233
+ } else {
234
+ return param.map(function (str) {
235
+ return camelToKebabCase(str);
236
+ });
237
+ }
238
+ }
239
+
240
+ /**
241
+ *
242
+ * @param {"em"|"rem"|"%"|"pt"|"px"} to Size units to be converted
243
+ * @param {string} size siSize to convert with units (ex: "15rem")
244
+ * @returns {string}
245
+ */
246
+ export function toFontUnit(to, size) {
247
+ const value = size.match(/(\d+(?:\.\d+)?)(.+)/);
248
+ const sizeNum = value ? Number(value[1]) : FONT_VALUES_MAP[size];
249
+ const from = value ? value[2] : 'rem';
250
+ let pxSize = sizeNum;
251
+
252
+ if (/em/.test(from)) {
253
+ pxSize = Math.round(sizeNum / 0.0625);
254
+ } else if (from === 'pt') {
255
+ pxSize = Math.round(sizeNum * 1.333);
256
+ } else if (from === '%') {
257
+ pxSize = sizeNum / 100;
258
+ }
259
+
260
+ switch (to) {
261
+ case 'em':
262
+ case 'rem':
263
+ return (pxSize * 0.0625).toFixed(2) + to;
264
+ case '%':
265
+ return Number((pxSize * 0.0625).toFixed(2)) * 100 + to;
266
+ case 'pt':
267
+ return Math.round(pxSize / 1.333) + to;
268
+ default:
269
+ // px
270
+ return pxSize + to;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * @description Convert the node list to an array. If not, returns an empty array.
276
+ * @param {?__se__NodeCollection} nodeList
277
+ * @returns Array
278
+ */
279
+ export function nodeListToArray(nodeList) {
280
+ if (!nodeList) return [];
281
+ return Array.prototype.slice.call(nodeList);
282
+ }
283
+
284
+ /**
285
+ * @description Returns a new object with keys and values swapped.
286
+ * @param {Object<*, *>} obj object
287
+ * @returns {Object<*, *>}
288
+ */
289
+ export function swapKeyValue(obj) {
290
+ const swappedObj = {};
291
+
292
+ const hasOwn = Object.prototype.hasOwnProperty;
293
+ for (const key in obj) {
294
+ if (hasOwn.call(obj, key)) {
295
+ swappedObj[obj[key]] = key;
296
+ }
297
+ }
298
+
299
+ return swappedObj;
300
+ }
301
+
302
+ /**
303
+ * @description Create whitelist RegExp object.
304
+ * @param {string} list Tags list ("br|p|div|pre...")
305
+ * @returns {RegExp} Return RegExp format: new RegExp("<\\/?\\b(?!" + list + ")\\b[^>^<]*+>", "gi")
306
+ */
307
+ export function createElementWhitelist(list) {
308
+ return new RegExp(`<\\/?\\b(?!\\b${(list || '').replace(/\|/g, '\\b|\\b')}\\b)[^>]*>`, 'gi');
309
+ }
310
+
311
+ /**
312
+ * @description Create blacklist RegExp object.
313
+ * @param {string} list Tags list ("br|p|div|pre...")
314
+ * @returns {RegExp} Return RegExp format: new RegExp("<\\/?\\b(?:" + list + ")\\b[^>^<]*+>", "gi")
315
+ */
316
+ export function createElementBlacklist(list) {
317
+ return new RegExp(`<\\/?\\b(?:\\b${(list || '^').replace(/\|/g, '\\b|\\b')}\\b)[^>]*>`, 'gi');
318
+ }
319
+
320
+ /**
321
+ * @description Function to check hex format color
322
+ * @param {string} str Color value
323
+ */
324
+ export function isHexColor(str) {
325
+ return /^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i.test(str);
326
+ }
327
+
328
+ /**
329
+ * @description Function to convert hex format to a rgb color
330
+ * @param {string} rgba RGBA color format
331
+ * @returns {string}
332
+ */
333
+ export function rgb2hex(rgba) {
334
+ if (isHexColor(rgba) || !rgba) return rgba;
335
+
336
+ const rgbaMatch = rgba.match(/^rgba?[\s+]?\(([\d]+)[\s+]?,[\s+]?([\d]+)[\s+]?,[\s+]?([\d]+)[\s+]?/i);
337
+
338
+ if (rgbaMatch && rgbaMatch.length >= 4) {
339
+ const r = ('0' + parseInt(rgbaMatch[1], 10).toString(16)).slice(-2);
340
+ const g = ('0' + parseInt(rgbaMatch[2], 10).toString(16)).slice(-2);
341
+ const b = ('0' + parseInt(rgbaMatch[3], 10).toString(16)).slice(-2);
342
+
343
+ let a = '';
344
+ if (rgba.includes('rgba')) {
345
+ const alphaMatch = rgba.match(/[\s+]?([\d]+\.?[\d]*)[\s+]?/i);
346
+ if (alphaMatch) {
347
+ a = ('0' + Math.round(parseFloat(alphaMatch[1]) * 255).toString(16)).slice(-2);
348
+ }
349
+ }
350
+
351
+ return `#${r}${g}${b}${a}`;
352
+ } else {
353
+ return rgba;
354
+ }
355
+ }
356
+
357
+ /**
358
+ * @description Computes the width as a percentage of the parent's width, and returns this value rounded to two decimal places.
359
+ * @param {HTMLElement} target The target element for which to calculate the width percentage.
360
+ * @param {?HTMLElement=} parentTarget The parent element to use as the reference for the width calculation. If not provided, the target's parent element is used.
361
+ * @returns {number}
362
+ */
363
+ export function getWidthInPercentage(target, parentTarget) {
364
+ const parent = /** @type {HTMLElement} */ (parentTarget || target.parentElement);
365
+ const parentStyle = _w.getComputedStyle(parent);
366
+ const parentPaddingLeft = parseFloat(parentStyle.paddingLeft);
367
+ const parentPaddingRight = parseFloat(parentStyle.paddingRight);
368
+ const scrollbarWidth = parent.offsetWidth - parent.clientWidth;
369
+ const parentWidth = parent.offsetWidth - parentPaddingLeft - parentPaddingRight - scrollbarWidth;
370
+ const widthInPercentage = (target.offsetWidth / parentWidth) * 100;
371
+ return widthInPercentage;
372
+ }
373
+
374
+ /**
375
+ * @description Convert url pattern text node to anchor node
376
+ * @param {Node} node Text node
377
+ * @returns {boolean} Return true if the text node is converted to an anchor node
378
+ */
379
+ export function textToAnchor(node) {
380
+ if (node.nodeType === 3 && URLPattern.test(node.textContent) && !/^A$/i.test(node.parentNode?.nodeName)) {
381
+ const textContent = node.textContent;
382
+ const fragment = _d.createDocumentFragment();
383
+
384
+ let lastIndex = 0;
385
+ textContent.replace(URLPattern, (match, offset) => {
386
+ if (offset > 0) {
387
+ fragment.appendChild(_d.createTextNode(textContent.slice(0, offset)));
388
+ }
389
+ const anchor = _d.createElement('a');
390
+ anchor.href = match;
391
+ anchor.target = '_blank';
392
+ anchor.textContent = match;
393
+ fragment.appendChild(anchor);
394
+ lastIndex = offset + match.length;
395
+ if (lastIndex < textContent.length) {
396
+ fragment.appendChild(_d.createTextNode(textContent.slice(lastIndex)));
397
+ }
398
+ return match;
399
+ });
400
+
401
+ node.parentNode.replaceChild(fragment, node);
402
+ return true;
403
+ }
404
+
405
+ return false;
406
+ }
407
+
408
+ /**
409
+ * Converts styles within a <span> tag to corresponding HTML tags (e.g., <strong>, <em>, <u>, <s>).
410
+ * Maintains the original <span> tag and wraps its content with the new tags.
411
+ * @param {{ regex: RegExp, tag: string }} styleToTag An object mapping style properties to HTML tags. ex) {bold: { regex: /font-weight\s*:\s*bold/i, tag: 'strong' },}
412
+ * @param {Node} node Node
413
+ */
414
+ export function spanToStyleNode(styleToTag, node) {
415
+ if (node.nodeType === 1 && /^SPAN$/i.test(node.nodeName) && /** @type {HTMLElement} */ (node).hasAttribute('style')) {
416
+ const style = /** @type {HTMLElement} */ (node).getAttribute('style');
417
+ const tags = [];
418
+ Object.keys(styleToTag).forEach((key) => {
419
+ if (styleToTag[key].regex.test(style)) {
420
+ const tag = _d.createElement(styleToTag[key].tag);
421
+ tags.push(tag);
422
+ }
423
+ });
424
+
425
+ if (tags.length > 0) {
426
+ const temp = _d.createElement('span');
427
+ let currentNode = node.firstChild;
428
+
429
+ tags.forEach((tag, index) => {
430
+ if (index === 0) {
431
+ temp.appendChild(tag);
432
+ } else {
433
+ tags[index - 1].appendChild(tag);
434
+ }
435
+ });
436
+
437
+ const parent = tags[tags.length - 1];
438
+ while (currentNode) {
439
+ const nextNode = currentNode.nextSibling;
440
+ parent.appendChild(currentNode);
441
+ currentNode = nextNode;
442
+ }
443
+
444
+ while (node.firstChild) {
445
+ node.removeChild(node.firstChild);
446
+ }
447
+
448
+ node.appendChild(temp);
449
+ }
450
+ }
451
+ }
452
+
453
+ /**
454
+ * Adds a query string to a URL. If the URL already contains a query string, the new query is appended to the existing one.
455
+ * @param {string} url The original URL to which the query string will be added.
456
+ * @param {string} query The query string to be added to the URL.
457
+ * @returns {string} The updated URL with the query string appended.
458
+ */
459
+ export function addUrlQuery(url, query) {
460
+ if (query.length > 0) {
461
+ if (/\?/.test(url)) {
462
+ const splitUrl = url.split('?');
463
+ url = splitUrl[0] + '?' + query + '&' + splitUrl[1];
464
+ } else {
465
+ url += '?' + query;
466
+ }
467
+ }
468
+ return url;
469
+ }
470
+
471
+ /**
472
+ * @description Converts options-related styles and returns them for each frame.
473
+ * @param {Map<string, *>} fo editor.frameOptions
474
+ * @param {string} cssText Style string
475
+ * @returns {{top: string, frame: string, editor: string}}
476
+ * @private
477
+ */
478
+ export function _setDefaultOptionStyle(fo, cssText) {
479
+ let optionStyle = '';
480
+ if (fo.get('height')) optionStyle += 'height:' + fo.get('height') + ';';
481
+ if (fo.get('minHeight')) optionStyle += 'min-height:' + fo.get('minHeight') + ';';
482
+ if (fo.get('maxHeight')) optionStyle += 'max-height:' + fo.get('maxHeight') + ';';
483
+ if (fo.get('width')) optionStyle += 'width:' + fo.get('width') + ';';
484
+ if (fo.get('minWidth')) optionStyle += 'min-width:' + fo.get('minWidth') + ';';
485
+ if (fo.get('maxWidth')) optionStyle += 'max-width:' + fo.get('maxWidth') + ';';
486
+
487
+ let top = '',
488
+ frame = '',
489
+ editor = '';
490
+ cssText = optionStyle + cssText;
491
+ const styleArr = cssText.split(';');
492
+ for (let i = 0, len = styleArr.length, s; i < len; i++) {
493
+ s = styleArr[i].trim();
494
+ if (!s) continue;
495
+ if (/^(min-|max-)?width\s*:/.test(s) || /^(z-index|position|display)\s*:/.test(s)) {
496
+ top += s + ';';
497
+ continue;
498
+ }
499
+ if (/^(min-|max-)?height\s*:/.test(s)) {
500
+ if (/^height/.test(s) && s.split(':')[1].trim() === 'auto') {
501
+ fo.set('height', 'auto');
502
+ }
503
+ frame += s + ';';
504
+ continue;
505
+ }
506
+ editor += s + ';';
507
+ }
508
+
509
+ return {
510
+ top: top,
511
+ frame: frame,
512
+ editor: editor
513
+ };
514
+ }
515
+
516
+ /**
517
+ * @description Set default style tag of the iframe
518
+ * @param {Array<string>} linkNames link names array of CSS files
519
+ * @returns {string} "<link rel="stylesheet" href=".." />.."
520
+ */
521
+ export function _setIframeStyleLinks(linkNames) {
522
+ let tagString = '';
523
+
524
+ if (linkNames) {
525
+ for (let f = 0, len = linkNames.length, path; f < len; f++) {
526
+ path = [];
527
+
528
+ if (/(^https?:\/\/)|(^data:text\/css,)/.test(linkNames[f])) {
529
+ path.push(linkNames[f]);
530
+ } else {
531
+ const CSSFileName = new RegExp(`(^|.*[\\/])${linkNames[f]}(\\..+)?.css((\\??.+?)|\\b)$`, 'i');
532
+ for (let c = _d.getElementsByTagName('link'), i = 0, cLen = c.length, styleTag; i < cLen; i++) {
533
+ styleTag = c[i].href.match(CSSFileName);
534
+ if (styleTag) path.push(styleTag[0]);
535
+ }
536
+ }
537
+
538
+ if (!path || path.length === 0)
539
+ throw '[SUNEDITOR.constructor.iframe.fail] The suneditor CSS files installation path could not be automatically detected. Please set the option property "iframe_cssFileName" before creating editor instances.';
540
+
541
+ for (let i = 0, pLen = path.length; i < pLen; i++) {
542
+ tagString += '<link href="' + path[i] + '" rel="stylesheet">';
543
+ }
544
+ }
545
+ }
546
+
547
+ return tagString;
548
+ }
549
+
550
+ /**
551
+ * @description When iframe height options is "auto" return "<style>" tag that required.
552
+ * @param {string} frameHeight height
553
+ * @returns {string} "<style>...</style>"
554
+ */
555
+ export function _setAutoHeightStyle(frameHeight) {
556
+ return frameHeight === 'auto' ? '<style>\n/** Iframe height auto */\nbody{height: min-content; overflow: hidden;}\n</style>' : '';
557
+ }
558
+
559
+ const converter = {
560
+ htmlToJson,
561
+ jsonToHtml,
562
+ htmlToEntity,
563
+ entityToHTML,
564
+ debounce,
565
+ syncMaps,
566
+ mergeMaps,
567
+ getValues,
568
+ camelToKebabCase,
569
+ kebabToCamelCase,
570
+ toFontUnit,
571
+ nodeListToArray,
572
+ swapKeyValue,
573
+ createElementWhitelist,
574
+ createElementBlacklist,
575
+ isHexColor,
576
+ rgb2hex,
577
+ getWidthInPercentage,
578
+ textToAnchor,
579
+ spanToStyleNode,
580
+ addUrlQuery,
581
+ _setDefaultOptionStyle,
582
+ _setIframeStyleLinks,
583
+ _setAutoHeightStyle
584
+ };
585
+
586
+ export default converter;