rich-html-editor 0.2.1
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/LICENSE +21 -0
- package/README.md +263 -0
- package/dist/chunk-ZDGUOGND.mjs +441 -0
- package/dist/chunk-ZDGUOGND.mjs.map +1 -0
- package/dist/index.d.mts +73 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +1896 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1439 -0
- package/dist/index.mjs.map +1 -0
- package/dist/state-CZIMHTJ3.mjs +25 -0
- package/dist/state-CZIMHTJ3.mjs.map +1 -0
- package/package.json +62 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1439 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACTIVE_OUTLINE,
|
|
3
|
+
BUTTON_ACTIVE_BG,
|
|
4
|
+
BUTTON_BG,
|
|
5
|
+
BUTTON_BORDER,
|
|
6
|
+
BUTTON_COLOR,
|
|
7
|
+
CLASS_ACTIVE,
|
|
8
|
+
CLASS_EDITABLE,
|
|
9
|
+
FONT_OPTIONS,
|
|
10
|
+
FORMAT_OPTIONS,
|
|
11
|
+
HOVER_OUTLINE,
|
|
12
|
+
INFO_COLOR,
|
|
13
|
+
LABEL_ALIGN_CENTER,
|
|
14
|
+
LABEL_ALIGN_LEFT,
|
|
15
|
+
LABEL_ALIGN_RIGHT,
|
|
16
|
+
LABEL_BOLD,
|
|
17
|
+
LABEL_ITALIC,
|
|
18
|
+
LABEL_LINK,
|
|
19
|
+
LABEL_REDO,
|
|
20
|
+
LABEL_STRIKETHROUGH,
|
|
21
|
+
LABEL_UNDERLINE,
|
|
22
|
+
LABEL_UNDO,
|
|
23
|
+
SIZE_OPTIONS,
|
|
24
|
+
STYLE_ID,
|
|
25
|
+
TOOLBAR_BG,
|
|
26
|
+
TOOLBAR_BORDER,
|
|
27
|
+
TOOLBAR_ID,
|
|
28
|
+
_getCurrentEditable,
|
|
29
|
+
_getDoc,
|
|
30
|
+
_getRedoStack,
|
|
31
|
+
_getUndoStack,
|
|
32
|
+
_setCurrentEditable,
|
|
33
|
+
_setDoc,
|
|
34
|
+
_setRedoStack,
|
|
35
|
+
_setUndoStack,
|
|
36
|
+
editorEventEmitter,
|
|
37
|
+
getEditorEventEmitter,
|
|
38
|
+
pushStandaloneSnapshot,
|
|
39
|
+
sanitizeHtml
|
|
40
|
+
} from "./chunk-ZDGUOGND.mjs";
|
|
41
|
+
|
|
42
|
+
// src/dom/styles.ts
|
|
43
|
+
function injectStyles(doc) {
|
|
44
|
+
const styleId = STYLE_ID;
|
|
45
|
+
let styleEl = doc.getElementById(styleId);
|
|
46
|
+
const css = `
|
|
47
|
+
.${CLASS_EDITABLE}{outline:2px dashed ${HOVER_OUTLINE};cursor:text}
|
|
48
|
+
.${CLASS_ACTIVE}{outline:2px solid ${ACTIVE_OUTLINE};cursor:text}
|
|
49
|
+
#${TOOLBAR_ID}{
|
|
50
|
+
position: sticky;
|
|
51
|
+
top: 0;
|
|
52
|
+
left: 0;
|
|
53
|
+
right: 0;
|
|
54
|
+
z-index: 9999;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: 8px;
|
|
58
|
+
padding: 8px 12px;
|
|
59
|
+
background: ${TOOLBAR_BG};
|
|
60
|
+
border-bottom: 1px solid ${TOOLBAR_BORDER};
|
|
61
|
+
font-family: inherit;
|
|
62
|
+
box-shadow: 0 6px 18px rgba(2,6,23,0.08);
|
|
63
|
+
backdrop-filter: blur(6px);
|
|
64
|
+
/* Allow toolbar items to wrap onto multiple lines on narrow screens */
|
|
65
|
+
flex-wrap: wrap;
|
|
66
|
+
justify-content: flex-start;
|
|
67
|
+
}
|
|
68
|
+
#${TOOLBAR_ID} button{
|
|
69
|
+
padding: 6px 8px;
|
|
70
|
+
border: 1px solid ${BUTTON_BORDER};
|
|
71
|
+
background: ${BUTTON_BG};
|
|
72
|
+
color: ${BUTTON_COLOR};
|
|
73
|
+
border-radius: 8px;
|
|
74
|
+
font-weight: 500;
|
|
75
|
+
cursor: pointer;
|
|
76
|
+
transition: transform .12s ease, box-shadow .12s ease, background .12s ease;
|
|
77
|
+
outline: none;
|
|
78
|
+
margin-right: 2px;
|
|
79
|
+
}
|
|
80
|
+
#${TOOLBAR_ID} button[aria-pressed="true"]{
|
|
81
|
+
background: ${BUTTON_ACTIVE_BG};
|
|
82
|
+
font-weight: 600;
|
|
83
|
+
box-shadow: 0 6px 12px rgba(99,102,241,0.12);
|
|
84
|
+
}
|
|
85
|
+
#${TOOLBAR_ID} button:hover:not(:disabled){
|
|
86
|
+
transform: translateY(-2px);
|
|
87
|
+
box-shadow: 0 8px 20px rgba(2,6,23,0.08);
|
|
88
|
+
}
|
|
89
|
+
#${TOOLBAR_ID} select{
|
|
90
|
+
padding: 6px 8px;
|
|
91
|
+
border-radius: 8px;
|
|
92
|
+
border: 1px solid ${BUTTON_BORDER};
|
|
93
|
+
background: #fff;
|
|
94
|
+
font-family: inherit;
|
|
95
|
+
}
|
|
96
|
+
#${TOOLBAR_ID} input[type="color"]{
|
|
97
|
+
border-radius: 8px;
|
|
98
|
+
border: 1px solid ${BUTTON_BORDER};
|
|
99
|
+
background: #fff;
|
|
100
|
+
font-family: inherit;
|
|
101
|
+
}
|
|
102
|
+
#${TOOLBAR_ID} .color-input-label{
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
gap: 8px;
|
|
106
|
+
padding: 0;
|
|
107
|
+
background: transparent;
|
|
108
|
+
border: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Labeled color inputs (e.g. "Text Color <input type=color>") */
|
|
112
|
+
#${TOOLBAR_ID} .color-label{
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: 8px;
|
|
116
|
+
padding: 0;
|
|
117
|
+
background: transparent;
|
|
118
|
+
border: none;
|
|
119
|
+
}
|
|
120
|
+
#${TOOLBAR_ID} .color-input-label .color-icon{
|
|
121
|
+
display: inline-flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
}
|
|
125
|
+
#${TOOLBAR_ID} .text-color-icon{
|
|
126
|
+
font-weight: 700;
|
|
127
|
+
font-size: 14px;
|
|
128
|
+
line-height: 1;
|
|
129
|
+
display: inline-block;
|
|
130
|
+
padding-bottom: 2px;
|
|
131
|
+
border-bottom: 3px solid currentColor;
|
|
132
|
+
transform-origin: center;
|
|
133
|
+
}
|
|
134
|
+
#${TOOLBAR_ID} .highlight-icon{
|
|
135
|
+
width: 16px;
|
|
136
|
+
height: 16px;
|
|
137
|
+
display: inline-block;
|
|
138
|
+
}
|
|
139
|
+
#${TOOLBAR_ID} .color-icon svg{
|
|
140
|
+
width: 16px;
|
|
141
|
+
height: 16px;
|
|
142
|
+
display: block;
|
|
143
|
+
}
|
|
144
|
+
/* Text color wrapper: A with a small swatch on the right */
|
|
145
|
+
#${TOOLBAR_ID} .text-color-wrapper{
|
|
146
|
+
display: inline-flex;
|
|
147
|
+
align-items: center;
|
|
148
|
+
gap: 6px;
|
|
149
|
+
}
|
|
150
|
+
#${TOOLBAR_ID} .text-color-wrapper .text-A{
|
|
151
|
+
font-weight: 700;
|
|
152
|
+
font-size: 14px;
|
|
153
|
+
line-height: 1;
|
|
154
|
+
}
|
|
155
|
+
#${TOOLBAR_ID} .text-color-wrapper .color-swatch{
|
|
156
|
+
width: 12px;
|
|
157
|
+
height: 12px;
|
|
158
|
+
border-radius: 3px;
|
|
159
|
+
border: 1px solid rgba(0,0,0,0.12);
|
|
160
|
+
box-shadow: 0 1px 0 rgba(255,255,255,0.5) inset;
|
|
161
|
+
background: currentColor;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Highlight wrapper: small colored bar under/behind the A to mimic highlighter */
|
|
165
|
+
#${TOOLBAR_ID} .highlight-wrapper{
|
|
166
|
+
display: inline-flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
gap: 6px;
|
|
169
|
+
position: relative;
|
|
170
|
+
}
|
|
171
|
+
#${TOOLBAR_ID} .highlight-wrapper .highlight-bar{
|
|
172
|
+
position: absolute;
|
|
173
|
+
left: 0;
|
|
174
|
+
right: 0;
|
|
175
|
+
bottom: 2px;
|
|
176
|
+
height: 8px;
|
|
177
|
+
border-radius: 3px;
|
|
178
|
+
background: #ffeb3b; /* default yellow */
|
|
179
|
+
z-index: 0;
|
|
180
|
+
}
|
|
181
|
+
#${TOOLBAR_ID} .highlight-wrapper .text-A{
|
|
182
|
+
position: relative;
|
|
183
|
+
z-index: 1;
|
|
184
|
+
font-weight: 700;
|
|
185
|
+
font-size: 14px;
|
|
186
|
+
line-height: 1;
|
|
187
|
+
padding: 0 4px;
|
|
188
|
+
}
|
|
189
|
+
#${TOOLBAR_ID} span{
|
|
190
|
+
color: ${INFO_COLOR};
|
|
191
|
+
font-size: 90%;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* Grouping and separators */
|
|
195
|
+
#${TOOLBAR_ID} .toolbar-group{
|
|
196
|
+
display: flex;
|
|
197
|
+
align-items: center;
|
|
198
|
+
gap: 6px;
|
|
199
|
+
}
|
|
200
|
+
#${TOOLBAR_ID} .toolbar-group{
|
|
201
|
+
/* groups may wrap internally to avoid overflow on narrow screens */
|
|
202
|
+
flex-wrap: wrap;
|
|
203
|
+
}
|
|
204
|
+
/* Overflow button + menu styling */
|
|
205
|
+
#${TOOLBAR_ID} .toolbar-overflow-btn{
|
|
206
|
+
display: none;
|
|
207
|
+
align-items: center;
|
|
208
|
+
justify-content: center;
|
|
209
|
+
padding: 6px 8px;
|
|
210
|
+
border-radius: 8px;
|
|
211
|
+
border: 1px solid ${BUTTON_BORDER};
|
|
212
|
+
background: ${BUTTON_BG};
|
|
213
|
+
color: ${BUTTON_COLOR};
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
}
|
|
216
|
+
#${TOOLBAR_ID} .toolbar-overflow-menu{
|
|
217
|
+
position: absolute;
|
|
218
|
+
top: calc(100% + 6px);
|
|
219
|
+
right: 12px;
|
|
220
|
+
min-width: 160px;
|
|
221
|
+
background: #fff;
|
|
222
|
+
border: 1px solid rgba(15,23,42,0.06);
|
|
223
|
+
border-radius: 8px;
|
|
224
|
+
padding: 8px;
|
|
225
|
+
box-shadow: 0 12px 40px rgba(2,6,23,0.12);
|
|
226
|
+
display: flex;
|
|
227
|
+
flex-direction: column;
|
|
228
|
+
gap: 6px;
|
|
229
|
+
z-index: 10000;
|
|
230
|
+
}
|
|
231
|
+
#${TOOLBAR_ID} .toolbar-overflow-menu[hidden]{
|
|
232
|
+
display: none;
|
|
233
|
+
}
|
|
234
|
+
#${TOOLBAR_ID} .toolbar-sep{
|
|
235
|
+
width: 1px;
|
|
236
|
+
height: 28px;
|
|
237
|
+
background: rgba(15,23,42,0.06);
|
|
238
|
+
margin: 0 8px;
|
|
239
|
+
border-radius: 1px;
|
|
240
|
+
}
|
|
241
|
+
#${TOOLBAR_ID} .toolbar-spacer{
|
|
242
|
+
flex: 1 1 auto;
|
|
243
|
+
}
|
|
244
|
+
#${TOOLBAR_ID} button svg{
|
|
245
|
+
width: 16px;
|
|
246
|
+
height: 16px;
|
|
247
|
+
display: block;
|
|
248
|
+
/* Default icon appearance */
|
|
249
|
+
fill: none;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Active/pressed: switch to filled appearance */
|
|
253
|
+
#${TOOLBAR_ID} button[aria-pressed="true"] svg{
|
|
254
|
+
fill: currentColor;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Focus and accessibility */
|
|
258
|
+
#${TOOLBAR_ID} button:focus{
|
|
259
|
+
outline: none;
|
|
260
|
+
box-shadow: 0 0 0 4px rgba(99,102,241,0.12);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Disabled state */
|
|
264
|
+
#${TOOLBAR_ID} button:disabled{
|
|
265
|
+
opacity: 0.48;
|
|
266
|
+
cursor: not-allowed;
|
|
267
|
+
}
|
|
268
|
+
/* Responsive tweaks: reduce spacing and allow horizontal scroll on very small screens */
|
|
269
|
+
@media (max-width: 720px){
|
|
270
|
+
#${TOOLBAR_ID}{
|
|
271
|
+
padding: 6px 8px;
|
|
272
|
+
gap: 6px;
|
|
273
|
+
}
|
|
274
|
+
#${TOOLBAR_ID} button,
|
|
275
|
+
#${TOOLBAR_ID} select,
|
|
276
|
+
#${TOOLBAR_ID} input[type="color"]{
|
|
277
|
+
padding: 4px 6px;
|
|
278
|
+
border-radius: 6px;
|
|
279
|
+
}
|
|
280
|
+
/* Hide visual separators to save horizontal space */
|
|
281
|
+
#${TOOLBAR_ID} .toolbar-sep{
|
|
282
|
+
display: none;
|
|
283
|
+
}
|
|
284
|
+
/* Collapse labeled color text visually but preserve accessibility on the input */
|
|
285
|
+
#${TOOLBAR_ID} .color-label{
|
|
286
|
+
font-size: 0;
|
|
287
|
+
}
|
|
288
|
+
#${TOOLBAR_ID} .color-label input{
|
|
289
|
+
font-size: initial;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
@media (max-width: 420px){
|
|
293
|
+
/* On very small screens prefer a single-line scrollable toolbar */
|
|
294
|
+
#${TOOLBAR_ID}{
|
|
295
|
+
flex-wrap: nowrap;
|
|
296
|
+
overflow-x: auto;
|
|
297
|
+
-webkit-overflow-scrolling: touch;
|
|
298
|
+
}
|
|
299
|
+
#${TOOLBAR_ID} .toolbar-group{
|
|
300
|
+
flex: 0 0 auto;
|
|
301
|
+
}
|
|
302
|
+
#${TOOLBAR_ID} button{ margin-right: 6px; }
|
|
303
|
+
/* Show overflow button and hide the groups marked for collapse */
|
|
304
|
+
#${TOOLBAR_ID} .toolbar-overflow-btn{
|
|
305
|
+
display: inline-flex;
|
|
306
|
+
}
|
|
307
|
+
#${TOOLBAR_ID} .collapse-on-small{
|
|
308
|
+
display: none;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
`;
|
|
312
|
+
if (!styleEl) {
|
|
313
|
+
styleEl = doc.createElement("style");
|
|
314
|
+
styleEl.id = styleId;
|
|
315
|
+
doc.head.appendChild(styleEl);
|
|
316
|
+
}
|
|
317
|
+
styleEl.textContent = css;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/toolbar/toolbar.ts
|
|
321
|
+
function injectToolbar(doc, options) {
|
|
322
|
+
const existing = doc.getElementById(TOOLBAR_ID);
|
|
323
|
+
if (existing) existing.remove();
|
|
324
|
+
const toolbar = doc.createElement("div");
|
|
325
|
+
toolbar.id = TOOLBAR_ID;
|
|
326
|
+
toolbar.setAttribute("role", "toolbar");
|
|
327
|
+
toolbar.setAttribute("aria-label", "Rich text editor toolbar");
|
|
328
|
+
function makeButton(label, title, command, value, isActive, disabled) {
|
|
329
|
+
const btn = doc.createElement("button");
|
|
330
|
+
btn.type = "button";
|
|
331
|
+
if (label && label.trim().startsWith("<")) {
|
|
332
|
+
btn.innerHTML = label;
|
|
333
|
+
} else {
|
|
334
|
+
btn.textContent = label;
|
|
335
|
+
}
|
|
336
|
+
btn.title = title;
|
|
337
|
+
btn.setAttribute("aria-label", title);
|
|
338
|
+
if (typeof isActive !== "undefined")
|
|
339
|
+
btn.setAttribute("aria-pressed", String(!!isActive));
|
|
340
|
+
btn.tabIndex = 0;
|
|
341
|
+
if (disabled) btn.disabled = true;
|
|
342
|
+
btn.onclick = () => options.onCommand(command, value);
|
|
343
|
+
btn.addEventListener("keydown", (ev) => {
|
|
344
|
+
if (ev.key === "Enter" || ev.key === " ") {
|
|
345
|
+
ev.preventDefault();
|
|
346
|
+
btn.click();
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
return btn;
|
|
350
|
+
}
|
|
351
|
+
function makeSelect(title, command, optionsList, initialValue) {
|
|
352
|
+
const select = doc.createElement("select");
|
|
353
|
+
select.title = title;
|
|
354
|
+
select.setAttribute("aria-label", title);
|
|
355
|
+
select.appendChild(new Option(title, "", true, true));
|
|
356
|
+
for (const opt of optionsList) {
|
|
357
|
+
select.appendChild(new Option(opt.label, opt.value));
|
|
358
|
+
}
|
|
359
|
+
try {
|
|
360
|
+
if (initialValue) select.value = initialValue;
|
|
361
|
+
} catch (e) {
|
|
362
|
+
}
|
|
363
|
+
select.onchange = (e) => {
|
|
364
|
+
const val = e.target.value;
|
|
365
|
+
options.onCommand(command, val);
|
|
366
|
+
select.selectedIndex = 0;
|
|
367
|
+
};
|
|
368
|
+
return select;
|
|
369
|
+
}
|
|
370
|
+
function makeColorInput(title, command, initialColor) {
|
|
371
|
+
const input = doc.createElement("input");
|
|
372
|
+
input.type = "color";
|
|
373
|
+
input.className = "toolbar-color-input";
|
|
374
|
+
const wrapper = doc.createElement("label");
|
|
375
|
+
wrapper.className = "color-label";
|
|
376
|
+
wrapper.appendChild(doc.createTextNode(title + " "));
|
|
377
|
+
wrapper.appendChild(input);
|
|
378
|
+
let savedRange = null;
|
|
379
|
+
input.addEventListener("pointerdown", () => {
|
|
380
|
+
const s = doc.getSelection();
|
|
381
|
+
if (s && s.rangeCount) savedRange = s.getRangeAt(0).cloneRange();
|
|
382
|
+
});
|
|
383
|
+
input.onchange = (e) => {
|
|
384
|
+
try {
|
|
385
|
+
const s = doc.getSelection();
|
|
386
|
+
if (savedRange && s) {
|
|
387
|
+
s.removeAllRanges();
|
|
388
|
+
s.addRange(savedRange);
|
|
389
|
+
}
|
|
390
|
+
} catch (err) {
|
|
391
|
+
}
|
|
392
|
+
options.onCommand(command, e.target.value);
|
|
393
|
+
savedRange = null;
|
|
394
|
+
};
|
|
395
|
+
function rgbToHex(input2) {
|
|
396
|
+
if (!input2) return null;
|
|
397
|
+
const v = input2.trim();
|
|
398
|
+
if (v.startsWith("#")) {
|
|
399
|
+
if (v.length === 4) {
|
|
400
|
+
return ("#" + v[1] + v[1] + v[2] + v[2] + v[3] + v[3]).toLowerCase();
|
|
401
|
+
}
|
|
402
|
+
return v.toLowerCase();
|
|
403
|
+
}
|
|
404
|
+
const rgbMatch = v.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
|
|
405
|
+
if (rgbMatch) {
|
|
406
|
+
const r = Number(rgbMatch[1]);
|
|
407
|
+
const g = Number(rgbMatch[2]);
|
|
408
|
+
const b = Number(rgbMatch[3]);
|
|
409
|
+
const hex = "#" + [r, g, b].map((n) => n.toString(16).padStart(2, "0")).join("").toLowerCase();
|
|
410
|
+
return hex;
|
|
411
|
+
}
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
const setColor = (val) => {
|
|
415
|
+
if (!val) return;
|
|
416
|
+
const hex = rgbToHex(val) || val;
|
|
417
|
+
try {
|
|
418
|
+
if (hex && hex.startsWith("#") && input.value !== hex) {
|
|
419
|
+
input.value = hex;
|
|
420
|
+
}
|
|
421
|
+
} catch (e) {
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
if (initialColor) setColor(initialColor);
|
|
425
|
+
input.addEventListener("input", (e) => {
|
|
426
|
+
const val = e.target.value;
|
|
427
|
+
setColor(val);
|
|
428
|
+
});
|
|
429
|
+
input.title = title;
|
|
430
|
+
input.setAttribute("aria-label", title);
|
|
431
|
+
return wrapper;
|
|
432
|
+
}
|
|
433
|
+
const format = options.getFormatState();
|
|
434
|
+
function makeGroup() {
|
|
435
|
+
const g = doc.createElement("div");
|
|
436
|
+
g.className = "toolbar-group";
|
|
437
|
+
return g;
|
|
438
|
+
}
|
|
439
|
+
function makeSep() {
|
|
440
|
+
const s = doc.createElement("div");
|
|
441
|
+
s.className = "toolbar-sep";
|
|
442
|
+
return s;
|
|
443
|
+
}
|
|
444
|
+
const undoBtn = makeButton(
|
|
445
|
+
LABEL_UNDO,
|
|
446
|
+
"Undo",
|
|
447
|
+
"undo",
|
|
448
|
+
void 0,
|
|
449
|
+
false,
|
|
450
|
+
!options.canUndo()
|
|
451
|
+
);
|
|
452
|
+
undoBtn.onclick = () => options.onUndo();
|
|
453
|
+
const redoBtn = makeButton(
|
|
454
|
+
LABEL_REDO,
|
|
455
|
+
"Redo",
|
|
456
|
+
"redo",
|
|
457
|
+
void 0,
|
|
458
|
+
false,
|
|
459
|
+
!options.canRedo()
|
|
460
|
+
);
|
|
461
|
+
redoBtn.onclick = () => options.onRedo();
|
|
462
|
+
const grp1 = makeGroup();
|
|
463
|
+
grp1.appendChild(undoBtn);
|
|
464
|
+
grp1.appendChild(redoBtn);
|
|
465
|
+
toolbar.appendChild(grp1);
|
|
466
|
+
toolbar.appendChild(makeSep());
|
|
467
|
+
const grp2 = makeGroup();
|
|
468
|
+
grp2.className = "toolbar-group collapse-on-small";
|
|
469
|
+
grp2.appendChild(
|
|
470
|
+
makeSelect(
|
|
471
|
+
"Format",
|
|
472
|
+
"formatBlock",
|
|
473
|
+
FORMAT_OPTIONS,
|
|
474
|
+
format.formatBlock
|
|
475
|
+
)
|
|
476
|
+
);
|
|
477
|
+
grp2.appendChild(
|
|
478
|
+
makeSelect("Font", "fontName", FONT_OPTIONS, format.fontName)
|
|
479
|
+
);
|
|
480
|
+
grp2.appendChild(
|
|
481
|
+
makeSelect("Size", "fontSize", SIZE_OPTIONS, format.fontSize)
|
|
482
|
+
);
|
|
483
|
+
toolbar.appendChild(grp2);
|
|
484
|
+
toolbar.appendChild(makeSep());
|
|
485
|
+
const grp3 = makeGroup();
|
|
486
|
+
grp3.appendChild(
|
|
487
|
+
makeButton(LABEL_BOLD, "Bold", "bold", void 0, format.bold)
|
|
488
|
+
);
|
|
489
|
+
grp3.appendChild(
|
|
490
|
+
makeButton(LABEL_ITALIC, "Italic", "italic", void 0, format.italic)
|
|
491
|
+
);
|
|
492
|
+
grp3.appendChild(
|
|
493
|
+
makeButton(
|
|
494
|
+
LABEL_UNDERLINE,
|
|
495
|
+
"Underline",
|
|
496
|
+
"underline",
|
|
497
|
+
void 0,
|
|
498
|
+
format.underline
|
|
499
|
+
)
|
|
500
|
+
);
|
|
501
|
+
grp3.appendChild(makeButton(LABEL_STRIKETHROUGH, "Strikethrough", "strike"));
|
|
502
|
+
toolbar.appendChild(grp3);
|
|
503
|
+
toolbar.appendChild(makeSep());
|
|
504
|
+
const grp4 = makeGroup();
|
|
505
|
+
grp4.appendChild(makeButton(LABEL_ALIGN_LEFT, "Align left", "align", "left"));
|
|
506
|
+
grp4.appendChild(
|
|
507
|
+
makeButton(LABEL_ALIGN_CENTER, "Align center", "align", "center")
|
|
508
|
+
);
|
|
509
|
+
grp4.appendChild(
|
|
510
|
+
makeButton(LABEL_ALIGN_RIGHT, "Align right", "align", "right")
|
|
511
|
+
);
|
|
512
|
+
toolbar.appendChild(grp4);
|
|
513
|
+
toolbar.appendChild(makeSep());
|
|
514
|
+
const grp5 = makeGroup();
|
|
515
|
+
grp5.className = "toolbar-group collapse-on-small";
|
|
516
|
+
grp5.appendChild(
|
|
517
|
+
makeColorInput("Text color", "foreColor", format.foreColor)
|
|
518
|
+
);
|
|
519
|
+
grp5.appendChild(
|
|
520
|
+
makeColorInput(
|
|
521
|
+
"Highlight color",
|
|
522
|
+
"hiliteColor",
|
|
523
|
+
format.hiliteColor
|
|
524
|
+
)
|
|
525
|
+
);
|
|
526
|
+
toolbar.appendChild(grp5);
|
|
527
|
+
toolbar.appendChild(makeSep());
|
|
528
|
+
const grp6 = makeGroup();
|
|
529
|
+
grp6.className = "toolbar-group collapse-on-small";
|
|
530
|
+
grp6.appendChild(makeButton(LABEL_LINK, "Insert link", "link"));
|
|
531
|
+
toolbar.appendChild(grp6);
|
|
532
|
+
const overflowBtn = doc.createElement("button");
|
|
533
|
+
overflowBtn.type = "button";
|
|
534
|
+
overflowBtn.className = "toolbar-overflow-btn";
|
|
535
|
+
overflowBtn.title = "More";
|
|
536
|
+
overflowBtn.setAttribute("aria-label", "More toolbar actions");
|
|
537
|
+
overflowBtn.setAttribute("aria-haspopup", "true");
|
|
538
|
+
overflowBtn.setAttribute("aria-expanded", "false");
|
|
539
|
+
overflowBtn.tabIndex = 0;
|
|
540
|
+
overflowBtn.innerHTML = "\u22EF";
|
|
541
|
+
const overflowMenu = doc.createElement("div");
|
|
542
|
+
overflowMenu.className = "toolbar-overflow-menu";
|
|
543
|
+
overflowMenu.setAttribute("role", "menu");
|
|
544
|
+
overflowMenu.hidden = true;
|
|
545
|
+
function openOverflow() {
|
|
546
|
+
overflowMenu.hidden = false;
|
|
547
|
+
overflowBtn.setAttribute("aria-expanded", "true");
|
|
548
|
+
const first = overflowMenu.querySelector(
|
|
549
|
+
"button, select, input"
|
|
550
|
+
);
|
|
551
|
+
first == null ? void 0 : first.focus();
|
|
552
|
+
}
|
|
553
|
+
function closeOverflow() {
|
|
554
|
+
overflowMenu.hidden = true;
|
|
555
|
+
overflowBtn.setAttribute("aria-expanded", "false");
|
|
556
|
+
overflowBtn.focus();
|
|
557
|
+
}
|
|
558
|
+
overflowBtn.addEventListener("click", (e) => {
|
|
559
|
+
if (overflowMenu.hidden) openOverflow();
|
|
560
|
+
else closeOverflow();
|
|
561
|
+
});
|
|
562
|
+
overflowBtn.addEventListener("keydown", (e) => {
|
|
563
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
564
|
+
e.preventDefault();
|
|
565
|
+
if (overflowMenu.hidden) openOverflow();
|
|
566
|
+
else closeOverflow();
|
|
567
|
+
}
|
|
568
|
+
if (e.key === "ArrowDown") {
|
|
569
|
+
e.preventDefault();
|
|
570
|
+
if (overflowMenu.hidden) openOverflow();
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
overflowMenu.addEventListener("keydown", (e) => {
|
|
574
|
+
if (e.key === "Escape") {
|
|
575
|
+
e.preventDefault();
|
|
576
|
+
closeOverflow();
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
doc.addEventListener("pointerdown", (ev) => {
|
|
580
|
+
if (!overflowMenu.hidden && !overflowMenu.contains(ev.target) && ev.target !== overflowBtn) {
|
|
581
|
+
closeOverflow();
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
overflowMenu.appendChild(
|
|
585
|
+
makeSelect(
|
|
586
|
+
"Format",
|
|
587
|
+
"formatBlock",
|
|
588
|
+
FORMAT_OPTIONS,
|
|
589
|
+
format.formatBlock
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
overflowMenu.appendChild(
|
|
593
|
+
makeSelect("Font", "fontName", FONT_OPTIONS, format.fontName)
|
|
594
|
+
);
|
|
595
|
+
overflowMenu.appendChild(
|
|
596
|
+
makeSelect("Size", "fontSize", SIZE_OPTIONS, format.fontSize)
|
|
597
|
+
);
|
|
598
|
+
overflowMenu.appendChild(
|
|
599
|
+
makeColorInput("Text color", "foreColor", format.foreColor)
|
|
600
|
+
);
|
|
601
|
+
overflowMenu.appendChild(
|
|
602
|
+
makeColorInput(
|
|
603
|
+
"Highlight color",
|
|
604
|
+
"hiliteColor",
|
|
605
|
+
format.hiliteColor
|
|
606
|
+
)
|
|
607
|
+
);
|
|
608
|
+
overflowMenu.appendChild(makeButton(LABEL_LINK, "Insert link", "link"));
|
|
609
|
+
const overflowWrap = makeGroup();
|
|
610
|
+
overflowWrap.className = "toolbar-group toolbar-overflow-wrap";
|
|
611
|
+
overflowWrap.appendChild(overflowBtn);
|
|
612
|
+
overflowWrap.appendChild(overflowMenu);
|
|
613
|
+
toolbar.appendChild(overflowWrap);
|
|
614
|
+
toolbar.addEventListener("keydown", (e) => {
|
|
615
|
+
const focusable = Array.from(
|
|
616
|
+
toolbar.querySelectorAll("button, select, input, [tabindex]")
|
|
617
|
+
).filter((el) => !el.hasAttribute("disabled"));
|
|
618
|
+
if (!focusable.length) return;
|
|
619
|
+
const idx = focusable.indexOf(document.activeElement);
|
|
620
|
+
if (e.key === "ArrowRight") {
|
|
621
|
+
e.preventDefault();
|
|
622
|
+
const next = focusable[Math.min(focusable.length - 1, Math.max(0, idx + 1))];
|
|
623
|
+
next == null ? void 0 : next.focus();
|
|
624
|
+
} else if (e.key === "ArrowLeft") {
|
|
625
|
+
e.preventDefault();
|
|
626
|
+
const prev = focusable[Math.max(0, idx - 1)] || focusable[0];
|
|
627
|
+
prev == null ? void 0 : prev.focus();
|
|
628
|
+
} else if (e.key === "Home") {
|
|
629
|
+
e.preventDefault();
|
|
630
|
+
focusable[0].focus();
|
|
631
|
+
} else if (e.key === "End") {
|
|
632
|
+
e.preventDefault();
|
|
633
|
+
focusable[focusable.length - 1].focus();
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
doc.body.insertBefore(toolbar, doc.body.firstChild);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// src/dom/candidates.ts
|
|
640
|
+
function isEditableCandidate(el) {
|
|
641
|
+
if (!el) return false;
|
|
642
|
+
const tag = el.tagName;
|
|
643
|
+
const DISALLOWED = [
|
|
644
|
+
"HTML",
|
|
645
|
+
"HEAD",
|
|
646
|
+
"BODY",
|
|
647
|
+
"SCRIPT",
|
|
648
|
+
"STYLE",
|
|
649
|
+
"LINK",
|
|
650
|
+
"META",
|
|
651
|
+
"NOSCRIPT"
|
|
652
|
+
];
|
|
653
|
+
if (DISALLOWED.includes(tag)) return false;
|
|
654
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return false;
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/dom/format.ts
|
|
659
|
+
function computeFormatState(doc) {
|
|
660
|
+
var _a, _b;
|
|
661
|
+
try {
|
|
662
|
+
const s = doc.getSelection();
|
|
663
|
+
let el = null;
|
|
664
|
+
if (s && s.anchorNode)
|
|
665
|
+
el = s.anchorNode.nodeType === Node.ELEMENT_NODE ? s.anchorNode : s.anchorNode.parentElement;
|
|
666
|
+
if (!el)
|
|
667
|
+
return {
|
|
668
|
+
bold: false,
|
|
669
|
+
italic: false,
|
|
670
|
+
underline: false,
|
|
671
|
+
foreColor: null,
|
|
672
|
+
hiliteColor: null,
|
|
673
|
+
fontName: null,
|
|
674
|
+
fontSize: null,
|
|
675
|
+
formatBlock: null
|
|
676
|
+
};
|
|
677
|
+
const computed = (_a = doc.defaultView) == null ? void 0 : _a.getComputedStyle(el);
|
|
678
|
+
const bold = !!(el.closest("strong, b") || computed && (computed.fontWeight === "700" || Number(computed.fontWeight) >= 700));
|
|
679
|
+
const italic = !!(el.closest("em, i") || computed && computed.fontStyle === "italic");
|
|
680
|
+
const underline = !!(el.closest("u") || computed && (computed.textDecorationLine || "").includes("underline"));
|
|
681
|
+
const foreColor = ((_b = el.closest("font[color]")) == null ? void 0 : _b.getAttribute(
|
|
682
|
+
"color"
|
|
683
|
+
)) || computed && computed.color || null;
|
|
684
|
+
const mark = el.closest("mark");
|
|
685
|
+
const hiliteColor = mark && (mark.getAttribute("style") || "") || (computed && computed.backgroundColor && computed.backgroundColor !== "rgba(0, 0, 0, 0)" ? computed.backgroundColor : null);
|
|
686
|
+
const fontName = computed && computed.fontFamily || null;
|
|
687
|
+
const fontSize = computed && computed.fontSize || null;
|
|
688
|
+
let blockEl = el;
|
|
689
|
+
while (blockEl && blockEl.parentElement) {
|
|
690
|
+
const tag = blockEl.tagName;
|
|
691
|
+
if ([
|
|
692
|
+
"P",
|
|
693
|
+
"DIV",
|
|
694
|
+
"SECTION",
|
|
695
|
+
"ARTICLE",
|
|
696
|
+
"LI",
|
|
697
|
+
"TD",
|
|
698
|
+
"BLOCKQUOTE",
|
|
699
|
+
"H1",
|
|
700
|
+
"H2",
|
|
701
|
+
"H3",
|
|
702
|
+
"H4",
|
|
703
|
+
"H5",
|
|
704
|
+
"H6"
|
|
705
|
+
].includes(tag)) {
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
blockEl = blockEl.parentElement;
|
|
709
|
+
}
|
|
710
|
+
const formatBlock = blockEl ? blockEl.tagName.toLowerCase() : null;
|
|
711
|
+
return {
|
|
712
|
+
bold,
|
|
713
|
+
italic,
|
|
714
|
+
underline,
|
|
715
|
+
foreColor,
|
|
716
|
+
hiliteColor,
|
|
717
|
+
fontName,
|
|
718
|
+
fontSize,
|
|
719
|
+
formatBlock
|
|
720
|
+
};
|
|
721
|
+
} catch (err) {
|
|
722
|
+
return {
|
|
723
|
+
bold: false,
|
|
724
|
+
italic: false,
|
|
725
|
+
underline: false,
|
|
726
|
+
foreColor: null,
|
|
727
|
+
hiliteColor: null,
|
|
728
|
+
fontName: null,
|
|
729
|
+
fontSize: null,
|
|
730
|
+
formatBlock: null
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function getElementLabel(el) {
|
|
735
|
+
if (!el) return null;
|
|
736
|
+
const id = el.id ? `#${el.id}` : "";
|
|
737
|
+
const cls = el.className ? `.${String(el.className).split(" ")[0]}` : "";
|
|
738
|
+
const tag = el.tagName.toLowerCase();
|
|
739
|
+
return `${tag}${id}${cls}`;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/core/sanitizeURL.ts
|
|
743
|
+
function sanitizeURL(url) {
|
|
744
|
+
if (!url) return "";
|
|
745
|
+
const trimmed = url.trim();
|
|
746
|
+
if (!trimmed) return "";
|
|
747
|
+
if (trimmed.toLowerCase().startsWith("javascript:") || trimmed.toLowerCase().startsWith("data:")) {
|
|
748
|
+
console.warn("Blocked potentially dangerous URL protocol");
|
|
749
|
+
return "";
|
|
750
|
+
}
|
|
751
|
+
if (!trimmed.startsWith("http") && !trimmed.startsWith("#")) {
|
|
752
|
+
return "https://" + trimmed;
|
|
753
|
+
}
|
|
754
|
+
return trimmed;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// src/core/history.ts
|
|
758
|
+
function handleUndo() {
|
|
759
|
+
try {
|
|
760
|
+
const doc = _getDoc();
|
|
761
|
+
if (!doc) {
|
|
762
|
+
console.warn(
|
|
763
|
+
"[rich-html-editor] handleUndo called before initialization"
|
|
764
|
+
);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
if (_getUndoStack().length < 2) return;
|
|
768
|
+
const undoStack = _getUndoStack();
|
|
769
|
+
const redoStack = _getRedoStack();
|
|
770
|
+
const current = undoStack.pop();
|
|
771
|
+
redoStack.push(current);
|
|
772
|
+
const prev = undoStack[undoStack.length - 1];
|
|
773
|
+
if (!doc.documentElement) {
|
|
774
|
+
throw new Error("Document is missing documentElement");
|
|
775
|
+
}
|
|
776
|
+
const safe = sanitizeHtml(prev.replace(/^<!doctype html>\n?/i, ""), doc);
|
|
777
|
+
try {
|
|
778
|
+
const parser = new DOMParser();
|
|
779
|
+
const parsed = parser.parseFromString(safe, "text/html");
|
|
780
|
+
if (parsed && parsed.body && doc.body) {
|
|
781
|
+
const parsedEls = parsed.body.querySelectorAll("[data-rhe-id]");
|
|
782
|
+
if (parsedEls && parsedEls.length) {
|
|
783
|
+
const loadPromises = [];
|
|
784
|
+
parsedEls.forEach((pe) => {
|
|
785
|
+
const id = pe.getAttribute("data-rhe-id");
|
|
786
|
+
if (!id) return;
|
|
787
|
+
const local = doc.body.querySelector(`[data-rhe-id="${id}"]`);
|
|
788
|
+
if (!local) return;
|
|
789
|
+
try {
|
|
790
|
+
Array.from(local.attributes).forEach((a) => {
|
|
791
|
+
if (a.name !== "data-rhe-id") local.removeAttribute(a.name);
|
|
792
|
+
});
|
|
793
|
+
Array.from(pe.attributes).forEach((a) => {
|
|
794
|
+
if (a.name !== "data-rhe-id")
|
|
795
|
+
local.setAttribute(a.name, a.value);
|
|
796
|
+
});
|
|
797
|
+
} catch (err) {
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
local.innerHTML = pe.innerHTML;
|
|
801
|
+
} catch (err) {
|
|
802
|
+
}
|
|
803
|
+
try {
|
|
804
|
+
const placeholders = pe.querySelectorAll("[data-rhe-script]");
|
|
805
|
+
placeholders.forEach((ph) => {
|
|
806
|
+
const encoded = ph.getAttribute("data-rhe-script") || "";
|
|
807
|
+
let code = "";
|
|
808
|
+
try {
|
|
809
|
+
code = typeof atob !== "undefined" ? decodeURIComponent(escape(atob(encoded))) : decodeURIComponent(encoded);
|
|
810
|
+
} catch (e) {
|
|
811
|
+
try {
|
|
812
|
+
code = decodeURIComponent(encoded);
|
|
813
|
+
} catch (er) {
|
|
814
|
+
code = "";
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
const attrsRaw = ph.getAttribute("data-rhe-script-attrs");
|
|
818
|
+
let attrs = {};
|
|
819
|
+
if (attrsRaw) {
|
|
820
|
+
try {
|
|
821
|
+
attrs = JSON.parse(decodeURIComponent(attrsRaw));
|
|
822
|
+
} catch (e) {
|
|
823
|
+
attrs = {};
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const parentId = ph.getAttribute("data-rhe-script-parent");
|
|
827
|
+
try {
|
|
828
|
+
const s = doc.createElement("script");
|
|
829
|
+
try {
|
|
830
|
+
s.type = "text/javascript";
|
|
831
|
+
s.async = false;
|
|
832
|
+
} catch (err) {
|
|
833
|
+
}
|
|
834
|
+
Object.keys(attrs).forEach(
|
|
835
|
+
(k) => s.setAttribute(k, attrs[k])
|
|
836
|
+
);
|
|
837
|
+
if (attrs.src) {
|
|
838
|
+
const p = new Promise((resolve) => {
|
|
839
|
+
s.addEventListener("load", () => resolve());
|
|
840
|
+
s.addEventListener("error", () => resolve());
|
|
841
|
+
});
|
|
842
|
+
loadPromises.push(p);
|
|
843
|
+
s.src = attrs.src;
|
|
844
|
+
} else {
|
|
845
|
+
s.textContent = code;
|
|
846
|
+
}
|
|
847
|
+
if (parentId === "head") {
|
|
848
|
+
doc.head.appendChild(s);
|
|
849
|
+
} else {
|
|
850
|
+
const target = doc.body.querySelector(
|
|
851
|
+
`[data-rhe-id="${parentId}"]`
|
|
852
|
+
);
|
|
853
|
+
if (target) target.appendChild(s);
|
|
854
|
+
else doc.body.appendChild(s);
|
|
855
|
+
}
|
|
856
|
+
} catch (e) {
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
} catch (e) {
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
try {
|
|
863
|
+
if (loadPromises.length) {
|
|
864
|
+
const waiter = Promise.allSettled ? Promise.allSettled(loadPromises) : Promise.all(
|
|
865
|
+
loadPromises.map((p) => p.catch(() => void 0))
|
|
866
|
+
);
|
|
867
|
+
waiter.then(() => {
|
|
868
|
+
try {
|
|
869
|
+
doc.dispatchEvent(new Event("rhe:scripts-restored"));
|
|
870
|
+
} catch (e) {
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
} else {
|
|
874
|
+
try {
|
|
875
|
+
doc.dispatchEvent(new Event("rhe:scripts-restored"));
|
|
876
|
+
} catch (e) {
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} catch (e) {
|
|
880
|
+
}
|
|
881
|
+
} else {
|
|
882
|
+
doc.body.innerHTML = parsed.body.innerHTML;
|
|
883
|
+
}
|
|
884
|
+
} else {
|
|
885
|
+
doc.documentElement.innerHTML = safe;
|
|
886
|
+
}
|
|
887
|
+
} catch (err) {
|
|
888
|
+
doc.documentElement.innerHTML = safe;
|
|
889
|
+
}
|
|
890
|
+
injectStyles(doc);
|
|
891
|
+
try {
|
|
892
|
+
doc.dispatchEvent(new Event("selectionchange"));
|
|
893
|
+
} catch (err) {
|
|
894
|
+
}
|
|
895
|
+
} catch (error) {
|
|
896
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
897
|
+
console.error("[rich-html-editor] Undo failed:", message);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function handleRedo() {
|
|
901
|
+
try {
|
|
902
|
+
const doc = _getDoc();
|
|
903
|
+
if (!doc) {
|
|
904
|
+
console.warn(
|
|
905
|
+
"[rich-html-editor] handleRedo called before initialization"
|
|
906
|
+
);
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (!_getRedoStack().length) return;
|
|
910
|
+
const undoStack = _getUndoStack();
|
|
911
|
+
const redoStack = _getRedoStack();
|
|
912
|
+
const next = redoStack.pop();
|
|
913
|
+
undoStack.push(next);
|
|
914
|
+
if (!doc.documentElement) {
|
|
915
|
+
throw new Error("Document is missing documentElement");
|
|
916
|
+
}
|
|
917
|
+
const safeNext = sanitizeHtml(
|
|
918
|
+
next.replace(/^<!doctype html>\n?/i, ""),
|
|
919
|
+
doc
|
|
920
|
+
);
|
|
921
|
+
try {
|
|
922
|
+
const parser = new DOMParser();
|
|
923
|
+
const parsed = parser.parseFromString(safeNext, "text/html");
|
|
924
|
+
if (parsed && parsed.body && doc.body) {
|
|
925
|
+
const parsedEls = parsed.body.querySelectorAll("[data-rhe-id]");
|
|
926
|
+
if (parsedEls && parsedEls.length) {
|
|
927
|
+
const loadPromises = [];
|
|
928
|
+
parsedEls.forEach((pe) => {
|
|
929
|
+
const id = pe.getAttribute("data-rhe-id");
|
|
930
|
+
if (!id) return;
|
|
931
|
+
const local = doc.body.querySelector(`[data-rhe-id="${id}"]`);
|
|
932
|
+
if (!local) return;
|
|
933
|
+
try {
|
|
934
|
+
Array.from(local.attributes).forEach((a) => {
|
|
935
|
+
if (a.name !== "data-rhe-id") local.removeAttribute(a.name);
|
|
936
|
+
});
|
|
937
|
+
Array.from(pe.attributes).forEach((a) => {
|
|
938
|
+
if (a.name !== "data-rhe-id")
|
|
939
|
+
local.setAttribute(a.name, a.value);
|
|
940
|
+
});
|
|
941
|
+
} catch (err) {
|
|
942
|
+
}
|
|
943
|
+
try {
|
|
944
|
+
local.innerHTML = pe.innerHTML;
|
|
945
|
+
} catch (err) {
|
|
946
|
+
}
|
|
947
|
+
try {
|
|
948
|
+
const placeholders = pe.querySelectorAll("[data-rhe-script]");
|
|
949
|
+
placeholders.forEach((ph) => {
|
|
950
|
+
const encoded = ph.getAttribute("data-rhe-script") || "";
|
|
951
|
+
let code = "";
|
|
952
|
+
try {
|
|
953
|
+
code = typeof atob !== "undefined" ? decodeURIComponent(escape(atob(encoded))) : decodeURIComponent(encoded);
|
|
954
|
+
} catch (e) {
|
|
955
|
+
try {
|
|
956
|
+
code = decodeURIComponent(encoded);
|
|
957
|
+
} catch (er) {
|
|
958
|
+
code = "";
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
const attrsRaw = ph.getAttribute("data-rhe-script-attrs");
|
|
962
|
+
let attrs = {};
|
|
963
|
+
if (attrsRaw) {
|
|
964
|
+
try {
|
|
965
|
+
attrs = JSON.parse(decodeURIComponent(attrsRaw));
|
|
966
|
+
} catch (e) {
|
|
967
|
+
attrs = {};
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
const parentId = ph.getAttribute("data-rhe-script-parent");
|
|
971
|
+
try {
|
|
972
|
+
const s = doc.createElement("script");
|
|
973
|
+
try {
|
|
974
|
+
s.type = "text/javascript";
|
|
975
|
+
s.async = false;
|
|
976
|
+
} catch (err) {
|
|
977
|
+
}
|
|
978
|
+
Object.keys(attrs).forEach(
|
|
979
|
+
(k) => s.setAttribute(k, attrs[k])
|
|
980
|
+
);
|
|
981
|
+
if (attrs.src) {
|
|
982
|
+
const p = new Promise((resolve) => {
|
|
983
|
+
s.addEventListener("load", () => resolve());
|
|
984
|
+
s.addEventListener("error", () => resolve());
|
|
985
|
+
});
|
|
986
|
+
loadPromises.push(p);
|
|
987
|
+
s.src = attrs.src;
|
|
988
|
+
} else {
|
|
989
|
+
s.textContent = code;
|
|
990
|
+
}
|
|
991
|
+
if (parentId === "head") {
|
|
992
|
+
doc.head.appendChild(s);
|
|
993
|
+
} else {
|
|
994
|
+
const target = doc.body.querySelector(
|
|
995
|
+
`[data-rhe-id="${parentId}"]`
|
|
996
|
+
);
|
|
997
|
+
if (target) target.appendChild(s);
|
|
998
|
+
else doc.body.appendChild(s);
|
|
999
|
+
}
|
|
1000
|
+
} catch (e) {
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
try {
|
|
1007
|
+
if (loadPromises.length) {
|
|
1008
|
+
const waiter = Promise.allSettled ? Promise.allSettled(loadPromises) : Promise.all(
|
|
1009
|
+
loadPromises.map((p) => p.catch(() => void 0))
|
|
1010
|
+
);
|
|
1011
|
+
waiter.then(() => {
|
|
1012
|
+
try {
|
|
1013
|
+
doc.dispatchEvent(new Event("rhe:scripts-restored"));
|
|
1014
|
+
} catch (e) {
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
} else {
|
|
1018
|
+
try {
|
|
1019
|
+
doc.dispatchEvent(new Event("rhe:scripts-restored"));
|
|
1020
|
+
} catch (e) {
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
} catch (e) {
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
doc.body.innerHTML = parsed.body.innerHTML;
|
|
1027
|
+
}
|
|
1028
|
+
} else {
|
|
1029
|
+
doc.documentElement.innerHTML = safeNext;
|
|
1030
|
+
}
|
|
1031
|
+
} catch (err) {
|
|
1032
|
+
doc.documentElement.innerHTML = safeNext;
|
|
1033
|
+
}
|
|
1034
|
+
injectStyles(doc);
|
|
1035
|
+
try {
|
|
1036
|
+
doc.dispatchEvent(new Event("selectionchange"));
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
}
|
|
1039
|
+
} catch (error) {
|
|
1040
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1041
|
+
console.error("[rich-html-editor] Redo failed:", message);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// src/core/formatActions.ts
|
|
1046
|
+
function handleToolbarCommand(command, value) {
|
|
1047
|
+
try {
|
|
1048
|
+
const doc = _getDoc();
|
|
1049
|
+
if (!doc) {
|
|
1050
|
+
console.warn(
|
|
1051
|
+
"[rich-html-editor] handleToolbarCommand called before initialization"
|
|
1052
|
+
);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (command === "undo") return;
|
|
1056
|
+
if (command === "redo") return;
|
|
1057
|
+
if (command === "link") {
|
|
1058
|
+
const url = window.prompt("Enter URL (https://...):", "https://");
|
|
1059
|
+
if (url) {
|
|
1060
|
+
const sanitized = sanitizeURL(url);
|
|
1061
|
+
if (sanitized) applyStandaloneCommand("link", sanitized);
|
|
1062
|
+
}
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
applyStandaloneCommand(command, value);
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1068
|
+
console.error("[rich-html-editor] Command handler failed:", message);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
function applyStandaloneCommand(command, value) {
|
|
1072
|
+
try {
|
|
1073
|
+
const doc = _getDoc();
|
|
1074
|
+
if (!doc) {
|
|
1075
|
+
console.warn(
|
|
1076
|
+
"[rich-html-editor] applyStandaloneCommand called before initialization"
|
|
1077
|
+
);
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
if (command === "bold") wrapSelectionWithElement(doc, "strong");
|
|
1081
|
+
else if (command === "italic") wrapSelectionWithElement(doc, "em");
|
|
1082
|
+
else if (command === "underline") wrapSelectionWithElement(doc, "u");
|
|
1083
|
+
else if (command === "strike") wrapSelectionWithElement(doc, "s");
|
|
1084
|
+
else if (command === "fontName")
|
|
1085
|
+
wrapSelectionWithElement(doc, "span", { fontFamily: value });
|
|
1086
|
+
else if (command === "fontSize") {
|
|
1087
|
+
const raw = value || "14";
|
|
1088
|
+
const n = parseInt(raw, 10);
|
|
1089
|
+
const sz = Number.isFinite(n) ? `${n}px` : raw;
|
|
1090
|
+
wrapSelectionWithElement(doc, "span", { fontSize: sz });
|
|
1091
|
+
} else if (command === "link") {
|
|
1092
|
+
const sel = doc.getSelection();
|
|
1093
|
+
if (!sel || !sel.rangeCount) return;
|
|
1094
|
+
const range = sel.getRangeAt(0);
|
|
1095
|
+
const content = range.extractContents();
|
|
1096
|
+
const a = doc.createElement("a");
|
|
1097
|
+
a.href = sanitizeURL(value || "#");
|
|
1098
|
+
a.appendChild(content);
|
|
1099
|
+
range.insertNode(a);
|
|
1100
|
+
} else if (command === "foreColor")
|
|
1101
|
+
wrapSelectionWithElement(doc, "span", { color: value });
|
|
1102
|
+
else if (command === "hiliteColor")
|
|
1103
|
+
wrapSelectionWithElement(doc, "span", { backgroundColor: value });
|
|
1104
|
+
else if (command === "align") {
|
|
1105
|
+
const sel = doc.getSelection();
|
|
1106
|
+
const node = (sel == null ? void 0 : sel.anchorNode) || null;
|
|
1107
|
+
const block = findBlockAncestor(node);
|
|
1108
|
+
if (block) block.style.textAlign = value || "left";
|
|
1109
|
+
else wrapSelectionWithElement(doc, "div", { textAlign: value });
|
|
1110
|
+
} else if (command === "formatBlock") {
|
|
1111
|
+
const sel = doc.getSelection();
|
|
1112
|
+
const node = (sel == null ? void 0 : sel.anchorNode) || null;
|
|
1113
|
+
const block = findBlockAncestor(node);
|
|
1114
|
+
const tag = (value || "p").toLowerCase();
|
|
1115
|
+
if (block && block.parentElement) {
|
|
1116
|
+
const newEl = doc.createElement(tag);
|
|
1117
|
+
newEl.className = block.className || "";
|
|
1118
|
+
while (block.firstChild) newEl.appendChild(block.firstChild);
|
|
1119
|
+
block.parentElement.replaceChild(newEl, block);
|
|
1120
|
+
} else {
|
|
1121
|
+
wrapSelectionWithElement(doc, tag);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
pushStandaloneSnapshot();
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1127
|
+
console.error("[rich-html-editor] Apply command failed:", message);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function wrapSelectionWithElement(doc, tagName, style) {
|
|
1131
|
+
const sel = doc.getSelection();
|
|
1132
|
+
if (!sel) return;
|
|
1133
|
+
if (!sel.rangeCount) return;
|
|
1134
|
+
const range = sel.getRangeAt(0);
|
|
1135
|
+
if (range.collapsed) {
|
|
1136
|
+
const el = doc.createElement(tagName);
|
|
1137
|
+
if (style) Object.assign(el.style, style);
|
|
1138
|
+
const zw = doc.createTextNode("\u200B");
|
|
1139
|
+
el.appendChild(zw);
|
|
1140
|
+
range.insertNode(el);
|
|
1141
|
+
const newRange2 = doc.createRange();
|
|
1142
|
+
newRange2.setStart(zw, 1);
|
|
1143
|
+
newRange2.collapse(true);
|
|
1144
|
+
sel.removeAllRanges();
|
|
1145
|
+
sel.addRange(newRange2);
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const content = range.extractContents();
|
|
1149
|
+
const wrapper = doc.createElement(tagName);
|
|
1150
|
+
if (style) Object.assign(wrapper.style, style);
|
|
1151
|
+
wrapper.appendChild(content);
|
|
1152
|
+
range.insertNode(wrapper);
|
|
1153
|
+
sel.removeAllRanges();
|
|
1154
|
+
const newRange = doc.createRange();
|
|
1155
|
+
newRange.selectNodeContents(wrapper);
|
|
1156
|
+
sel.addRange(newRange);
|
|
1157
|
+
}
|
|
1158
|
+
function findBlockAncestor(node) {
|
|
1159
|
+
let n = node;
|
|
1160
|
+
const BLOCKS = [
|
|
1161
|
+
"P",
|
|
1162
|
+
"DIV",
|
|
1163
|
+
"SECTION",
|
|
1164
|
+
"ARTICLE",
|
|
1165
|
+
"LI",
|
|
1166
|
+
"TD",
|
|
1167
|
+
"BLOCKQUOTE",
|
|
1168
|
+
"H1",
|
|
1169
|
+
"H2",
|
|
1170
|
+
"H3",
|
|
1171
|
+
"H4",
|
|
1172
|
+
"H5",
|
|
1173
|
+
"H6"
|
|
1174
|
+
];
|
|
1175
|
+
while (n) {
|
|
1176
|
+
if (n.nodeType === Node.ELEMENT_NODE) {
|
|
1177
|
+
const el = n;
|
|
1178
|
+
if (BLOCKS.includes(el.tagName)) return el;
|
|
1179
|
+
}
|
|
1180
|
+
n = n.parentNode;
|
|
1181
|
+
}
|
|
1182
|
+
return null;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/dom/handlers.ts
|
|
1186
|
+
function attachStandaloneHandlers(doc) {
|
|
1187
|
+
try {
|
|
1188
|
+
const selector = [
|
|
1189
|
+
"p",
|
|
1190
|
+
"div",
|
|
1191
|
+
"section",
|
|
1192
|
+
"article",
|
|
1193
|
+
"header",
|
|
1194
|
+
"footer",
|
|
1195
|
+
"aside",
|
|
1196
|
+
"nav",
|
|
1197
|
+
"span",
|
|
1198
|
+
"h1",
|
|
1199
|
+
"h2",
|
|
1200
|
+
"h3",
|
|
1201
|
+
"h4",
|
|
1202
|
+
"h5",
|
|
1203
|
+
"h6",
|
|
1204
|
+
"li",
|
|
1205
|
+
"figure",
|
|
1206
|
+
"figcaption",
|
|
1207
|
+
"blockquote",
|
|
1208
|
+
"pre",
|
|
1209
|
+
"code"
|
|
1210
|
+
].join(",");
|
|
1211
|
+
const candidates = Array.from(
|
|
1212
|
+
doc.querySelectorAll(selector)
|
|
1213
|
+
).filter((el) => isEditableCandidate(el));
|
|
1214
|
+
candidates.forEach((target) => {
|
|
1215
|
+
if (!target.hasAttribute("data-rhe-id")) {
|
|
1216
|
+
const uid = `rhe-init-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1217
|
+
try {
|
|
1218
|
+
target.setAttribute("data-rhe-id", uid);
|
|
1219
|
+
} catch (err) {
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
});
|
|
1223
|
+
} catch (err) {
|
|
1224
|
+
}
|
|
1225
|
+
doc.addEventListener(
|
|
1226
|
+
"click",
|
|
1227
|
+
(e) => {
|
|
1228
|
+
var _a, _b;
|
|
1229
|
+
const target = e.target;
|
|
1230
|
+
if (!isEditableCandidate(target)) return;
|
|
1231
|
+
if (_getCurrentEditable() && _getCurrentEditable() !== target) {
|
|
1232
|
+
(_a = _getCurrentEditable()) == null ? void 0 : _a.removeAttribute("contenteditable");
|
|
1233
|
+
(_b = _getCurrentEditable()) == null ? void 0 : _b.classList.remove(CLASS_ACTIVE);
|
|
1234
|
+
}
|
|
1235
|
+
if (!target.hasAttribute("data-rhe-id")) {
|
|
1236
|
+
const uid = `rhe-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1237
|
+
try {
|
|
1238
|
+
target.setAttribute("data-rhe-id", uid);
|
|
1239
|
+
} catch (err) {
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
_setCurrentEditable(target);
|
|
1243
|
+
target.classList.add(CLASS_ACTIVE);
|
|
1244
|
+
target.setAttribute("contenteditable", "true");
|
|
1245
|
+
target.focus();
|
|
1246
|
+
},
|
|
1247
|
+
true
|
|
1248
|
+
);
|
|
1249
|
+
doc.addEventListener("selectionchange", () => {
|
|
1250
|
+
injectToolbar(doc, {
|
|
1251
|
+
onCommand: handleToolbarCommand,
|
|
1252
|
+
canUndo: () => _getUndoStack().length > 1,
|
|
1253
|
+
canRedo: () => _getRedoStack().length > 0,
|
|
1254
|
+
onUndo: handleUndo,
|
|
1255
|
+
onRedo: handleRedo,
|
|
1256
|
+
getFormatState: () => computeFormatState(doc),
|
|
1257
|
+
getSelectedElementInfo: () => getElementLabel(_getCurrentEditable())
|
|
1258
|
+
});
|
|
1259
|
+
});
|
|
1260
|
+
doc.addEventListener("input", () => pushStandaloneSnapshot(), true);
|
|
1261
|
+
doc.addEventListener(
|
|
1262
|
+
"keydown",
|
|
1263
|
+
(e) => {
|
|
1264
|
+
const meta = e.ctrlKey || e.metaKey;
|
|
1265
|
+
if (!meta) return;
|
|
1266
|
+
const key = e.key.toLowerCase();
|
|
1267
|
+
if (key === "b") {
|
|
1268
|
+
e.preventDefault();
|
|
1269
|
+
handleToolbarCommand("bold");
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
if (key === "i") {
|
|
1273
|
+
e.preventDefault();
|
|
1274
|
+
handleToolbarCommand("italic");
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (key === "u") {
|
|
1278
|
+
e.preventDefault();
|
|
1279
|
+
handleToolbarCommand("underline");
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
if (key === "z") {
|
|
1283
|
+
e.preventDefault();
|
|
1284
|
+
if (e.shiftKey) {
|
|
1285
|
+
handleRedo();
|
|
1286
|
+
} else {
|
|
1287
|
+
handleUndo();
|
|
1288
|
+
}
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
if (key === "y") {
|
|
1292
|
+
e.preventDefault();
|
|
1293
|
+
handleRedo();
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
},
|
|
1297
|
+
true
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
// src/core/editor.ts
|
|
1302
|
+
function initRichEditor(iframe, config) {
|
|
1303
|
+
try {
|
|
1304
|
+
if (!iframe || !(iframe instanceof HTMLIFrameElement)) {
|
|
1305
|
+
throw new Error("Invalid iframe element provided to initRichEditor");
|
|
1306
|
+
}
|
|
1307
|
+
const doc = iframe.contentDocument;
|
|
1308
|
+
if (!doc) {
|
|
1309
|
+
throw new Error(
|
|
1310
|
+
"Unable to access iframe contentDocument. Ensure iframe src is same-origin."
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
_setDoc(doc);
|
|
1314
|
+
injectStyles(doc);
|
|
1315
|
+
_setUndoStack([]);
|
|
1316
|
+
_setRedoStack([]);
|
|
1317
|
+
_setCurrentEditable(null);
|
|
1318
|
+
if (config == null ? void 0 : config.maxStackSize) {
|
|
1319
|
+
import("./state-CZIMHTJ3.mjs").then((m) => m.setMaxStackSize(config.maxStackSize)).catch(() => {
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1322
|
+
attachStandaloneHandlers(doc);
|
|
1323
|
+
pushStandaloneSnapshot();
|
|
1324
|
+
injectToolbar(doc, {
|
|
1325
|
+
onCommand: handleToolbarCommand,
|
|
1326
|
+
canUndo: () => _getUndoStack().length > 1,
|
|
1327
|
+
canRedo: () => _getRedoStack().length > 0,
|
|
1328
|
+
onUndo: handleUndo,
|
|
1329
|
+
onRedo: handleRedo,
|
|
1330
|
+
getFormatState: () => computeFormatState(doc),
|
|
1331
|
+
getSelectedElementInfo: () => getElementLabel(_getCurrentEditable())
|
|
1332
|
+
});
|
|
1333
|
+
} catch (error) {
|
|
1334
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1335
|
+
console.error("[rich-html-editor] Failed to initialize editor:", message);
|
|
1336
|
+
throw error;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
function getCleanHTML() {
|
|
1340
|
+
try {
|
|
1341
|
+
const doc = _getDoc();
|
|
1342
|
+
if (!doc) {
|
|
1343
|
+
console.warn(
|
|
1344
|
+
"[rich-html-editor] getCleanHTML called before editor initialization"
|
|
1345
|
+
);
|
|
1346
|
+
return "";
|
|
1347
|
+
}
|
|
1348
|
+
if (!doc.documentElement) {
|
|
1349
|
+
throw new Error("Document is missing documentElement");
|
|
1350
|
+
}
|
|
1351
|
+
const clone = doc.documentElement.cloneNode(true);
|
|
1352
|
+
const toolbarNode = clone.querySelector(`#${TOOLBAR_ID}`);
|
|
1353
|
+
if (toolbarNode && toolbarNode.parentNode)
|
|
1354
|
+
toolbarNode.parentNode.removeChild(toolbarNode);
|
|
1355
|
+
const styleNode = clone.querySelector(`#${STYLE_ID}`);
|
|
1356
|
+
if (styleNode && styleNode.parentNode)
|
|
1357
|
+
styleNode.parentNode.removeChild(styleNode);
|
|
1358
|
+
try {
|
|
1359
|
+
const cleanElement = (el) => {
|
|
1360
|
+
try {
|
|
1361
|
+
if (el.hasAttribute("contenteditable"))
|
|
1362
|
+
el.removeAttribute("contenteditable");
|
|
1363
|
+
if (el.hasAttribute("tabindex")) el.removeAttribute("tabindex");
|
|
1364
|
+
} catch (e) {
|
|
1365
|
+
}
|
|
1366
|
+
try {
|
|
1367
|
+
const attrs = Array.from(el.attributes || []);
|
|
1368
|
+
attrs.forEach((a) => {
|
|
1369
|
+
const rawName = a.name;
|
|
1370
|
+
const name = rawName.toLowerCase();
|
|
1371
|
+
if (name.startsWith("on")) {
|
|
1372
|
+
try {
|
|
1373
|
+
el.removeAttribute(rawName);
|
|
1374
|
+
} catch (e) {
|
|
1375
|
+
}
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (name === "data-rhe-id" || name.startsWith("data-rhe-") || name === "data-rhe") {
|
|
1379
|
+
try {
|
|
1380
|
+
el.removeAttribute(rawName);
|
|
1381
|
+
} catch (e) {
|
|
1382
|
+
}
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
} catch (e) {
|
|
1387
|
+
}
|
|
1388
|
+
try {
|
|
1389
|
+
if (el.id) {
|
|
1390
|
+
const id = el.id;
|
|
1391
|
+
if (id === TOOLBAR_ID || id === STYLE_ID || id.startsWith("editor-") || id.startsWith("rhe-")) {
|
|
1392
|
+
el.removeAttribute("id");
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
} catch (e) {
|
|
1396
|
+
}
|
|
1397
|
+
try {
|
|
1398
|
+
const cls = Array.from(el.classList || []);
|
|
1399
|
+
cls.forEach((c) => {
|
|
1400
|
+
if (c === CLASS_EDITABLE || c === CLASS_ACTIVE || c.startsWith("editor-") || c.startsWith("rhe-")) {
|
|
1401
|
+
try {
|
|
1402
|
+
el.classList.remove(c);
|
|
1403
|
+
} catch (e) {
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
if (el.hasAttribute("class") && (el.getAttribute("class") || "").trim() === "") {
|
|
1408
|
+
try {
|
|
1409
|
+
el.removeAttribute("class");
|
|
1410
|
+
} catch (e) {
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
} catch (e) {
|
|
1414
|
+
}
|
|
1415
|
+
try {
|
|
1416
|
+
const children = Array.from(el.children || []);
|
|
1417
|
+
children.forEach((child) => cleanElement(child));
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
if (clone.nodeType === Node.ELEMENT_NODE) {
|
|
1422
|
+
cleanElement(clone);
|
|
1423
|
+
}
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
}
|
|
1426
|
+
return "<!doctype html>\n" + clone.outerHTML;
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1429
|
+
console.error("[rich-html-editor] Failed to get clean HTML:", message);
|
|
1430
|
+
throw error;
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
export {
|
|
1434
|
+
editorEventEmitter,
|
|
1435
|
+
getCleanHTML,
|
|
1436
|
+
getEditorEventEmitter,
|
|
1437
|
+
initRichEditor
|
|
1438
|
+
};
|
|
1439
|
+
//# sourceMappingURL=index.mjs.map
|