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.
- package/CONTRIBUTING.md +186 -184
- package/LICENSE +21 -21
- package/README.md +157 -180
- package/dist/suneditor.min.css +1 -1
- package/dist/suneditor.min.js +1 -1
- package/package.json +126 -123
- package/src/assets/design/color.css +131 -121
- package/src/assets/design/index.css +3 -3
- package/src/assets/design/size.css +37 -35
- package/src/assets/design/typography.css +37 -37
- package/src/assets/icons/defaultIcons.js +247 -232
- package/src/assets/suneditor-contents.css +779 -778
- package/src/assets/suneditor.css +43 -35
- package/src/core/base/eventHandlers/handler_toolbar.js +135 -135
- package/src/core/base/eventHandlers/handler_ww_clipboard.js +56 -56
- package/src/core/base/eventHandlers/handler_ww_dragDrop.js +115 -113
- package/src/core/base/eventHandlers/handler_ww_key_input.js +1200 -1200
- package/src/core/base/eventHandlers/handler_ww_mouse.js +194 -194
- package/src/core/base/eventManager.js +1550 -1484
- package/src/core/base/history.js +355 -355
- package/src/core/class/char.js +163 -162
- package/src/core/class/component.js +856 -842
- package/src/core/class/format.js +3433 -3422
- package/src/core/class/html.js +1927 -1890
- package/src/core/class/menu.js +357 -346
- package/src/core/class/nodeTransform.js +424 -424
- package/src/core/class/offset.js +858 -891
- package/src/core/class/selection.js +710 -620
- package/src/core/class/shortcuts.js +98 -98
- package/src/core/class/toolbar.js +438 -430
- package/src/core/class/ui.js +424 -422
- package/src/core/class/viewer.js +750 -750
- package/src/core/editor.js +1810 -1708
- package/src/core/section/actives.js +268 -241
- package/src/core/section/constructor.js +1348 -1661
- package/src/core/section/context.js +102 -102
- package/src/core/section/documentType.js +582 -561
- package/src/core/section/options.js +367 -0
- package/src/core/util/instanceCheck.js +59 -0
- package/src/editorInjector/_classes.js +36 -36
- package/src/editorInjector/_core.js +92 -92
- package/src/editorInjector/index.js +75 -75
- package/src/events.js +634 -622
- package/src/helper/clipboard.js +59 -59
- package/src/helper/converter.js +586 -564
- package/src/helper/dom/domCheck.js +304 -304
- package/src/helper/dom/domQuery.js +677 -669
- package/src/helper/dom/domUtils.js +618 -557
- package/src/helper/dom/index.js +12 -12
- package/src/helper/env.js +249 -240
- package/src/helper/index.js +25 -25
- package/src/helper/keyCodeMap.js +183 -183
- package/src/helper/numbers.js +72 -72
- package/src/helper/unicode.js +47 -47
- package/src/langs/ckb.js +231 -231
- package/src/langs/cs.js +231 -231
- package/src/langs/da.js +231 -231
- package/src/langs/de.js +231 -231
- package/src/langs/en.js +230 -230
- package/src/langs/es.js +231 -231
- package/src/langs/fa.js +231 -231
- package/src/langs/fr.js +231 -231
- package/src/langs/he.js +231 -231
- package/src/langs/hu.js +230 -230
- package/src/langs/index.js +28 -28
- package/src/langs/it.js +231 -231
- package/src/langs/ja.js +230 -230
- package/src/langs/km.js +230 -230
- package/src/langs/ko.js +230 -230
- package/src/langs/lv.js +231 -231
- package/src/langs/nl.js +231 -231
- package/src/langs/pl.js +231 -231
- package/src/langs/pt_br.js +231 -231
- package/src/langs/ro.js +231 -231
- package/src/langs/ru.js +231 -231
- package/src/langs/se.js +231 -231
- package/src/langs/tr.js +231 -231
- package/src/langs/uk.js +231 -231
- package/src/langs/ur.js +231 -231
- package/src/langs/zh_cn.js +231 -231
- package/src/modules/ApiManager.js +191 -191
- package/src/modules/Browser.js +669 -667
- package/src/modules/ColorPicker.js +364 -362
- package/src/modules/Controller.js +474 -454
- package/src/modules/Figure.js +1620 -1617
- package/src/modules/FileManager.js +359 -359
- package/src/modules/HueSlider.js +577 -565
- package/src/modules/Modal.js +346 -346
- package/src/modules/ModalAnchorEditor.js +643 -643
- package/src/modules/SelectMenu.js +549 -549
- package/src/modules/_DragHandle.js +17 -17
- package/src/modules/index.js +14 -14
- package/src/plugins/browser/audioGallery.js +83 -83
- package/src/plugins/browser/fileBrowser.js +103 -103
- package/src/plugins/browser/fileGallery.js +83 -83
- package/src/plugins/browser/imageGallery.js +81 -81
- package/src/plugins/browser/videoGallery.js +103 -103
- package/src/plugins/command/blockquote.js +61 -60
- package/src/plugins/command/exportPDF.js +134 -134
- package/src/plugins/command/fileUpload.js +456 -456
- package/src/plugins/command/list_bulleted.js +149 -148
- package/src/plugins/command/list_numbered.js +152 -151
- package/src/plugins/dropdown/align.js +157 -155
- package/src/plugins/dropdown/backgroundColor.js +108 -104
- package/src/plugins/dropdown/font.js +141 -137
- package/src/plugins/dropdown/fontColor.js +109 -105
- package/src/plugins/dropdown/formatBlock.js +170 -178
- package/src/plugins/dropdown/hr.js +152 -152
- package/src/plugins/dropdown/layout.js +83 -83
- package/src/plugins/dropdown/lineHeight.js +131 -130
- package/src/plugins/dropdown/list.js +123 -122
- package/src/plugins/dropdown/paragraphStyle.js +138 -138
- package/src/plugins/dropdown/table.js +4110 -4000
- package/src/plugins/dropdown/template.js +83 -83
- package/src/plugins/dropdown/textStyle.js +149 -149
- package/src/plugins/field/mention.js +242 -242
- package/src/plugins/index.js +120 -120
- package/src/plugins/input/fontSize.js +414 -410
- package/src/plugins/input/pageNavigator.js +71 -70
- package/src/plugins/modal/audio.js +677 -677
- package/src/plugins/modal/drawing.js +537 -531
- package/src/plugins/modal/embed.js +886 -886
- package/src/plugins/modal/image.js +1377 -1376
- package/src/plugins/modal/link.js +248 -240
- package/src/plugins/modal/math.js +563 -563
- package/src/plugins/modal/video.js +1226 -1226
- package/src/plugins/popup/anchor.js +224 -222
- package/src/suneditor.js +114 -107
- package/src/themes/dark.css +132 -122
- package/src/typedef.js +132 -130
- package/types/assets/icons/defaultIcons.d.ts +8 -0
- package/types/core/base/eventManager.d.ts +29 -4
- package/types/core/class/char.d.ts +2 -1
- package/types/core/class/component.d.ts +1 -2
- package/types/core/class/format.d.ts +8 -1
- package/types/core/class/html.d.ts +8 -0
- package/types/core/class/menu.d.ts +8 -0
- package/types/core/class/offset.d.ts +24 -26
- package/types/core/class/selection.d.ts +2 -0
- package/types/core/class/toolbar.d.ts +6 -0
- package/types/core/class/ui.d.ts +1 -1
- package/types/core/editor.d.ts +34 -12
- package/types/core/section/constructor.d.ts +5 -638
- package/types/core/section/documentType.d.ts +12 -2
- package/types/core/section/options.d.ts +740 -0
- package/types/core/util/instanceCheck.d.ts +50 -0
- package/types/editorInjector/_core.d.ts +5 -5
- package/types/editorInjector/index.d.ts +2 -2
- package/types/events.d.ts +2 -0
- package/types/helper/converter.d.ts +9 -0
- package/types/helper/dom/domQuery.d.ts +5 -5
- package/types/helper/dom/domUtils.d.ts +8 -0
- package/types/helper/env.d.ts +6 -1
- package/types/helper/index.d.ts +4 -1
- package/types/index.d.ts +122 -120
- package/types/langs/_Lang.d.ts +194 -194
- package/types/modules/ColorPicker.d.ts +5 -1
- package/types/modules/Controller.d.ts +8 -4
- package/types/modules/Figure.d.ts +2 -1
- package/types/modules/HueSlider.d.ts +4 -1
- package/types/modules/SelectMenu.d.ts +1 -1
- package/types/plugins/command/blockquote.d.ts +1 -0
- package/types/plugins/command/list_bulleted.d.ts +1 -0
- package/types/plugins/command/list_numbered.d.ts +1 -0
- package/types/plugins/dropdown/align.d.ts +1 -0
- package/types/plugins/dropdown/backgroundColor.d.ts +1 -0
- package/types/plugins/dropdown/font.d.ts +1 -0
- package/types/plugins/dropdown/fontColor.d.ts +1 -0
- package/types/plugins/dropdown/formatBlock.d.ts +3 -2
- package/types/plugins/dropdown/lineHeight.d.ts +1 -0
- package/types/plugins/dropdown/list.d.ts +1 -0
- package/types/plugins/dropdown/table.d.ts +6 -0
- package/types/plugins/input/fontSize.d.ts +1 -0
- package/types/plugins/modal/drawing.d.ts +4 -0
- package/types/plugins/modal/link.d.ts +32 -15
- package/types/suneditor.d.ts +13 -9
- package/types/typedef.d.ts +8 -0
package/src/helper/converter.js
CHANGED
|
@@ -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':
|
|
6
|
-
'x-small':
|
|
7
|
-
small:
|
|
8
|
-
medium:
|
|
9
|
-
large:
|
|
10
|
-
'x-large':
|
|
11
|
-
'xx-large':
|
|
12
|
-
'xxx-large':
|
|
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
|
-
'&': '&',
|
|
105
|
-
'\u00A0': ' ',
|
|
106
|
-
"'": ''',
|
|
107
|
-
'"': '"',
|
|
108
|
-
'<': '<',
|
|
109
|
-
'>': '>'
|
|
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
|
-
'&': '&',
|
|
124
|
-
' ': '\u00A0',
|
|
125
|
-
''': "'",
|
|
126
|
-
'"': '"',
|
|
127
|
-
'<': '<',
|
|
128
|
-
'>': '>'
|
|
129
|
-
};
|
|
130
|
-
return content.replace(/&| |'|"|\$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
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
* @
|
|
208
|
-
* @
|
|
209
|
-
*/
|
|
210
|
-
export function
|
|
211
|
-
if (typeof param === 'string') {
|
|
212
|
-
return param.replace(
|
|
213
|
-
} else {
|
|
214
|
-
return param.map(function (str) {
|
|
215
|
-
return camelToKebabCase(str);
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
*
|
|
222
|
-
* @param {
|
|
223
|
-
* @
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
*
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
node.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
*
|
|
455
|
-
* @
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
if (
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
+
'&': '&',
|
|
105
|
+
'\u00A0': ' ',
|
|
106
|
+
"'": ''',
|
|
107
|
+
'"': '"',
|
|
108
|
+
'<': '<',
|
|
109
|
+
'>': '>'
|
|
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
|
+
'&': '&',
|
|
124
|
+
' ': '\u00A0',
|
|
125
|
+
''': "'",
|
|
126
|
+
'"': '"',
|
|
127
|
+
'<': '<',
|
|
128
|
+
'>': '>'
|
|
129
|
+
};
|
|
130
|
+
return content.replace(/&| |'|"|\$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;
|