smartrte-react 0.2.2 → 0.2.4
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/dist/components/ClassicEditor.d.ts +2 -2
- package/dist/components/ClassicEditor.js +1243 -622
- package/dist/components/MediaManager.js +4 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/standalone/classic-editor-embed.js +1 -1
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +25 -3
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { MediaManager } from "./MediaManager";
|
|
3
|
+
import { MediaManager } from "./MediaManager.js";
|
|
4
4
|
import * as pdfjsLib from 'pdfjs-dist';
|
|
5
5
|
import mammoth from 'mammoth';
|
|
6
6
|
import JSZip from 'jszip';
|
|
7
|
-
import { ensureStyleSheet } from '../theme';
|
|
7
|
+
import { ensureStyleSheet } from '../theme.js';
|
|
8
8
|
// Initialize PDF.js worker
|
|
9
9
|
if (typeof window !== 'undefined') {
|
|
10
10
|
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
|
@@ -20,6 +20,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
20
20
|
], defaultFont, preserveFontFamily = false, preserveColors = false, preserveDocxStyles = true, theme = "light", className, }) {
|
|
21
21
|
ensureStyleSheet();
|
|
22
22
|
const editableRef = useRef(null);
|
|
23
|
+
const editorScrollRef = useRef(null);
|
|
23
24
|
const lastEmittedRef = useRef("");
|
|
24
25
|
const isComposingRef = useRef(false);
|
|
25
26
|
const fileInputRef = useRef(null);
|
|
@@ -53,6 +54,18 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
53
54
|
const savedRangeRef = useRef(null);
|
|
54
55
|
const [currentFontSize, setCurrentFontSize] = useState("");
|
|
55
56
|
const [currentFont, setCurrentFont] = useState("");
|
|
57
|
+
const [activeState, setActiveState] = useState({
|
|
58
|
+
bold: false,
|
|
59
|
+
italic: false,
|
|
60
|
+
underline: false,
|
|
61
|
+
strikeThrough: false,
|
|
62
|
+
subscript: false,
|
|
63
|
+
superscript: false,
|
|
64
|
+
unorderedList: false,
|
|
65
|
+
orderedList: false,
|
|
66
|
+
blockquote: false,
|
|
67
|
+
codeBlock: false,
|
|
68
|
+
});
|
|
56
69
|
useEffect(() => {
|
|
57
70
|
const el = editableRef.current;
|
|
58
71
|
if (!el)
|
|
@@ -77,6 +90,35 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
77
90
|
el.removeEventListener("contextmenu", onCtx, { capture: true });
|
|
78
91
|
};
|
|
79
92
|
}, [value]);
|
|
93
|
+
const updateActiveState = () => {
|
|
94
|
+
const editor = editableRef.current;
|
|
95
|
+
if (!editor)
|
|
96
|
+
return;
|
|
97
|
+
try {
|
|
98
|
+
const sel = window.getSelection();
|
|
99
|
+
const range = sel && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
|
|
100
|
+
const inEditor = range && editor.contains(range.commonAncestorContainer);
|
|
101
|
+
if (!inEditor)
|
|
102
|
+
return;
|
|
103
|
+
let node = range.commonAncestorContainer;
|
|
104
|
+
if (node.nodeType === Node.TEXT_NODE)
|
|
105
|
+
node = node.parentNode;
|
|
106
|
+
const element = node instanceof HTMLElement ? node : null;
|
|
107
|
+
setActiveState({
|
|
108
|
+
bold: document.queryCommandState("bold"),
|
|
109
|
+
italic: document.queryCommandState("italic"),
|
|
110
|
+
underline: document.queryCommandState("underline"),
|
|
111
|
+
strikeThrough: document.queryCommandState("strikeThrough"),
|
|
112
|
+
subscript: document.queryCommandState("subscript"),
|
|
113
|
+
superscript: document.queryCommandState("superscript"),
|
|
114
|
+
unorderedList: Boolean(element?.closest("ul")),
|
|
115
|
+
orderedList: Boolean(element?.closest("ol")),
|
|
116
|
+
blockquote: Boolean(element?.closest("blockquote")),
|
|
117
|
+
codeBlock: Boolean(element?.closest("pre")),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch { }
|
|
121
|
+
};
|
|
80
122
|
// Save selection whenever it changes
|
|
81
123
|
useEffect(() => {
|
|
82
124
|
const saveSelection = () => {
|
|
@@ -86,6 +128,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
86
128
|
const editor = editableRef.current;
|
|
87
129
|
if (editor && editor.contains(range.commonAncestorContainer)) {
|
|
88
130
|
savedRangeRef.current = range.cloneRange();
|
|
131
|
+
updateActiveState();
|
|
89
132
|
}
|
|
90
133
|
}
|
|
91
134
|
};
|
|
@@ -96,8 +139,27 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
96
139
|
}, []);
|
|
97
140
|
const exec = (command, valueArg) => {
|
|
98
141
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
142
|
+
if (!restoreSavedSelection()) {
|
|
143
|
+
safeSelectRange(getSelectionRangeInEditor());
|
|
144
|
+
}
|
|
145
|
+
if (command === "insertUnorderedList") {
|
|
146
|
+
toggleList("ul");
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (command === "insertOrderedList") {
|
|
150
|
+
toggleList("ol");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const beforeHtml = editableRef.current?.innerHTML || "";
|
|
154
|
+
const ok = document.execCommand(command, false, valueArg);
|
|
155
|
+
const afterHtml = editableRef.current?.innerHTML || "";
|
|
156
|
+
const needsFallback = !ok ||
|
|
157
|
+
(command === "formatBlock" && beforeHtml === afterHtml);
|
|
158
|
+
if (needsFallback) {
|
|
159
|
+
if (command === "formatBlock" && valueArg)
|
|
160
|
+
applyFormatBlockFallback(valueArg);
|
|
161
|
+
}
|
|
162
|
+
handleInput();
|
|
101
163
|
}
|
|
102
164
|
catch { }
|
|
103
165
|
};
|
|
@@ -114,6 +176,29 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
114
176
|
onChange(html);
|
|
115
177
|
}
|
|
116
178
|
};
|
|
179
|
+
const restoreSavedSelection = () => {
|
|
180
|
+
const editor = editableRef.current;
|
|
181
|
+
if (!editor)
|
|
182
|
+
return false;
|
|
183
|
+
const saved = savedRangeRef.current;
|
|
184
|
+
if (saved && editor.contains(saved.commonAncestorContainer)) {
|
|
185
|
+
editor.focus({ preventScroll: true });
|
|
186
|
+
safeSelectRange(saved.cloneRange());
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
editor.focus({ preventScroll: true });
|
|
190
|
+
return false;
|
|
191
|
+
};
|
|
192
|
+
const preserveEditorSelection = () => {
|
|
193
|
+
const editor = editableRef.current;
|
|
194
|
+
const sel = window.getSelection();
|
|
195
|
+
if (!editor || !sel || sel.rangeCount === 0)
|
|
196
|
+
return;
|
|
197
|
+
const range = sel.getRangeAt(0);
|
|
198
|
+
if (editor.contains(range.commonAncestorContainer)) {
|
|
199
|
+
savedRangeRef.current = range.cloneRange();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
117
202
|
const insertLink = () => {
|
|
118
203
|
const url = window.prompt("Enter URL", "https://");
|
|
119
204
|
if (!url)
|
|
@@ -124,7 +209,6 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
124
209
|
const editor = editableRef.current;
|
|
125
210
|
if (!editor)
|
|
126
211
|
return null;
|
|
127
|
-
editor.focus();
|
|
128
212
|
const sel = window.getSelection();
|
|
129
213
|
if (sel && sel.rangeCount > 0) {
|
|
130
214
|
const range = sel.getRangeAt(0);
|
|
@@ -139,6 +223,307 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
139
223
|
range.collapse(false);
|
|
140
224
|
return range;
|
|
141
225
|
};
|
|
226
|
+
const getCurrentBlock = () => {
|
|
227
|
+
const editor = editableRef.current;
|
|
228
|
+
const range = getSelectionRangeInEditor();
|
|
229
|
+
if (!editor || !range)
|
|
230
|
+
return null;
|
|
231
|
+
let node = range.commonAncestorContainer;
|
|
232
|
+
if (node.nodeType === Node.TEXT_NODE)
|
|
233
|
+
node = node.parentNode;
|
|
234
|
+
const element = node instanceof HTMLElement ? node : null;
|
|
235
|
+
const block = element?.closest("p,h1,h2,h3,h4,h5,h6,li,blockquote,pre,div");
|
|
236
|
+
if (!block || block === editor || !editor.contains(block))
|
|
237
|
+
return null;
|
|
238
|
+
return block;
|
|
239
|
+
};
|
|
240
|
+
const blockSelector = "p,h1,h2,h3,h4,h5,h6,li,blockquote,pre,div";
|
|
241
|
+
const getSelectedBlocks = (range) => {
|
|
242
|
+
const editor = editableRef.current;
|
|
243
|
+
if (!editor)
|
|
244
|
+
return [];
|
|
245
|
+
const blocks = Array.from(editor.querySelectorAll(blockSelector))
|
|
246
|
+
.filter((block) => {
|
|
247
|
+
if (block === editor)
|
|
248
|
+
return false;
|
|
249
|
+
const parentBlock = block.parentElement?.closest(blockSelector);
|
|
250
|
+
if (parentBlock && parentBlock !== editor && editor.contains(parentBlock))
|
|
251
|
+
return false;
|
|
252
|
+
try {
|
|
253
|
+
return range.intersectsNode(block);
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
if (blocks.length > 0)
|
|
260
|
+
return blocks;
|
|
261
|
+
const current = getCurrentBlock();
|
|
262
|
+
return current ? [current] : [];
|
|
263
|
+
};
|
|
264
|
+
const getSelectedListItems = (blocks) => {
|
|
265
|
+
const seen = new Set();
|
|
266
|
+
const items = [];
|
|
267
|
+
blocks.forEach((block) => {
|
|
268
|
+
const li = block.tagName.toLowerCase() === "li"
|
|
269
|
+
? block
|
|
270
|
+
: block.closest("li");
|
|
271
|
+
if (li && editableRef.current?.contains(li) && !seen.has(li)) {
|
|
272
|
+
seen.add(li);
|
|
273
|
+
items.push(li);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
return items;
|
|
277
|
+
};
|
|
278
|
+
const copyCellOrBlockStyles = (from, to) => {
|
|
279
|
+
to.innerHTML = from.innerHTML || "<br>";
|
|
280
|
+
const style = from.getAttribute("style");
|
|
281
|
+
if (style)
|
|
282
|
+
to.setAttribute("style", style);
|
|
283
|
+
};
|
|
284
|
+
const applyHeaderCellStyle = (cell) => {
|
|
285
|
+
cell.style.fontWeight = "700";
|
|
286
|
+
cell.style.background = "#f3f4f6";
|
|
287
|
+
cell.style.textAlign = cell.style.textAlign || "left";
|
|
288
|
+
};
|
|
289
|
+
const clearHeaderCellStyle = (cell) => {
|
|
290
|
+
if (cell.style.fontWeight === "700" || cell.style.fontWeight === "bold")
|
|
291
|
+
cell.style.fontWeight = "";
|
|
292
|
+
if (cell.style.background === "var(--srte-surface-subtle)" || cell.style.background === "rgb(243, 244, 246)" || cell.style.background === "#f3f4f6")
|
|
293
|
+
cell.style.background = "";
|
|
294
|
+
};
|
|
295
|
+
const replaceTableCellTag = (cell, tag) => {
|
|
296
|
+
const replacement = document.createElement(tag);
|
|
297
|
+
replacement.innerHTML = cell.innerHTML || " ";
|
|
298
|
+
replacement.colSpan = cell.colSpan;
|
|
299
|
+
replacement.rowSpan = cell.rowSpan;
|
|
300
|
+
const style = cell.getAttribute("style");
|
|
301
|
+
if (style)
|
|
302
|
+
replacement.setAttribute("style", style);
|
|
303
|
+
if (cell.__rtePrevBg != null) {
|
|
304
|
+
replacement.style.background = cell.__rtePrevBg || "";
|
|
305
|
+
delete cell.__rtePrevBg;
|
|
306
|
+
}
|
|
307
|
+
if (replacement.style.background === "var(--srte-accent-bg)" ||
|
|
308
|
+
replacement.style.background.includes("59, 130, 246") ||
|
|
309
|
+
replacement.style.background.includes("59, 158, 255")) {
|
|
310
|
+
replacement.style.background = "";
|
|
311
|
+
}
|
|
312
|
+
replacement.style.outline = "";
|
|
313
|
+
replacement.style.outlineOffset = "";
|
|
314
|
+
replacement.style.border = replacement.style.border || "1px solid #d1d5db";
|
|
315
|
+
replacement.style.padding = replacement.style.padding || "6px";
|
|
316
|
+
replacement.style.minWidth = replacement.style.minWidth || "60px";
|
|
317
|
+
if (tag === "th")
|
|
318
|
+
applyHeaderCellStyle(replacement);
|
|
319
|
+
else
|
|
320
|
+
clearHeaderCellStyle(replacement);
|
|
321
|
+
cell.parentElement?.replaceChild(replacement, cell);
|
|
322
|
+
return replacement;
|
|
323
|
+
};
|
|
324
|
+
const applyFormatBlockFallback = (blockName) => {
|
|
325
|
+
const editor = editableRef.current;
|
|
326
|
+
const block = getCurrentBlock();
|
|
327
|
+
const tag = blockName.replace(/[<>]/g, "").toLowerCase() || "p";
|
|
328
|
+
if (!editor || !block || block === editor || !/^(p|h1|h2|h3|h4|h5|h6|pre|blockquote)$/.test(tag))
|
|
329
|
+
return;
|
|
330
|
+
const replacement = document.createElement(tag);
|
|
331
|
+
copyCellOrBlockStyles(block, replacement);
|
|
332
|
+
block.parentElement?.replaceChild(replacement, block);
|
|
333
|
+
const range = document.createRange();
|
|
334
|
+
range.selectNodeContents(replacement);
|
|
335
|
+
range.collapse(false);
|
|
336
|
+
safeSelectRange(range);
|
|
337
|
+
};
|
|
338
|
+
const cloneListShell = (list, tagName) => {
|
|
339
|
+
const clone = document.createElement(tagName || list.tagName.toLowerCase());
|
|
340
|
+
Array.from(list.attributes).forEach((attr) => clone.setAttribute(attr.name, attr.value));
|
|
341
|
+
return clone;
|
|
342
|
+
};
|
|
343
|
+
const focusElementEnd = (element) => {
|
|
344
|
+
const range = document.createRange();
|
|
345
|
+
range.selectNodeContents(element);
|
|
346
|
+
range.collapse(false);
|
|
347
|
+
safeSelectRange(range);
|
|
348
|
+
savedRangeRef.current = range.cloneRange();
|
|
349
|
+
};
|
|
350
|
+
const insertEmptyListAtSelection = (listTag) => {
|
|
351
|
+
const editor = editableRef.current;
|
|
352
|
+
const range = getSelectionRangeInEditor();
|
|
353
|
+
if (!editor || !range)
|
|
354
|
+
return;
|
|
355
|
+
const list = document.createElement(listTag);
|
|
356
|
+
const li = document.createElement("li");
|
|
357
|
+
li.innerHTML = "<br>";
|
|
358
|
+
list.appendChild(li);
|
|
359
|
+
range.deleteContents();
|
|
360
|
+
range.insertNode(list);
|
|
361
|
+
focusElementEnd(li);
|
|
362
|
+
};
|
|
363
|
+
const unwrapListItem = (li, list) => {
|
|
364
|
+
const parent = list.parentElement;
|
|
365
|
+
if (!parent)
|
|
366
|
+
return;
|
|
367
|
+
const paragraph = document.createElement("p");
|
|
368
|
+
paragraph.innerHTML = li.innerHTML || "<br>";
|
|
369
|
+
const beforeList = cloneListShell(list);
|
|
370
|
+
const afterList = cloneListShell(list);
|
|
371
|
+
while (list.firstChild && list.firstChild !== li) {
|
|
372
|
+
beforeList.appendChild(list.firstChild);
|
|
373
|
+
}
|
|
374
|
+
while (li.nextSibling) {
|
|
375
|
+
afterList.appendChild(li.nextSibling);
|
|
376
|
+
}
|
|
377
|
+
if (beforeList.childNodes.length)
|
|
378
|
+
parent.insertBefore(beforeList, list);
|
|
379
|
+
parent.insertBefore(paragraph, list);
|
|
380
|
+
if (afterList.childNodes.length)
|
|
381
|
+
parent.insertBefore(afterList, list);
|
|
382
|
+
li.remove();
|
|
383
|
+
if (!list.querySelector("li"))
|
|
384
|
+
list.remove();
|
|
385
|
+
focusElementEnd(paragraph);
|
|
386
|
+
};
|
|
387
|
+
const convertListTag = (list, listTag) => {
|
|
388
|
+
const replacement = cloneListShell(list, listTag);
|
|
389
|
+
replacement.innerHTML = list.innerHTML;
|
|
390
|
+
list.parentElement?.replaceChild(replacement, list);
|
|
391
|
+
const li = replacement.querySelector("li");
|
|
392
|
+
focusElementEnd(li || replacement);
|
|
393
|
+
};
|
|
394
|
+
const getOrCreateNestedList = (li, listTag) => {
|
|
395
|
+
let nested = Array.from(li.children).find((child) => {
|
|
396
|
+
const tag = child.tagName.toLowerCase();
|
|
397
|
+
return tag === "ul" || tag === "ol";
|
|
398
|
+
});
|
|
399
|
+
if (nested && nested.tagName.toLowerCase() !== listTag) {
|
|
400
|
+
const replacement = cloneListShell(nested, listTag);
|
|
401
|
+
replacement.innerHTML = nested.innerHTML;
|
|
402
|
+
nested.parentElement?.replaceChild(replacement, nested);
|
|
403
|
+
nested = replacement;
|
|
404
|
+
}
|
|
405
|
+
if (!nested) {
|
|
406
|
+
nested = document.createElement(listTag);
|
|
407
|
+
li.appendChild(nested);
|
|
408
|
+
}
|
|
409
|
+
return nested;
|
|
410
|
+
};
|
|
411
|
+
const nestSelectedListItems = (items, listTag) => {
|
|
412
|
+
if (items.length === 0)
|
|
413
|
+
return false;
|
|
414
|
+
let changed = false;
|
|
415
|
+
let lastTargetList = null;
|
|
416
|
+
items.forEach((li) => {
|
|
417
|
+
const previous = li.previousElementSibling;
|
|
418
|
+
if (!previous || previous.tagName.toLowerCase() !== "li")
|
|
419
|
+
return;
|
|
420
|
+
const targetList = getOrCreateNestedList(previous, listTag);
|
|
421
|
+
targetList.appendChild(li);
|
|
422
|
+
lastTargetList = targetList;
|
|
423
|
+
changed = true;
|
|
424
|
+
});
|
|
425
|
+
if (changed) {
|
|
426
|
+
const lastItem = lastTargetList?.lastElementChild;
|
|
427
|
+
if (lastItem)
|
|
428
|
+
focusElementEnd(lastItem);
|
|
429
|
+
}
|
|
430
|
+
return changed;
|
|
431
|
+
};
|
|
432
|
+
const convertSelectedBlocksToList = (blocks, listTag) => {
|
|
433
|
+
const editor = editableRef.current;
|
|
434
|
+
if (!editor || blocks.length === 0)
|
|
435
|
+
return false;
|
|
436
|
+
const selected = blocks.filter((block) => {
|
|
437
|
+
if (!editor.contains(block))
|
|
438
|
+
return false;
|
|
439
|
+
if (block.tagName.toLowerCase() === "li")
|
|
440
|
+
return false;
|
|
441
|
+
if (block.closest("ul,ol"))
|
|
442
|
+
return false;
|
|
443
|
+
return block.parentElement;
|
|
444
|
+
});
|
|
445
|
+
if (selected.length === 0)
|
|
446
|
+
return false;
|
|
447
|
+
const groups = new Map();
|
|
448
|
+
selected.forEach((block) => {
|
|
449
|
+
const parent = block.parentElement;
|
|
450
|
+
if (!parent)
|
|
451
|
+
return;
|
|
452
|
+
const group = groups.get(parent) || [];
|
|
453
|
+
group.push(block);
|
|
454
|
+
groups.set(parent, group);
|
|
455
|
+
});
|
|
456
|
+
let lastLi = null;
|
|
457
|
+
groups.forEach((group, parent) => {
|
|
458
|
+
group.sort((a, b) => {
|
|
459
|
+
const position = a.compareDocumentPosition(b);
|
|
460
|
+
return position & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
|
|
461
|
+
});
|
|
462
|
+
const list = document.createElement(listTag);
|
|
463
|
+
parent.insertBefore(list, group[0]);
|
|
464
|
+
group.forEach((block) => {
|
|
465
|
+
const li = document.createElement("li");
|
|
466
|
+
li.innerHTML = block.innerHTML || "<br>";
|
|
467
|
+
list.appendChild(li);
|
|
468
|
+
block.remove();
|
|
469
|
+
lastLi = li;
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
if (lastLi)
|
|
473
|
+
focusElementEnd(lastLi);
|
|
474
|
+
return true;
|
|
475
|
+
};
|
|
476
|
+
const toggleList = (listTag) => {
|
|
477
|
+
const editor = editableRef.current;
|
|
478
|
+
if (!editor)
|
|
479
|
+
return;
|
|
480
|
+
if (!restoreSavedSelection())
|
|
481
|
+
safeSelectRange(getSelectionRangeInEditor());
|
|
482
|
+
const range = getSelectionRangeInEditor();
|
|
483
|
+
if (range && !range.collapsed) {
|
|
484
|
+
const blocks = getSelectedBlocks(range);
|
|
485
|
+
const selectedListItems = getSelectedListItems(blocks);
|
|
486
|
+
const convertedBlocks = convertSelectedBlocksToList(blocks, listTag);
|
|
487
|
+
const nestedItems = nestSelectedListItems(selectedListItems, listTag);
|
|
488
|
+
if (convertedBlocks || nestedItems) {
|
|
489
|
+
handleInput();
|
|
490
|
+
requestAnimationFrame(updateActiveState);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
const block = getCurrentBlock();
|
|
495
|
+
if (!block) {
|
|
496
|
+
insertEmptyListAtSelection(listTag);
|
|
497
|
+
handleInput();
|
|
498
|
+
requestAnimationFrame(updateActiveState);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const currentList = block.closest("ul,ol");
|
|
502
|
+
if (currentList && editor.contains(currentList)) {
|
|
503
|
+
if (currentList.tagName.toLowerCase() === listTag) {
|
|
504
|
+
const li = block.closest("li");
|
|
505
|
+
if (li)
|
|
506
|
+
unwrapListItem(li, currentList);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
convertListTag(currentList, listTag);
|
|
510
|
+
}
|
|
511
|
+
handleInput();
|
|
512
|
+
requestAnimationFrame(updateActiveState);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const list = document.createElement(listTag);
|
|
516
|
+
const li = document.createElement("li");
|
|
517
|
+
li.innerHTML = block.innerHTML || "<br>";
|
|
518
|
+
list.appendChild(li);
|
|
519
|
+
block.parentElement?.replaceChild(list, block);
|
|
520
|
+
focusElementEnd(li);
|
|
521
|
+
handleInput();
|
|
522
|
+
requestAnimationFrame(updateActiveState);
|
|
523
|
+
};
|
|
524
|
+
const toggleListFallback = (listTag) => {
|
|
525
|
+
toggleList(listTag);
|
|
526
|
+
};
|
|
142
527
|
const insertTextAtSelection = (text) => {
|
|
143
528
|
if (!text)
|
|
144
529
|
return;
|
|
@@ -310,15 +695,17 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
310
695
|
};
|
|
311
696
|
const scheduleImageOverlay = () => {
|
|
312
697
|
const img = selectedImage;
|
|
698
|
+
const scroller = editorScrollRef.current;
|
|
313
699
|
if (!img) {
|
|
314
700
|
setImageOverlay(null);
|
|
315
701
|
return;
|
|
316
702
|
}
|
|
317
703
|
try {
|
|
318
704
|
const rect = img.getBoundingClientRect();
|
|
705
|
+
const hostRect = scroller?.getBoundingClientRect();
|
|
319
706
|
setImageOverlay({
|
|
320
|
-
left: rect.left,
|
|
321
|
-
top: rect.top,
|
|
707
|
+
left: hostRect && scroller ? rect.left - hostRect.left + scroller.scrollLeft : rect.left,
|
|
708
|
+
top: hostRect && scroller ? rect.top - hostRect.top + scroller.scrollTop : rect.top,
|
|
322
709
|
width: rect.width,
|
|
323
710
|
height: rect.height,
|
|
324
711
|
});
|
|
@@ -617,6 +1004,16 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
617
1004
|
if (!media)
|
|
618
1005
|
return;
|
|
619
1006
|
const list = Array.from(files).filter((f) => f.type.startsWith("image/"));
|
|
1007
|
+
if (mediaManager) {
|
|
1008
|
+
try {
|
|
1009
|
+
const uploaded = await mediaManager.upload(list);
|
|
1010
|
+
uploaded.forEach((item) => insertImageAtSelection(item));
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
catch (error) {
|
|
1014
|
+
console.error("Image upload failed, inserting local image data instead:", error);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
620
1017
|
for (const f of list) {
|
|
621
1018
|
await new Promise((resolve) => {
|
|
622
1019
|
const reader = new FileReader();
|
|
@@ -669,6 +1066,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
669
1066
|
let fullHtml = '';
|
|
670
1067
|
for (let i = 1; i <= pdf.numPages; i++) {
|
|
671
1068
|
const page = await pdf.getPage(i);
|
|
1069
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
672
1070
|
const textContent = await page.getTextContent();
|
|
673
1071
|
const styles = textContent.styles;
|
|
674
1072
|
// 1. Group items into lines
|
|
@@ -740,8 +1138,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
740
1138
|
const width = item.width;
|
|
741
1139
|
const fontName = item.fontName;
|
|
742
1140
|
const fontObj = styles[fontName];
|
|
743
|
-
const
|
|
744
|
-
|
|
1141
|
+
const fontFamily = fontObj?.fontFamily?.toLowerCase() || '';
|
|
1142
|
+
const isBold = fontFamily.includes('bold') || false;
|
|
1143
|
+
const isItalic = fontFamily.includes('italic') || fontFamily.includes('oblique');
|
|
1144
|
+
const fontSize = Math.max(8, Math.round(Math.abs(item.transform[3])));
|
|
745
1145
|
if (lastX > 0) {
|
|
746
1146
|
const gap = x - lastX;
|
|
747
1147
|
if (gap > 2) { // Minimal space threshold
|
|
@@ -760,10 +1160,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
760
1160
|
itemXs.push(x);
|
|
761
1161
|
}
|
|
762
1162
|
// Append text style
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1163
|
+
const chunkStyle = cssRules([
|
|
1164
|
+
['font-size', `${fontSize}px`],
|
|
1165
|
+
['font-weight', isBold ? '700' : ''],
|
|
1166
|
+
['font-style', isItalic ? 'italic' : ''],
|
|
1167
|
+
]);
|
|
1168
|
+
let chunk = `<span${styleAttr(chunkStyle)}>${escapeHtml(item.str)}</span>`;
|
|
767
1169
|
lineText += item.str;
|
|
768
1170
|
lineHtmlContent += chunk;
|
|
769
1171
|
lastX = x + width;
|
|
@@ -834,8 +1236,15 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
834
1236
|
const w = item.width;
|
|
835
1237
|
const txt = item.str;
|
|
836
1238
|
const fontObj = styles[item.fontName];
|
|
837
|
-
const
|
|
838
|
-
const
|
|
1239
|
+
const fontFamily = fontObj?.fontFamily?.toLowerCase() || '';
|
|
1240
|
+
const isBold = fontFamily.includes('bold');
|
|
1241
|
+
const isItalic = fontFamily.includes('italic') || fontFamily.includes('oblique');
|
|
1242
|
+
const fontSize = Math.max(8, Math.round(Math.abs(item.transform[3])));
|
|
1243
|
+
const styledTxt = `<span${styleAttr(cssRules([
|
|
1244
|
+
['font-size', `${fontSize}px`],
|
|
1245
|
+
['font-weight', isBold ? '700' : ''],
|
|
1246
|
+
['font-style', isItalic ? 'italic' : ''],
|
|
1247
|
+
]))}>${escapeHtml(txt)}</span>`;
|
|
839
1248
|
// Decide which column this belongs to
|
|
840
1249
|
// Find closest column to the left (or close enough)
|
|
841
1250
|
let colIdx = 0;
|
|
@@ -854,7 +1263,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
854
1263
|
cellContents[colIdx] += styledTxt;
|
|
855
1264
|
}
|
|
856
1265
|
cellContents.forEach(content => {
|
|
857
|
-
rowHtml += `<td style="border:1px solid
|
|
1266
|
+
rowHtml += `<td style="border:1px solid #d1d5db;padding:8px;vertical-align:top;">${content || ' '}</td>`;
|
|
858
1267
|
});
|
|
859
1268
|
rowHtml += '</tr>';
|
|
860
1269
|
tableHtml += rowHtml;
|
|
@@ -877,10 +1286,23 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
877
1286
|
closeList();
|
|
878
1287
|
if (isHeader) {
|
|
879
1288
|
const tag = maxH > medianHeight * 1.5 ? 'h2' : 'h3';
|
|
880
|
-
|
|
1289
|
+
const firstX = line.items[0]?.transform?.[4] || 0;
|
|
1290
|
+
const lastItem = line.items[line.items.length - 1];
|
|
1291
|
+
const lastRight = lastItem ? lastItem.transform[4] + lastItem.width : firstX;
|
|
1292
|
+
const center = (firstX + lastRight) / 2;
|
|
1293
|
+
const align = Math.abs(center - viewport.width / 2) < viewport.width * 0.12 ? 'center' : firstX > viewport.width * 0.55 ? 'right' : '';
|
|
1294
|
+
html += `<${tag}${styleAttr(cssRules([['text-align', align]]))}>${lineHtmlContent}</${tag}>`;
|
|
881
1295
|
}
|
|
882
1296
|
else {
|
|
883
|
-
|
|
1297
|
+
const firstX = line.items[0]?.transform?.[4] || 0;
|
|
1298
|
+
const lastItem = line.items[line.items.length - 1];
|
|
1299
|
+
const lastRight = lastItem ? lastItem.transform[4] + lastItem.width : firstX;
|
|
1300
|
+
const center = (firstX + lastRight) / 2;
|
|
1301
|
+
const align = Math.abs(center - viewport.width / 2) < viewport.width * 0.12 ? 'center' : firstX > viewport.width * 0.55 ? 'right' : '';
|
|
1302
|
+
html += `<p${styleAttr(cssRules([
|
|
1303
|
+
['text-align', align],
|
|
1304
|
+
['margin-left', firstX > 40 && !align ? `${Math.round(firstX)}px` : ''],
|
|
1305
|
+
]))}>${lineHtmlContent}</p>`;
|
|
884
1306
|
}
|
|
885
1307
|
}
|
|
886
1308
|
}
|
|
@@ -889,7 +1311,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
889
1311
|
closeTable();
|
|
890
1312
|
fullHtml += html;
|
|
891
1313
|
}
|
|
892
|
-
insertImportedHtml(fullHtml, mode);
|
|
1314
|
+
insertImportedHtml(fullHtml, mode, { preserveColors: true, preserveDocumentLayout: true });
|
|
893
1315
|
}
|
|
894
1316
|
catch (error) {
|
|
895
1317
|
console.error('Error reading PDF:', error);
|
|
@@ -1146,63 +1568,163 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1146
1568
|
.replace(/&/g, "&")
|
|
1147
1569
|
.replace(/</g, "<")
|
|
1148
1570
|
.replace(/>/g, ">");
|
|
1571
|
+
const escapeHtmlAttribute = (value) => escapeHtml(value).replace(/"/g, """);
|
|
1149
1572
|
const markdownToHtml = (markdown) => {
|
|
1150
1573
|
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
1151
1574
|
let html = "";
|
|
1152
1575
|
let listType = null;
|
|
1576
|
+
let paragraph = [];
|
|
1577
|
+
let codeFence = null;
|
|
1153
1578
|
const closeList = () => {
|
|
1154
1579
|
if (listType) {
|
|
1155
1580
|
html += `</${listType}>`;
|
|
1156
1581
|
listType = null;
|
|
1157
1582
|
}
|
|
1158
1583
|
};
|
|
1159
|
-
const inline = (text) =>
|
|
1160
|
-
|
|
1161
|
-
.replace(
|
|
1162
|
-
|
|
1163
|
-
|
|
1584
|
+
const inline = (text) => {
|
|
1585
|
+
const codeTokens = [];
|
|
1586
|
+
let value = text.replace(/`([^`]+)`/g, (_match, code) => {
|
|
1587
|
+
const token = `@@SRTE_CODE_${codeTokens.length}@@`;
|
|
1588
|
+
codeTokens.push(`<code>${escapeHtml(code)}</code>`);
|
|
1589
|
+
return token;
|
|
1590
|
+
});
|
|
1591
|
+
value = escapeHtml(value)
|
|
1592
|
+
.replace(/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/g, (_match, alt, src, title) => {
|
|
1593
|
+
const titleAttr = title ? ` title="${escapeHtmlAttribute(title)}"` : "";
|
|
1594
|
+
return `<img src="${escapeHtmlAttribute(src)}" alt="${escapeHtmlAttribute(alt)}"${titleAttr}>`;
|
|
1595
|
+
})
|
|
1596
|
+
.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"([^"]+)")?\)/g, (_match, label, href, title) => {
|
|
1597
|
+
const titleAttr = title ? ` title="${escapeHtmlAttribute(title)}"` : "";
|
|
1598
|
+
return `<a href="${escapeHtmlAttribute(href)}"${titleAttr}>${label}</a>`;
|
|
1599
|
+
})
|
|
1600
|
+
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
|
1601
|
+
.replace(/__([^_]+)__/g, "<strong>$1</strong>")
|
|
1602
|
+
.replace(/~~([^~]+)~~/g, "<s>$1</s>")
|
|
1603
|
+
.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1<em>$2</em>")
|
|
1604
|
+
.replace(/(^|[^_])_([^_\n]+)_/g, "$1<em>$2</em>");
|
|
1605
|
+
codeTokens.forEach((replacement, index) => {
|
|
1606
|
+
value = value.replace(`@@SRTE_CODE_${index}@@`, replacement);
|
|
1607
|
+
});
|
|
1608
|
+
return value;
|
|
1609
|
+
};
|
|
1610
|
+
const closeParagraph = () => {
|
|
1611
|
+
if (!paragraph.length)
|
|
1612
|
+
return;
|
|
1613
|
+
html += `<p>${inline(paragraph.join(" "))}</p>`;
|
|
1614
|
+
paragraph = [];
|
|
1615
|
+
};
|
|
1616
|
+
const isTableSeparator = (line) => /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(line);
|
|
1617
|
+
const parseTableRow = (line) => {
|
|
1618
|
+
let value = line.trim();
|
|
1619
|
+
if (value.startsWith("|"))
|
|
1620
|
+
value = value.slice(1);
|
|
1621
|
+
if (value.endsWith("|"))
|
|
1622
|
+
value = value.slice(0, -1);
|
|
1623
|
+
return value.split("|").map((cell) => cell.trim());
|
|
1624
|
+
};
|
|
1625
|
+
const renderTable = (startIndex) => {
|
|
1626
|
+
const header = parseTableRow(lines[startIndex]);
|
|
1627
|
+
let index = startIndex + 2;
|
|
1628
|
+
const rows = [];
|
|
1629
|
+
while (index < lines.length && lines[index].includes("|") && lines[index].trim()) {
|
|
1630
|
+
rows.push(parseTableRow(lines[index]));
|
|
1631
|
+
index += 1;
|
|
1632
|
+
}
|
|
1633
|
+
const headHtml = `<thead><tr>${header.map((cell) => `<th>${inline(cell)}</th>`).join("")}</tr></thead>`;
|
|
1634
|
+
const bodyHtml = rows.length
|
|
1635
|
+
? `<tbody>${rows.map((row) => `<tr>${header.map((_cell, cellIndex) => `<td>${inline(row[cellIndex] || "")}</td>`).join("")}</tr>`).join("")}</tbody>`
|
|
1636
|
+
: "";
|
|
1637
|
+
html += `<table style="border-collapse: collapse; width: 100%; margin: 12px 0;">${headHtml}${bodyHtml}</table>`;
|
|
1638
|
+
return index;
|
|
1639
|
+
};
|
|
1640
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
1641
|
+
const line = lines[i];
|
|
1164
1642
|
const trimmed = line.trim();
|
|
1643
|
+
const fence = /^```([A-Za-z0-9_-]+)?\s*$/.exec(trimmed);
|
|
1644
|
+
if (fence) {
|
|
1645
|
+
closeParagraph();
|
|
1646
|
+
closeList();
|
|
1647
|
+
if (codeFence) {
|
|
1648
|
+
const langClass = codeFence.lang ? ` class="language-${escapeHtmlAttribute(codeFence.lang)}"` : "";
|
|
1649
|
+
html += `<pre><code${langClass}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`;
|
|
1650
|
+
codeFence = null;
|
|
1651
|
+
}
|
|
1652
|
+
else {
|
|
1653
|
+
codeFence = { lang: fence[1] || "", lines: [] };
|
|
1654
|
+
}
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
if (codeFence) {
|
|
1658
|
+
codeFence.lines.push(line);
|
|
1659
|
+
continue;
|
|
1660
|
+
}
|
|
1165
1661
|
if (!trimmed) {
|
|
1662
|
+
closeParagraph();
|
|
1166
1663
|
closeList();
|
|
1167
|
-
|
|
1664
|
+
continue;
|
|
1665
|
+
}
|
|
1666
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(trimmed)) {
|
|
1667
|
+
closeParagraph();
|
|
1668
|
+
closeList();
|
|
1669
|
+
html += "<hr>";
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
if (i + 1 < lines.length && trimmed.includes("|") && isTableSeparator(lines[i + 1])) {
|
|
1673
|
+
closeParagraph();
|
|
1674
|
+
closeList();
|
|
1675
|
+
i = renderTable(i) - 1;
|
|
1676
|
+
continue;
|
|
1168
1677
|
}
|
|
1169
1678
|
const heading = /^(#{1,6})\s+(.+)$/.exec(trimmed);
|
|
1170
1679
|
if (heading) {
|
|
1680
|
+
closeParagraph();
|
|
1171
1681
|
closeList();
|
|
1172
1682
|
const level = heading[1].length;
|
|
1173
1683
|
html += `<h${level}>${inline(heading[2])}</h${level}>`;
|
|
1174
|
-
|
|
1684
|
+
continue;
|
|
1175
1685
|
}
|
|
1176
|
-
const bullet = /^[
|
|
1686
|
+
const bullet = /^[-*+]\s+(.+)$/.exec(trimmed);
|
|
1177
1687
|
if (bullet) {
|
|
1688
|
+
closeParagraph();
|
|
1178
1689
|
if (listType !== "ul") {
|
|
1179
1690
|
closeList();
|
|
1180
1691
|
html += "<ul>";
|
|
1181
1692
|
listType = "ul";
|
|
1182
1693
|
}
|
|
1183
1694
|
html += `<li>${inline(bullet[1])}</li>`;
|
|
1184
|
-
|
|
1695
|
+
continue;
|
|
1185
1696
|
}
|
|
1186
1697
|
const numbered = /^\d+[.)]\s+(.+)$/.exec(trimmed);
|
|
1187
1698
|
if (numbered) {
|
|
1699
|
+
closeParagraph();
|
|
1188
1700
|
if (listType !== "ol") {
|
|
1189
1701
|
closeList();
|
|
1190
1702
|
html += "<ol>";
|
|
1191
1703
|
listType = "ol";
|
|
1192
1704
|
}
|
|
1193
1705
|
html += `<li>${inline(numbered[1])}</li>`;
|
|
1194
|
-
|
|
1706
|
+
continue;
|
|
1195
1707
|
}
|
|
1196
|
-
|
|
1708
|
+
const quote = /^>\s?(.*)$/.exec(trimmed);
|
|
1709
|
+
if (quote) {
|
|
1710
|
+
closeParagraph();
|
|
1197
1711
|
closeList();
|
|
1198
|
-
html += `<blockquote>${inline(
|
|
1199
|
-
|
|
1712
|
+
html += `<blockquote>${inline(quote[1]) || "<br>"}</blockquote>`;
|
|
1713
|
+
continue;
|
|
1200
1714
|
}
|
|
1201
1715
|
closeList();
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1716
|
+
paragraph.push(trimmed);
|
|
1717
|
+
}
|
|
1718
|
+
if (codeFence) {
|
|
1719
|
+
const langClass = codeFence.lang ? ` class="language-${escapeHtmlAttribute(codeFence.lang)}"` : "";
|
|
1720
|
+
html += `<pre><code${langClass}>${escapeHtml(codeFence.lines.join("\n"))}</code></pre>`;
|
|
1721
|
+
}
|
|
1722
|
+
closeParagraph();
|
|
1204
1723
|
closeList();
|
|
1205
|
-
|
|
1724
|
+
const root = document.createElement("div");
|
|
1725
|
+
root.innerHTML = html;
|
|
1726
|
+
enhanceImportedTables(root);
|
|
1727
|
+
return root.innerHTML;
|
|
1206
1728
|
};
|
|
1207
1729
|
const htmlToMarkdown = (html) => {
|
|
1208
1730
|
const root = document.createElement("div");
|
|
@@ -1250,7 +1772,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1250
1772
|
const html = type === "html" ? text : markdownToHtml(text);
|
|
1251
1773
|
const el = editableRef.current;
|
|
1252
1774
|
const hasContent = el && el.textContent && el.textContent.trim().length > 0;
|
|
1253
|
-
insertImportedHtml(html, hasContent ? "append" : "replace"
|
|
1775
|
+
insertImportedHtml(html, hasContent ? "append" : "replace", {
|
|
1776
|
+
preserveColors: true,
|
|
1777
|
+
preserveDocumentLayout: true,
|
|
1778
|
+
});
|
|
1254
1779
|
};
|
|
1255
1780
|
const downloadText = (filename, content, mimeType) => {
|
|
1256
1781
|
const blob = new Blob([content], { type: mimeType });
|
|
@@ -1271,6 +1796,139 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1271
1796
|
const html = editableRef.current?.innerHTML || "";
|
|
1272
1797
|
downloadText("smart-rte-export.md", htmlToMarkdown(html), "text/markdown");
|
|
1273
1798
|
};
|
|
1799
|
+
const htmlToDocxXml = (html) => {
|
|
1800
|
+
const root = document.createElement("div");
|
|
1801
|
+
root.innerHTML = html;
|
|
1802
|
+
const xmlEscape = (value) => value
|
|
1803
|
+
.replace(/&/g, "&")
|
|
1804
|
+
.replace(/</g, "<")
|
|
1805
|
+
.replace(/>/g, ">")
|
|
1806
|
+
.replace(/"/g, """);
|
|
1807
|
+
const colorValue = (value) => {
|
|
1808
|
+
const hex = /^#([0-9a-f]{6})$/i.exec(value.trim());
|
|
1809
|
+
if (hex)
|
|
1810
|
+
return hex[1].toUpperCase();
|
|
1811
|
+
const rgb = /^rgb\(\s*(\d+),\s*(\d+),\s*(\d+)\s*\)$/i.exec(value.trim());
|
|
1812
|
+
if (!rgb)
|
|
1813
|
+
return "";
|
|
1814
|
+
return [rgb[1], rgb[2], rgb[3]]
|
|
1815
|
+
.map((part) => Math.max(0, Math.min(255, Number(part))).toString(16).padStart(2, "0"))
|
|
1816
|
+
.join("")
|
|
1817
|
+
.toUpperCase();
|
|
1818
|
+
};
|
|
1819
|
+
const sizeToHalfPoints = (value) => {
|
|
1820
|
+
const trimmed = value.trim();
|
|
1821
|
+
const match = /^([\d.]+)(px|pt)$/i.exec(trimmed);
|
|
1822
|
+
if (!match)
|
|
1823
|
+
return "";
|
|
1824
|
+
const raw = Number(match[1]);
|
|
1825
|
+
const pt = match[2].toLowerCase() === "px" ? raw * 0.75 : raw;
|
|
1826
|
+
return String(Math.max(2, Math.round(pt * 2)));
|
|
1827
|
+
};
|
|
1828
|
+
const runProperties = (el) => {
|
|
1829
|
+
const style = el.style;
|
|
1830
|
+
const color = colorValue(style.color);
|
|
1831
|
+
const size = sizeToHalfPoints(style.fontSize);
|
|
1832
|
+
const isBold = el.tagName === "B" || el.tagName === "STRONG" || /bold|700|800|900/.test(style.fontWeight);
|
|
1833
|
+
const isItalic = el.tagName === "I" || el.tagName === "EM" || style.fontStyle === "italic";
|
|
1834
|
+
const isUnderline = el.tagName === "U" || style.textDecoration.includes("underline");
|
|
1835
|
+
return [
|
|
1836
|
+
isBold ? "<w:b/>" : "",
|
|
1837
|
+
isItalic ? "<w:i/>" : "",
|
|
1838
|
+
isUnderline ? '<w:u w:val="single"/>' : "",
|
|
1839
|
+
color ? `<w:color w:val="${color}"/>` : "",
|
|
1840
|
+
size ? `<w:sz w:val="${size}"/>` : "",
|
|
1841
|
+
].join("");
|
|
1842
|
+
};
|
|
1843
|
+
const runs = (node, inheritedProps = "") => {
|
|
1844
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1845
|
+
const text = node.textContent || "";
|
|
1846
|
+
return text ? `<w:r>${inheritedProps ? `<w:rPr>${inheritedProps}</w:rPr>` : ""}<w:t xml:space="preserve">${xmlEscape(text)}</w:t></w:r>` : "";
|
|
1847
|
+
}
|
|
1848
|
+
if (!(node instanceof HTMLElement))
|
|
1849
|
+
return "";
|
|
1850
|
+
if (node.tagName === "BR")
|
|
1851
|
+
return "<w:r><w:br/></w:r>";
|
|
1852
|
+
if (node.tagName === "IMG") {
|
|
1853
|
+
const alt = node.getAttribute("alt") || node.getAttribute("title") || "Image";
|
|
1854
|
+
return `<w:r><w:t>[Image: ${xmlEscape(alt)}]</w:t></w:r>`;
|
|
1855
|
+
}
|
|
1856
|
+
const props = `${inheritedProps}${runProperties(node)}`;
|
|
1857
|
+
return Array.from(node.childNodes).map((child) => runs(child, props)).join("");
|
|
1858
|
+
};
|
|
1859
|
+
const paragraph = (el, fallbackTag = "p") => {
|
|
1860
|
+
const tag = el.tagName.toLowerCase();
|
|
1861
|
+
const headingMatch = /^h([1-6])$/.exec(tag);
|
|
1862
|
+
const style = el.style;
|
|
1863
|
+
const align = style.textAlign ? `<w:jc w:val="${xmlEscape(style.textAlign)}"/>` : "";
|
|
1864
|
+
const headingSize = headingMatch ? `<w:rPr><w:b/><w:sz w:val="${Math.max(24, 40 - Number(headingMatch[1]) * 4)}"/></w:rPr>` : "";
|
|
1865
|
+
const body = runs(el);
|
|
1866
|
+
return `<w:p><w:pPr>${align}${headingSize}</w:pPr>${body || "<w:r><w:t></w:t></w:r>"}</w:p>`;
|
|
1867
|
+
};
|
|
1868
|
+
const tableCell = (cell) => {
|
|
1869
|
+
const fill = colorValue(cell.style.backgroundColor);
|
|
1870
|
+
const shading = fill ? `<w:shd w:val="clear" w:color="auto" w:fill="${fill}"/>` : "";
|
|
1871
|
+
const cellContent = Array.from(cell.childNodes)
|
|
1872
|
+
.map((child) => child instanceof HTMLElement && ["P", "DIV", "H1", "H2", "H3", "H4", "H5", "H6"].includes(child.tagName)
|
|
1873
|
+
? paragraph(child)
|
|
1874
|
+
: `<w:p>${runs(child)}</w:p>`)
|
|
1875
|
+
.join("");
|
|
1876
|
+
return `<w:tc><w:tcPr>${shading}<w:tcBorders><w:top w:val="single" w:sz="4" w:color="D1D5DB"/><w:left w:val="single" w:sz="4" w:color="D1D5DB"/><w:bottom w:val="single" w:sz="4" w:color="D1D5DB"/><w:right w:val="single" w:sz="4" w:color="D1D5DB"/></w:tcBorders></w:tcPr>${cellContent || "<w:p/>"}</w:tc>`;
|
|
1877
|
+
};
|
|
1878
|
+
const tableXml = (table) => {
|
|
1879
|
+
const rows = Array.from(table.querySelectorAll("tr"));
|
|
1880
|
+
return `<w:tbl><w:tblPr><w:tblW w:w="0" w:type="auto"/><w:tblBorders><w:top w:val="single" w:sz="4" w:color="D1D5DB"/><w:left w:val="single" w:sz="4" w:color="D1D5DB"/><w:bottom w:val="single" w:sz="4" w:color="D1D5DB"/><w:right w:val="single" w:sz="4" w:color="D1D5DB"/><w:insideH w:val="single" w:sz="4" w:color="D1D5DB"/><w:insideV w:val="single" w:sz="4" w:color="D1D5DB"/></w:tblBorders></w:tblPr>${rows.map((row) => `<w:tr>${Array.from(row.children).map((cell) => tableCell(cell)).join("")}</w:tr>`).join("")}</w:tbl>`;
|
|
1881
|
+
};
|
|
1882
|
+
const blockXml = (node) => {
|
|
1883
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
1884
|
+
const text = node.textContent?.trim();
|
|
1885
|
+
return text ? `<w:p>${runs(node)}</w:p>` : "";
|
|
1886
|
+
}
|
|
1887
|
+
if (!(node instanceof HTMLElement))
|
|
1888
|
+
return "";
|
|
1889
|
+
if (node.tagName === "TABLE")
|
|
1890
|
+
return tableXml(node);
|
|
1891
|
+
if (node.tagName === "UL" || node.tagName === "OL") {
|
|
1892
|
+
return Array.from(node.children).map((li) => `<w:p><w:r><w:t>• </w:t></w:r>${runs(li)}</w:p>`).join("");
|
|
1893
|
+
}
|
|
1894
|
+
if (node.tagName === "BLOCKQUOTE") {
|
|
1895
|
+
return `<w:p><w:pPr><w:ind w:left="720"/></w:pPr>${runs(node)}</w:p>`;
|
|
1896
|
+
}
|
|
1897
|
+
if (node.tagName === "HR")
|
|
1898
|
+
return '<w:p><w:pPr><w:pBdr><w:bottom w:val="single" w:sz="6" w:color="D1D5DB"/></w:pBdr></w:pPr></w:p>';
|
|
1899
|
+
if (["P", "DIV", "PRE", "H1", "H2", "H3", "H4", "H5", "H6"].includes(node.tagName))
|
|
1900
|
+
return paragraph(node);
|
|
1901
|
+
return Array.from(node.childNodes).map(blockXml).join("");
|
|
1902
|
+
};
|
|
1903
|
+
const body = Array.from(root.childNodes).map(blockXml).join("");
|
|
1904
|
+
return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body>${body}<w:sectPr><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:top="720" w:right="720" w:bottom="720" w:left="720"/></w:sectPr></w:body></w:document>`;
|
|
1905
|
+
};
|
|
1906
|
+
const exportDocx = async () => {
|
|
1907
|
+
const html = editableRef.current?.innerHTML || "";
|
|
1908
|
+
const zip = new JSZip();
|
|
1909
|
+
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>`);
|
|
1910
|
+
zip.folder("_rels")?.file(".rels", `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>`);
|
|
1911
|
+
zip.folder("word")?.file("document.xml", htmlToDocxXml(html));
|
|
1912
|
+
zip.folder("word")?.folder("_rels")?.file("document.xml.rels", `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>`);
|
|
1913
|
+
const blob = await zip.generateAsync({ type: "blob", mimeType: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" });
|
|
1914
|
+
const url = URL.createObjectURL(blob);
|
|
1915
|
+
const link = document.createElement("a");
|
|
1916
|
+
link.href = url;
|
|
1917
|
+
link.download = "smart-rte-export.docx";
|
|
1918
|
+
document.body.appendChild(link);
|
|
1919
|
+
link.click();
|
|
1920
|
+
link.remove();
|
|
1921
|
+
URL.revokeObjectURL(url);
|
|
1922
|
+
};
|
|
1923
|
+
const exportPdf = () => {
|
|
1924
|
+
const html = editableRef.current?.innerHTML || "";
|
|
1925
|
+
const printWindow = window.open("", "_blank", "width=900,height=700");
|
|
1926
|
+
if (!printWindow)
|
|
1927
|
+
return;
|
|
1928
|
+
printWindow.document.open();
|
|
1929
|
+
printWindow.document.write(`<!doctype html><html><head><title>Export PDF</title><style>@page{margin:18mm}html,body{background:#fff}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.6;padding:32px;color:#111}table{border-collapse:collapse;width:100%;margin:12px 0;break-inside:auto}tr,img,blockquote,pre{break-inside:avoid}td,th{border:1px solid #d1d5db;padding:8px;vertical-align:top}img{max-width:100%;height:auto}blockquote{border-left:4px solid #d1d5db;padding-left:12px;color:#374151}pre,code{background:#f3f4f6}pre{padding:12px;white-space:pre-wrap}@media print{body{padding:0}}</style></head><body>${html || "<p></p>"}<script>window.addEventListener("load",function(){setTimeout(function(){window.focus();window.print();},150);});</script></body></html>`);
|
|
1930
|
+
printWindow.document.close();
|
|
1931
|
+
};
|
|
1274
1932
|
const fixNegativeMargins = (root) => {
|
|
1275
1933
|
try {
|
|
1276
1934
|
const nodes = root.querySelectorAll('*');
|
|
@@ -1438,10 +2096,12 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1438
2096
|
const wrapper = document.createElement('div');
|
|
1439
2097
|
wrapper.setAttribute('data-table-wrapper', 'true');
|
|
1440
2098
|
wrapper.style.overflowX = 'auto';
|
|
2099
|
+
wrapper.style.overflowY = 'visible';
|
|
1441
2100
|
wrapper.style.webkitOverflowScrolling = 'touch';
|
|
1442
2101
|
wrapper.style.width = '100%';
|
|
1443
2102
|
wrapper.style.maxWidth = '100%';
|
|
1444
2103
|
wrapper.style.display = 'block';
|
|
2104
|
+
wrapper.style.paddingBottom = '8px';
|
|
1445
2105
|
// Use insertBefore + appendChild to move element without losing too much state
|
|
1446
2106
|
// simpler than replaceChild for wrapping
|
|
1447
2107
|
parent.insertBefore(wrapper, table);
|
|
@@ -1489,7 +2149,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1489
2149
|
html += "<tr>";
|
|
1490
2150
|
for (let c = 0; c < safeCols; c++) {
|
|
1491
2151
|
html +=
|
|
1492
|
-
'<td style="border:1px solid
|
|
2152
|
+
'<td style="border:1px solid #d1d5db;padding:6px;min-width:60px;"> </td>';
|
|
1493
2153
|
}
|
|
1494
2154
|
html += "</tr>";
|
|
1495
2155
|
}
|
|
@@ -1572,16 +2232,56 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1572
2232
|
}
|
|
1573
2233
|
catch { }
|
|
1574
2234
|
};
|
|
2235
|
+
const getTableGrid = (tbody) => {
|
|
2236
|
+
const rows = Array.from(tbody.querySelectorAll("tr"));
|
|
2237
|
+
const grid = [];
|
|
2238
|
+
rows.forEach((row, rIdx) => {
|
|
2239
|
+
grid[rIdx] = grid[rIdx] || [];
|
|
2240
|
+
let cIdx = 0;
|
|
2241
|
+
const cells = cellsOfRow(row);
|
|
2242
|
+
cells.forEach((cell) => {
|
|
2243
|
+
while (grid[rIdx][cIdx])
|
|
2244
|
+
cIdx += 1;
|
|
2245
|
+
const rowSpan = Math.max(1, cell.rowSpan || 1);
|
|
2246
|
+
const colSpan = Math.max(1, cell.colSpan || 1);
|
|
2247
|
+
for (let r = rIdx; r < rIdx + rowSpan; r += 1) {
|
|
2248
|
+
grid[r] = grid[r] || [];
|
|
2249
|
+
for (let c = cIdx; c < cIdx + colSpan; c += 1) {
|
|
2250
|
+
grid[r][c] = cell;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
cIdx += colSpan;
|
|
2254
|
+
});
|
|
2255
|
+
});
|
|
2256
|
+
return { rows, grid };
|
|
2257
|
+
};
|
|
2258
|
+
const getCellsInGridRect = (tbody, sr, sc, er, ec) => {
|
|
2259
|
+
const { grid } = getTableGrid(tbody);
|
|
2260
|
+
const seen = new Set();
|
|
2261
|
+
const cells = [];
|
|
2262
|
+
for (let r = sr; r <= er; r += 1) {
|
|
2263
|
+
for (let c = sc; c <= ec; c += 1) {
|
|
2264
|
+
const cell = grid[r]?.[c];
|
|
2265
|
+
if (cell && !seen.has(cell)) {
|
|
2266
|
+
seen.add(cell);
|
|
2267
|
+
cells.push(cell);
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
return cells;
|
|
2272
|
+
};
|
|
1575
2273
|
const getCellPosition = (cell) => {
|
|
1576
2274
|
const row = cell.parentElement;
|
|
1577
2275
|
const tbody = row?.parentElement;
|
|
1578
2276
|
const table = tbody?.parentElement;
|
|
1579
2277
|
if (!row || !tbody || !table)
|
|
1580
2278
|
return null;
|
|
1581
|
-
const rows =
|
|
2279
|
+
const { rows, grid } = getTableGrid(tbody);
|
|
1582
2280
|
const rIdx = rows.indexOf(row);
|
|
1583
|
-
|
|
1584
|
-
|
|
2281
|
+
let cIdx = -1;
|
|
2282
|
+
if (rIdx >= 0) {
|
|
2283
|
+
cIdx = (grid[rIdx] || []).findIndex((candidate) => candidate === cell);
|
|
2284
|
+
}
|
|
1585
2285
|
return { row, tbody, table, rIdx, cIdx };
|
|
1586
2286
|
};
|
|
1587
2287
|
const cellsOfRow = (row) => Array.from(row.children).filter((c) => ["TD", "TH"].includes(c.tagName));
|
|
@@ -1590,42 +2290,27 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1590
2290
|
if (!sel)
|
|
1591
2291
|
return;
|
|
1592
2292
|
const { tbody, sr, sc, er, ec } = sel;
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
const cell = cells[c];
|
|
1599
|
-
if (!cell)
|
|
1600
|
-
continue;
|
|
1601
|
-
if (cell.__rtePrevBg != null) {
|
|
1602
|
-
cell.style.background = cell.__rtePrevBg;
|
|
1603
|
-
delete cell.__rtePrevBg;
|
|
1604
|
-
}
|
|
1605
|
-
cell.style.outline = "";
|
|
1606
|
-
cell.style.outlineOffset = "";
|
|
2293
|
+
const cells = getCellsInGridRect(tbody, sr, sc, er, ec);
|
|
2294
|
+
cells.forEach((cell) => {
|
|
2295
|
+
if (cell.__rtePrevBg != null) {
|
|
2296
|
+
cell.style.background = cell.__rtePrevBg;
|
|
2297
|
+
delete cell.__rtePrevBg;
|
|
1607
2298
|
}
|
|
1608
|
-
|
|
2299
|
+
cell.style.outline = "";
|
|
2300
|
+
cell.style.outlineOffset = "";
|
|
2301
|
+
});
|
|
1609
2302
|
selectionRef.current = null;
|
|
1610
2303
|
};
|
|
1611
2304
|
const updateSelectionDecor = (tbody, sr, sc, er, ec) => {
|
|
1612
2305
|
clearSelectionDecor();
|
|
1613
2306
|
selectionRef.current = { tbody, sr, sc, er, ec };
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
continue;
|
|
1622
|
-
cell.__rtePrevBg =
|
|
1623
|
-
cell.style.background || "";
|
|
1624
|
-
cell.style.background = "var(--srte-accent-bg)";
|
|
1625
|
-
cell.style.outline = "2px solid var(--srte-accent)";
|
|
1626
|
-
cell.style.outlineOffset = "-2px";
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
2307
|
+
const cells = getCellsInGridRect(tbody, sr, sc, er, ec);
|
|
2308
|
+
cells.forEach((cell) => {
|
|
2309
|
+
cell.__rtePrevBg = cell.style.background || "";
|
|
2310
|
+
cell.style.background = "var(--srte-accent-bg)";
|
|
2311
|
+
cell.style.outline = "2px solid var(--srte-accent)";
|
|
2312
|
+
cell.style.outlineOffset = "-2px";
|
|
2313
|
+
});
|
|
1629
2314
|
};
|
|
1630
2315
|
const canMergeSelection = () => {
|
|
1631
2316
|
const sel = selectionRef.current;
|
|
@@ -1633,32 +2318,37 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1633
2318
|
return false;
|
|
1634
2319
|
return sel.sr !== sel.er || sel.sc !== sel.ec;
|
|
1635
2320
|
};
|
|
2321
|
+
const canMergeFromCell = (cell) => Boolean(cell && canMergeSelection() && isCellInsideSelection(cell));
|
|
2322
|
+
const canSplitCell = (cell) => Boolean(cell && (Math.max(1, cell.rowSpan || 1) > 1 || Math.max(1, cell.colSpan || 1) > 1));
|
|
2323
|
+
const isCellInsideSelection = (cell) => {
|
|
2324
|
+
const sel = selectionRef.current;
|
|
2325
|
+
if (!sel)
|
|
2326
|
+
return false;
|
|
2327
|
+
const pos = getCellPosition(cell);
|
|
2328
|
+
if (!pos || pos.tbody !== sel.tbody)
|
|
2329
|
+
return false;
|
|
2330
|
+
return pos.rIdx >= sel.sr && pos.rIdx <= sel.er && pos.cIdx >= sel.sc && pos.cIdx <= sel.ec;
|
|
2331
|
+
};
|
|
2332
|
+
const shouldUseTableSelection = (fallbackCell) => Boolean(selectionRef.current && fallbackCell && isCellInsideSelection(fallbackCell));
|
|
1636
2333
|
const mergeSelection = () => {
|
|
1637
2334
|
const sel = selectionRef.current;
|
|
1638
2335
|
if (!sel)
|
|
1639
2336
|
return;
|
|
1640
2337
|
const { tbody, sr, sc, er, ec } = sel;
|
|
1641
|
-
const
|
|
1642
|
-
const
|
|
1643
|
-
const anchor = cellsOfRow(anchorRow)[sc];
|
|
2338
|
+
const { grid } = getTableGrid(tbody);
|
|
2339
|
+
const anchor = grid[sr]?.[sc];
|
|
1644
2340
|
if (!anchor)
|
|
1645
2341
|
return;
|
|
1646
2342
|
// Collect content and remove other cells
|
|
1647
2343
|
const contents = [];
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
continue;
|
|
1657
|
-
const html = cell.innerHTML.trim();
|
|
1658
|
-
if (html)
|
|
1659
|
-
contents.push(html);
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
2344
|
+
const cellsToMerge = getCellsInGridRect(tbody, sr, sc, er, ec);
|
|
2345
|
+
cellsToMerge.forEach((cell) => {
|
|
2346
|
+
if (cell === anchor)
|
|
2347
|
+
return;
|
|
2348
|
+
const html = cell.innerHTML.trim();
|
|
2349
|
+
if (html)
|
|
2350
|
+
contents.push(html);
|
|
2351
|
+
});
|
|
1662
2352
|
if (contents.length) {
|
|
1663
2353
|
anchor.innerHTML = (anchor.innerHTML || "") + " " + contents.join(" ");
|
|
1664
2354
|
}
|
|
@@ -1666,18 +2356,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1666
2356
|
anchor.colSpan = ec - sc + 1;
|
|
1667
2357
|
anchor.rowSpan = er - sr + 1;
|
|
1668
2358
|
// Remove other cells
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
const cells = cellsOfRow(row);
|
|
1672
|
-
for (let c = ec; c >= sc; c--) {
|
|
1673
|
-
const cell = cells[c];
|
|
1674
|
-
if (!cell)
|
|
1675
|
-
continue;
|
|
1676
|
-
if (r === sr && c === sc)
|
|
1677
|
-
continue;
|
|
2359
|
+
cellsToMerge.forEach((cell) => {
|
|
2360
|
+
if (cell !== anchor)
|
|
1678
2361
|
cell.remove();
|
|
1679
|
-
|
|
1680
|
-
}
|
|
2362
|
+
});
|
|
1681
2363
|
moveCaretToCell(anchor, false);
|
|
1682
2364
|
clearSelectionDecor();
|
|
1683
2365
|
handleInput();
|
|
@@ -1691,7 +2373,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1691
2373
|
const numCols = Array.from(row.children).filter((c) => ["TD", "TH"].includes(c.tagName)).length;
|
|
1692
2374
|
for (let i = 0; i < numCols; i++) {
|
|
1693
2375
|
const td = document.createElement("td");
|
|
1694
|
-
td.style.border = "1px solid
|
|
2376
|
+
td.style.border = "1px solid #d1d5db";
|
|
1695
2377
|
td.style.padding = "6px";
|
|
1696
2378
|
td.style.minWidth = "60px";
|
|
1697
2379
|
td.innerHTML = " ";
|
|
@@ -1721,7 +2403,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1721
2403
|
for (const r of rows) {
|
|
1722
2404
|
const cells = Array.from(r.children).filter((c) => ["TD", "TH"].includes(c.tagName));
|
|
1723
2405
|
const td = document.createElement("td");
|
|
1724
|
-
td.style.border = "1px solid
|
|
2406
|
+
td.style.border = "1px solid #d1d5db";
|
|
1725
2407
|
td.style.padding = "6px";
|
|
1726
2408
|
td.style.minWidth = "60px";
|
|
1727
2409
|
td.innerHTML = " ";
|
|
@@ -1750,14 +2432,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1750
2432
|
table.parentElement?.removeChild(table);
|
|
1751
2433
|
};
|
|
1752
2434
|
const toggleHeaderCell = (cell) => {
|
|
2435
|
+
clearSelectionDecor();
|
|
1753
2436
|
const isTh = cell.tagName === "TH";
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
replacement.style.border =
|
|
1757
|
-
cell.style.border || "1px solid var(--srte-border)";
|
|
1758
|
-
replacement.style.padding = cell.style.padding || "6px";
|
|
1759
|
-
replacement.style.minWidth = cell.style.minWidth || "60px";
|
|
1760
|
-
cell.parentElement?.replaceChild(replacement, cell);
|
|
2437
|
+
replaceTableCellTag(cell, isTh ? "td" : "th");
|
|
2438
|
+
handleInput();
|
|
1761
2439
|
};
|
|
1762
2440
|
const deleteTable = (cell) => {
|
|
1763
2441
|
const pos = getCellPosition(cell);
|
|
@@ -1782,7 +2460,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1782
2460
|
const currentRow = Array.from(tbody.querySelectorAll("tr"))[rIdx];
|
|
1783
2461
|
for (let j = 1; j < cs; j++) {
|
|
1784
2462
|
const td = document.createElement("td");
|
|
1785
|
-
td.style.border = "1px solid
|
|
2463
|
+
td.style.border = "1px solid #d1d5db";
|
|
1786
2464
|
td.style.padding = "6px";
|
|
1787
2465
|
td.style.minWidth = "60px";
|
|
1788
2466
|
td.innerHTML = " ";
|
|
@@ -1795,7 +2473,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1795
2473
|
const row = Array.from(tbody.querySelectorAll("tr"))[rIdx + i];
|
|
1796
2474
|
for (let j = 0; j < cs; j++) {
|
|
1797
2475
|
const td = document.createElement("td");
|
|
1798
|
-
td.style.border = "1px solid
|
|
2476
|
+
td.style.border = "1px solid #d1d5db";
|
|
1799
2477
|
td.style.padding = "6px";
|
|
1800
2478
|
td.style.minWidth = "60px";
|
|
1801
2479
|
td.innerHTML = " ";
|
|
@@ -1810,29 +2488,17 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1810
2488
|
const pos = getCellPosition(cell);
|
|
1811
2489
|
if (!pos)
|
|
1812
2490
|
return;
|
|
1813
|
-
|
|
1814
|
-
const
|
|
1815
|
-
|
|
1816
|
-
return;
|
|
1817
|
-
const cells = cellsOfRow(firstRow);
|
|
2491
|
+
clearSelectionDecor();
|
|
2492
|
+
const { row } = pos;
|
|
2493
|
+
const cells = cellsOfRow(row);
|
|
1818
2494
|
const shouldMakeHeader = cells.some((c) => c.tagName !== "TH");
|
|
1819
2495
|
for (const c of cells) {
|
|
1820
2496
|
const isTh = c.tagName === "TH";
|
|
1821
2497
|
if (shouldMakeHeader && !isTh) {
|
|
1822
|
-
|
|
1823
|
-
th.innerHTML = c.innerHTML || " ";
|
|
1824
|
-
th.style.border = c.style.border || "1px solid var(--srte-border)";
|
|
1825
|
-
th.style.padding = c.style.padding || "6px";
|
|
1826
|
-
th.style.minWidth = c.style.minWidth || "60px";
|
|
1827
|
-
firstRow.replaceChild(th, c);
|
|
2498
|
+
replaceTableCellTag(c, "th");
|
|
1828
2499
|
}
|
|
1829
2500
|
else if (!shouldMakeHeader && isTh) {
|
|
1830
|
-
|
|
1831
|
-
td.innerHTML = c.innerHTML || " ";
|
|
1832
|
-
td.style.border = c.style.border || "1px solid var(--srte-border)";
|
|
1833
|
-
td.style.padding = c.style.padding || "6px";
|
|
1834
|
-
td.style.minWidth = c.style.minWidth || "60px";
|
|
1835
|
-
firstRow.replaceChild(td, c);
|
|
2501
|
+
replaceTableCellTag(c, "td");
|
|
1836
2502
|
}
|
|
1837
2503
|
}
|
|
1838
2504
|
handleInput();
|
|
@@ -1841,35 +2507,30 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1841
2507
|
const pos = getCellPosition(cell);
|
|
1842
2508
|
if (!pos)
|
|
1843
2509
|
return;
|
|
2510
|
+
clearSelectionDecor();
|
|
1844
2511
|
const { tbody, cIdx } = pos;
|
|
1845
|
-
const
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1848
|
-
.
|
|
2512
|
+
const { grid } = getTableGrid(tbody);
|
|
2513
|
+
const seen = new Set();
|
|
2514
|
+
const columnCells = grid
|
|
2515
|
+
.map((row) => row?.[cIdx])
|
|
2516
|
+
.filter((candidate) => {
|
|
2517
|
+
if (!candidate || seen.has(candidate))
|
|
2518
|
+
return false;
|
|
2519
|
+
seen.add(candidate);
|
|
2520
|
+
return true;
|
|
2521
|
+
});
|
|
1849
2522
|
const shouldMakeHeader = columnCells.some((c) => c.tagName !== "TH");
|
|
1850
2523
|
for (const c of columnCells) {
|
|
1851
|
-
|
|
1852
|
-
replacement.innerHTML = c.innerHTML || " ";
|
|
1853
|
-
replacement.style.border = c.style.border || "1px solid var(--srte-border)";
|
|
1854
|
-
replacement.style.padding = c.style.padding || "6px";
|
|
1855
|
-
replacement.style.minWidth = c.style.minWidth || "60px";
|
|
1856
|
-
c.parentElement?.replaceChild(replacement, c);
|
|
2524
|
+
replaceTableCellTag(c, shouldMakeHeader ? "th" : "td");
|
|
1857
2525
|
}
|
|
1858
2526
|
handleInput();
|
|
1859
2527
|
};
|
|
1860
2528
|
const applyBgToSelection = (hex, fallbackCell) => {
|
|
1861
|
-
const sel = selectionRef.current;
|
|
2529
|
+
const sel = shouldUseTableSelection(fallbackCell) ? selectionRef.current : null;
|
|
1862
2530
|
if (sel) {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
const cells = cellsOfRow(row);
|
|
1867
|
-
for (let c = sel.sc; c <= sel.ec; c++) {
|
|
1868
|
-
const cell = cells[c];
|
|
1869
|
-
if (cell)
|
|
1870
|
-
cell.style.background = hex;
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
2531
|
+
getCellsInGridRect(sel.tbody, sel.sr, sel.sc, sel.er, sel.ec).forEach((cell) => {
|
|
2532
|
+
cell.style.background = hex;
|
|
2533
|
+
});
|
|
1873
2534
|
}
|
|
1874
2535
|
else if (fallbackCell) {
|
|
1875
2536
|
fallbackCell.style.background = hex;
|
|
@@ -1879,25 +2540,21 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
1879
2540
|
const applyToggle = (cell) => {
|
|
1880
2541
|
const cur = cell.style.border;
|
|
1881
2542
|
cell.style.border =
|
|
1882
|
-
cur && cur !== "none" ? "none" : "1px solid #
|
|
2543
|
+
cur && cur !== "none" ? "none" : "1px solid #d1d5db";
|
|
1883
2544
|
};
|
|
1884
|
-
const sel = selectionRef.current;
|
|
2545
|
+
const sel = shouldUseTableSelection(fallbackCell) ? selectionRef.current : null;
|
|
1885
2546
|
if (sel) {
|
|
1886
|
-
|
|
1887
|
-
for (let r = sel.sr; r <= sel.er; r++) {
|
|
1888
|
-
const row = rows[r];
|
|
1889
|
-
const cells = cellsOfRow(row);
|
|
1890
|
-
for (let c = sel.sc; c <= sel.ec; c++) {
|
|
1891
|
-
const cell = cells[c];
|
|
1892
|
-
if (cell)
|
|
1893
|
-
applyToggle(cell);
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
2547
|
+
getCellsInGridRect(sel.tbody, sel.sr, sel.sc, sel.er, sel.ec).forEach(applyToggle);
|
|
1896
2548
|
}
|
|
1897
2549
|
else if (fallbackCell) {
|
|
1898
2550
|
applyToggle(fallbackCell);
|
|
1899
2551
|
}
|
|
1900
2552
|
};
|
|
2553
|
+
const runTableCellAction = (cell, action) => {
|
|
2554
|
+
action(cell);
|
|
2555
|
+
handleInput();
|
|
2556
|
+
setTableMenu(null);
|
|
2557
|
+
};
|
|
1901
2558
|
// Table column and row resizing functions
|
|
1902
2559
|
const getColumnCells = (table, colIndex) => {
|
|
1903
2560
|
const tbody = table.querySelector('tbody');
|
|
@@ -2018,19 +2675,40 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2018
2675
|
});
|
|
2019
2676
|
});
|
|
2020
2677
|
};
|
|
2678
|
+
const activeButtonStyle = (active = false, extra = {}) => ({
|
|
2679
|
+
height: 32,
|
|
2680
|
+
minWidth: 32,
|
|
2681
|
+
padding: "0 8px",
|
|
2682
|
+
border: active
|
|
2683
|
+
? "2px solid var(--srte-accent)"
|
|
2684
|
+
: "1px solid var(--srte-input-border)",
|
|
2685
|
+
borderRadius: 6,
|
|
2686
|
+
background: active ? "var(--srte-accent-bg)" : "var(--srte-input-bg)",
|
|
2687
|
+
color: "var(--srte-input-text)",
|
|
2688
|
+
boxShadow: active ? "inset 0 0 0 1px var(--srte-accent)" : "none",
|
|
2689
|
+
...extra,
|
|
2690
|
+
});
|
|
2691
|
+
const preserveToolbarMouseDown = (event) => {
|
|
2692
|
+
const target = event.target;
|
|
2693
|
+
const button = target?.closest("button");
|
|
2694
|
+
preserveEditorSelection();
|
|
2695
|
+
if (button && !button.hasAttribute("disabled")) {
|
|
2696
|
+
event.preventDefault();
|
|
2697
|
+
}
|
|
2698
|
+
};
|
|
2021
2699
|
const editorClass = `srte-editor${theme === 'dark' ? ' srte-dark' : ''}${className ? ' ' + className : ''}`;
|
|
2022
2700
|
return (_jsxs("div", { className: editorClass, style: {
|
|
2023
2701
|
border: "1px solid var(--srte-border)",
|
|
2024
2702
|
borderRadius: 6,
|
|
2025
2703
|
width: "100%",
|
|
2026
2704
|
maxWidth: "100vw",
|
|
2027
|
-
overflow: "
|
|
2705
|
+
overflow: "visible",
|
|
2028
2706
|
display: "flex",
|
|
2029
2707
|
flexDirection: "column",
|
|
2030
2708
|
background: "var(--srte-bg)",
|
|
2031
2709
|
color: "var(--srte-text)",
|
|
2032
2710
|
boxSizing: "border-box"
|
|
2033
|
-
}, children: [_jsxs("div", { style: {
|
|
2711
|
+
}, children: [_jsxs("div", { onMouseDown: preserveToolbarMouseDown, style: {
|
|
2034
2712
|
display: "flex",
|
|
2035
2713
|
flexWrap: "wrap",
|
|
2036
2714
|
maxWidth: "100%",
|
|
@@ -2079,7 +2757,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2079
2757
|
} }), _jsx("input", { ref: mdInputRef, type: "file", accept: ".md,.markdown,text/markdown,text/plain", style: { display: "none" }, onChange: (e) => {
|
|
2080
2758
|
importTextFile(e.currentTarget.files, "md");
|
|
2081
2759
|
e.currentTarget.value = "";
|
|
2082
|
-
} }), _jsxs("select", { defaultValue: "p", onChange: (e) => {
|
|
2760
|
+
} }), _jsxs("select", { defaultValue: "p", onMouseDown: preserveEditorSelection, onChange: (e) => {
|
|
2083
2761
|
const val = e.target.value;
|
|
2084
2762
|
if (val === "p")
|
|
2085
2763
|
applyFormatBlock("<p>");
|
|
@@ -2096,42 +2774,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2096
2774
|
borderRadius: 6,
|
|
2097
2775
|
background: "var(--srte-input-bg)",
|
|
2098
2776
|
color: "var(--srte-input-text)",
|
|
2099
|
-
}, children: [_jsx("option", { value: "p", children: "Paragraph" }), _jsx("option", { value: "h1", children: "Heading 1" }), _jsx("option", { value: "h2", children: "Heading 2" }), _jsx("option", { value: "h3", children: "Heading 3" })] }), _jsx("button", { title: "Bold", onClick: () => exec("bold"), style: {
|
|
2100
|
-
height: 32,
|
|
2101
|
-
minWidth: 32,
|
|
2102
|
-
padding: "0 8px",
|
|
2103
|
-
border: "1px solid var(--srte-input-border)",
|
|
2104
|
-
borderRadius: 6,
|
|
2105
|
-
background: "var(--srte-input-bg)",
|
|
2106
|
-
color: "var(--srte-input-text)",
|
|
2107
|
-
}, children: _jsx("span", { style: { fontWeight: 700 }, children: "B" }) }), _jsx("button", { title: "Italic", onClick: () => exec("italic"), style: {
|
|
2108
|
-
height: 32,
|
|
2109
|
-
minWidth: 32,
|
|
2110
|
-
padding: "0 8px",
|
|
2111
|
-
border: "1px solid var(--srte-input-border)",
|
|
2112
|
-
borderRadius: 6,
|
|
2113
|
-
background: "var(--srte-input-bg)",
|
|
2114
|
-
fontStyle: "italic",
|
|
2115
|
-
color: "var(--srte-input-text)",
|
|
2116
|
-
}, children: "I" }), _jsx("button", { title: "Underline", onClick: () => exec("underline"), style: {
|
|
2117
|
-
height: 32,
|
|
2118
|
-
minWidth: 32,
|
|
2119
|
-
padding: "0 8px",
|
|
2120
|
-
border: "1px solid var(--srte-input-border)",
|
|
2121
|
-
borderRadius: 6,
|
|
2122
|
-
background: "var(--srte-input-bg)",
|
|
2123
|
-
textDecoration: "underline",
|
|
2124
|
-
color: "var(--srte-input-text)",
|
|
2125
|
-
}, children: "U" }), _jsx("button", { title: "Strikethrough", onClick: () => exec("strikeThrough"), style: {
|
|
2126
|
-
height: 32,
|
|
2127
|
-
minWidth: 32,
|
|
2128
|
-
padding: "0 8px",
|
|
2129
|
-
border: "1px solid var(--srte-input-border)",
|
|
2130
|
-
borderRadius: 6,
|
|
2131
|
-
background: "var(--srte-input-bg)",
|
|
2132
|
-
textDecoration: "line-through",
|
|
2133
|
-
color: "var(--srte-input-text)",
|
|
2134
|
-
}, children: "S" }), _jsxs("select", { value: currentFontSize, onMouseDown: () => {
|
|
2777
|
+
}, children: [_jsx("option", { value: "p", children: "Paragraph" }), _jsx("option", { value: "h1", children: "Heading 1" }), _jsx("option", { value: "h2", children: "Heading 2" }), _jsx("option", { value: "h3", children: "Heading 3" })] }), _jsx("button", { title: "Bold", onClick: () => exec("bold"), "aria-pressed": activeState.bold, style: activeButtonStyle(activeState.bold), children: _jsx("span", { style: { fontWeight: 700 }, children: "B" }) }), _jsx("button", { title: "Italic", onClick: () => exec("italic"), "aria-pressed": activeState.italic, style: activeButtonStyle(activeState.italic, { fontStyle: "italic" }), children: "I" }), _jsx("button", { title: "Underline", onClick: () => exec("underline"), "aria-pressed": activeState.underline, style: activeButtonStyle(activeState.underline, { textDecoration: "underline" }), children: "U" }), _jsx("button", { title: "Strikethrough", onClick: () => exec("strikeThrough"), "aria-pressed": activeState.strikeThrough, style: activeButtonStyle(activeState.strikeThrough, { textDecoration: "line-through" }), children: "S" }), _jsxs("select", { value: currentFontSize, onMouseDown: () => {
|
|
2135
2778
|
// Save selection before dropdown interaction
|
|
2136
2779
|
const sel = window.getSelection();
|
|
2137
2780
|
if (sel && sel.rangeCount > 0) {
|
|
@@ -2188,7 +2831,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2188
2831
|
borderRadius: 6,
|
|
2189
2832
|
background: "var(--srte-input-bg)",
|
|
2190
2833
|
color: "var(--srte-input-text)",
|
|
2191
|
-
}, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { title: "Subscript", onClick: () => exec("subscript"), style: {
|
|
2834
|
+
}, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { title: "Subscript", onClick: () => exec("subscript"), "aria-pressed": activeState.subscript, style: activeButtonStyle(activeState.subscript), children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { title: "Superscript", onClick: () => exec("superscript"), "aria-pressed": activeState.superscript, style: activeButtonStyle(activeState.superscript), children: ["X", _jsx("sup", { children: "2" })] }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), "aria-pressed": activeState.unorderedList, style: activeButtonStyle(activeState.unorderedList, { padding: "0 10px" }), children: "\u2022 List" }), _jsx("button", { title: "Numbered list", onClick: () => exec("insertOrderedList"), "aria-pressed": activeState.orderedList, style: activeButtonStyle(activeState.orderedList, { padding: "0 10px" }), children: "1. List" }), _jsx("button", { title: "Blockquote", onClick: toggleBlockquote, "aria-pressed": activeState.blockquote, style: activeButtonStyle(activeState.blockquote), children: "\u275D" }), _jsx("button", { title: "Special characters", onClick: () => setShowSpecialChars(true), style: {
|
|
2192
2835
|
height: 32,
|
|
2193
2836
|
minWidth: 32,
|
|
2194
2837
|
padding: "0 8px",
|
|
@@ -2196,54 +2839,10 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2196
2839
|
borderRadius: 6,
|
|
2197
2840
|
background: "var(--srte-input-bg)",
|
|
2198
2841
|
color: "var(--srte-input-text)",
|
|
2199
|
-
}, children:
|
|
2200
|
-
height: 32,
|
|
2201
|
-
minWidth: 32,
|
|
2202
|
-
padding: "0 8px",
|
|
2203
|
-
border: "1px solid var(--srte-input-border)",
|
|
2204
|
-
borderRadius: 6,
|
|
2205
|
-
background: "var(--srte-input-bg)",
|
|
2206
|
-
color: "var(--srte-input-text)",
|
|
2207
|
-
}, children: ["X", _jsx("sup", { children: "2" })] }), _jsx("button", { title: "Bulleted list", onClick: () => exec("insertUnorderedList"), style: {
|
|
2208
|
-
height: 32,
|
|
2209
|
-
padding: "0 10px",
|
|
2210
|
-
border: "1px solid var(--srte-input-border)",
|
|
2211
|
-
borderRadius: 6,
|
|
2212
|
-
background: "var(--srte-input-bg)",
|
|
2213
|
-
color: "var(--srte-input-text)",
|
|
2214
|
-
}, children: "\u2022 List" }), _jsx("button", { title: "Numbered list", onClick: () => exec("insertOrderedList"), style: {
|
|
2215
|
-
height: 32,
|
|
2216
|
-
padding: "0 10px",
|
|
2217
|
-
border: "1px solid var(--srte-input-border)",
|
|
2218
|
-
borderRadius: 6,
|
|
2219
|
-
background: "var(--srte-input-bg)",
|
|
2220
|
-
color: "var(--srte-input-text)",
|
|
2221
|
-
}, children: "1. List" }), _jsx("button", { title: "Blockquote", onClick: toggleBlockquote, style: {
|
|
2222
|
-
height: 32,
|
|
2223
|
-
minWidth: 32,
|
|
2224
|
-
padding: "0 8px",
|
|
2225
|
-
border: "1px solid var(--srte-input-border)",
|
|
2226
|
-
borderRadius: 6,
|
|
2227
|
-
background: "var(--srte-input-bg)",
|
|
2228
|
-
color: "var(--srte-input-text)",
|
|
2229
|
-
}, children: "\u275D" }), _jsx("button", { title: "Special characters", onClick: () => setShowSpecialChars(true), style: {
|
|
2230
|
-
height: 32,
|
|
2231
|
-
minWidth: 32,
|
|
2232
|
-
padding: "0 8px",
|
|
2233
|
-
border: "1px solid var(--srte-input-border)",
|
|
2234
|
-
borderRadius: 6,
|
|
2235
|
-
background: "var(--srte-input-bg)",
|
|
2236
|
-
color: "var(--srte-input-text)",
|
|
2237
|
-
}, children: "\u03A9" }), _jsx("button", { title: "Code block", onClick: () => exec("formatBlock", "<pre>"), style: {
|
|
2238
|
-
height: 32,
|
|
2842
|
+
}, children: "\u03A9" }), _jsx("button", { title: "Code block", onClick: () => exec("formatBlock", "<pre>"), "aria-pressed": activeState.codeBlock, style: activeButtonStyle(activeState.codeBlock, {
|
|
2239
2843
|
minWidth: 36,
|
|
2240
|
-
padding: "0 8px",
|
|
2241
|
-
border: "1px solid var(--srte-input-border)",
|
|
2242
|
-
borderRadius: 6,
|
|
2243
|
-
background: "var(--srte-input-bg)",
|
|
2244
2844
|
fontFamily: "ui-monospace, SFMono-Regular, Menlo",
|
|
2245
|
-
|
|
2246
|
-
}, children: "< />" }), formula && (_jsx("button", { title: "Insert formula", onClick: () => setShowFormulaDialog(true), style: {
|
|
2845
|
+
}), children: "< />" }), formula && (_jsx("button", { title: "Insert formula", onClick: () => setShowFormulaDialog(true), style: {
|
|
2247
2846
|
height: 32,
|
|
2248
2847
|
minWidth: 32,
|
|
2249
2848
|
padding: "0 8px",
|
|
@@ -2323,7 +2922,21 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2323
2922
|
borderRadius: 6,
|
|
2324
2923
|
background: "var(--srte-input-bg)",
|
|
2325
2924
|
color: "var(--srte-input-text)",
|
|
2326
|
-
}, children: "Export MD" }),
|
|
2925
|
+
}, children: "Export MD" }), _jsx("button", { title: "Export DOCX", onClick: exportDocx, style: {
|
|
2926
|
+
height: 32,
|
|
2927
|
+
padding: "0 10px",
|
|
2928
|
+
border: "1px solid var(--srte-input-border)",
|
|
2929
|
+
borderRadius: 6,
|
|
2930
|
+
background: "var(--srte-input-bg)",
|
|
2931
|
+
color: "var(--srte-input-text)",
|
|
2932
|
+
}, children: "Export DOCX" }), _jsx("button", { title: "Export PDF", onClick: exportPdf, style: {
|
|
2933
|
+
height: 32,
|
|
2934
|
+
padding: "0 10px",
|
|
2935
|
+
border: "1px solid var(--srte-input-border)",
|
|
2936
|
+
borderRadius: 6,
|
|
2937
|
+
background: "var(--srte-input-bg)",
|
|
2938
|
+
color: "var(--srte-input-text)",
|
|
2939
|
+
}, children: "Export PDF" }), _jsxs("div", { style: {
|
|
2327
2940
|
display: "inline-flex",
|
|
2328
2941
|
gap: 4,
|
|
2329
2942
|
alignItems: "center",
|
|
@@ -2407,17 +3020,19 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2407
3020
|
position: "fixed",
|
|
2408
3021
|
inset: 0,
|
|
2409
3022
|
background: "var(--srte-modal-backdrop)",
|
|
3023
|
+
backdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
3024
|
+
WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
2410
3025
|
display: "flex",
|
|
2411
3026
|
alignItems: "center",
|
|
2412
3027
|
justifyContent: "center",
|
|
2413
|
-
zIndex:
|
|
3028
|
+
zIndex: 90,
|
|
2414
3029
|
}, onClick: () => setShowTableDialog(false), children: _jsxs("div", { style: {
|
|
2415
3030
|
background: "var(--srte-modal-bg)",
|
|
2416
3031
|
color: "var(--srte-modal-text)",
|
|
2417
3032
|
padding: 16,
|
|
2418
3033
|
borderRadius: 8,
|
|
2419
3034
|
minWidth: 280,
|
|
2420
|
-
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 8 }, children: "Insert table" }), _jsxs("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [_jsx("div", { style: {
|
|
3035
|
+
}, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, marginBottom: 8 }, children: "Insert table" }), _jsxs("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [_jsx("div", { style: {
|
|
2421
3036
|
display: "grid",
|
|
2422
3037
|
gridTemplateColumns: "repeat(10, 18px)",
|
|
2423
3038
|
gap: 2,
|
|
@@ -2451,6 +3066,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2451
3066
|
position: "fixed",
|
|
2452
3067
|
inset: 0,
|
|
2453
3068
|
background: "var(--srte-modal-backdrop)",
|
|
3069
|
+
backdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
3070
|
+
WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
2454
3071
|
display: "flex",
|
|
2455
3072
|
alignItems: "center",
|
|
2456
3073
|
justifyContent: "center",
|
|
@@ -2505,6 +3122,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2505
3122
|
position: "fixed",
|
|
2506
3123
|
inset: 0,
|
|
2507
3124
|
background: "var(--srte-modal-backdrop)",
|
|
3125
|
+
backdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
3126
|
+
WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
2508
3127
|
display: "flex",
|
|
2509
3128
|
alignItems: "center",
|
|
2510
3129
|
justifyContent: "center",
|
|
@@ -2571,6 +3190,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2571
3190
|
position: "fixed",
|
|
2572
3191
|
inset: 0,
|
|
2573
3192
|
background: "var(--srte-modal-backdrop)",
|
|
3193
|
+
backdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
3194
|
+
WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
2574
3195
|
display: "flex",
|
|
2575
3196
|
alignItems: "center",
|
|
2576
3197
|
justifyContent: "center",
|
|
@@ -2615,6 +3236,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2615
3236
|
position: "fixed",
|
|
2616
3237
|
inset: 0,
|
|
2617
3238
|
background: "var(--srte-modal-backdrop)",
|
|
3239
|
+
backdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
3240
|
+
WebkitBackdropFilter: "var(--srte-modal-backdrop-filter)",
|
|
2618
3241
|
display: "flex",
|
|
2619
3242
|
alignItems: "center",
|
|
2620
3243
|
justifyContent: "center",
|
|
@@ -2712,383 +3335,389 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
2712
3335
|
borderRadius: 4,
|
|
2713
3336
|
background: "var(--srte-input-bg)",
|
|
2714
3337
|
color: "var(--srte-modal-text)",
|
|
2715
|
-
}, title: sym, children: sym }, i)))] })] }) })),
|
|
3338
|
+
}, title: sym, children: sym }, i)))] })] }) })), _jsxs("div", { ref: editorScrollRef, style: {
|
|
2716
3339
|
width: "100%",
|
|
2717
3340
|
maxWidth: "100%",
|
|
2718
3341
|
flex: "1 1 auto",
|
|
3342
|
+
minWidth: 0,
|
|
2719
3343
|
minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
|
|
2720
3344
|
maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight,
|
|
2721
3345
|
overflowY: "auto",
|
|
2722
|
-
overflowX: "
|
|
3346
|
+
overflowX: "auto",
|
|
3347
|
+
overscrollBehavior: "contain",
|
|
2723
3348
|
boxSizing: "border-box",
|
|
2724
3349
|
position: "relative",
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
3350
|
+
scrollPaddingBottom: 24,
|
|
3351
|
+
}, children: [_jsx("div", { ref: editableRef, contentEditable: !readOnly, suppressContentEditableWarning: true, onInput: handleInput, onKeyUp: updateActiveState, onMouseUp: updateActiveState, onCompositionStart: () => (isComposingRef.current = true), onCompositionEnd: () => {
|
|
3352
|
+
isComposingRef.current = false;
|
|
3353
|
+
handleInput();
|
|
3354
|
+
}, onPaste: (e) => {
|
|
3355
|
+
const items = e.clipboardData?.files;
|
|
3356
|
+
if (media && items && items.length) {
|
|
3357
|
+
const hasImage = Array.from(items).some((f) => f.type.startsWith("image/"));
|
|
3358
|
+
if (hasImage) {
|
|
3359
|
+
e.preventDefault();
|
|
3360
|
+
handleLocalImageFiles(items);
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
const html = e.clipboardData?.getData("text/html");
|
|
3365
|
+
if (html) {
|
|
2733
3366
|
e.preventDefault();
|
|
2734
|
-
|
|
2735
|
-
return;
|
|
3367
|
+
insertCleanHtml(cleanPastedHtml(html));
|
|
2736
3368
|
}
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
}
|
|
2743
|
-
}, onDragOver: (e) => {
|
|
2744
|
-
// Allow dragging images within editor and file drops
|
|
2745
|
-
if (draggedImageRef.current ||
|
|
2746
|
-
e.dataTransfer?.types?.includes("Files")) {
|
|
2747
|
-
e.preventDefault();
|
|
2748
|
-
}
|
|
2749
|
-
}, onDrop: (e) => {
|
|
2750
|
-
// Move existing dragged image inside editor
|
|
2751
|
-
if (draggedImageRef.current) {
|
|
2752
|
-
e.preventDefault();
|
|
2753
|
-
const x = e.clientX;
|
|
2754
|
-
const y = e.clientY;
|
|
2755
|
-
let range = null;
|
|
2756
|
-
// @ts-ignore
|
|
2757
|
-
if (document.caretRangeFromPoint) {
|
|
2758
|
-
// @ts-ignore
|
|
2759
|
-
range = document.caretRangeFromPoint(x, y);
|
|
3369
|
+
}, onDragOver: (e) => {
|
|
3370
|
+
// Allow dragging images within editor and file drops
|
|
3371
|
+
if (draggedImageRef.current ||
|
|
3372
|
+
e.dataTransfer?.types?.includes("Files")) {
|
|
3373
|
+
e.preventDefault();
|
|
2760
3374
|
}
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
3375
|
+
}, onDrop: (e) => {
|
|
3376
|
+
// Move existing dragged image inside editor
|
|
3377
|
+
if (draggedImageRef.current) {
|
|
3378
|
+
e.preventDefault();
|
|
3379
|
+
const x = e.clientX;
|
|
3380
|
+
const y = e.clientY;
|
|
3381
|
+
let range = null;
|
|
3382
|
+
// @ts-ignore
|
|
3383
|
+
if (document.caretRangeFromPoint) {
|
|
3384
|
+
// @ts-ignore
|
|
3385
|
+
range = document.caretRangeFromPoint(x, y);
|
|
2766
3386
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
editableRef.current?.contains(range.commonAncestorContainer)) {
|
|
2773
|
-
// Avoid inserting inside the image itself
|
|
2774
|
-
if (range.startContainer === img || range.endContainer === img)
|
|
2775
|
-
return;
|
|
2776
|
-
// If dropping inside a link, insert right after the link element
|
|
2777
|
-
let container = range.commonAncestorContainer;
|
|
2778
|
-
let linkAncestor = null;
|
|
2779
|
-
let el = container;
|
|
2780
|
-
while (el && el !== editableRef.current) {
|
|
2781
|
-
if (el.tagName === "A") {
|
|
2782
|
-
linkAncestor = el;
|
|
2783
|
-
break;
|
|
3387
|
+
else if (document.caretPositionFromPoint) {
|
|
3388
|
+
const pos = document.caretPositionFromPoint(x, y);
|
|
3389
|
+
if (pos) {
|
|
3390
|
+
range = document.createRange();
|
|
3391
|
+
range.setStart(pos.offsetNode, pos.offset);
|
|
2784
3392
|
}
|
|
2785
|
-
el = el.parentElement;
|
|
2786
|
-
}
|
|
2787
|
-
if (linkAncestor) {
|
|
2788
|
-
linkAncestor.parentElement?.insertBefore(img, linkAncestor.nextSibling);
|
|
2789
3393
|
}
|
|
2790
|
-
|
|
2791
|
-
|
|
3394
|
+
const img = draggedImageRef.current;
|
|
3395
|
+
draggedImageRef.current = null;
|
|
3396
|
+
if (range &&
|
|
3397
|
+
img &&
|
|
3398
|
+
editableRef.current?.contains(range.commonAncestorContainer)) {
|
|
3399
|
+
// Avoid inserting inside the image itself
|
|
3400
|
+
if (range.startContainer === img || range.endContainer === img)
|
|
3401
|
+
return;
|
|
3402
|
+
// If dropping inside a link, insert right after the link element
|
|
3403
|
+
let container = range.commonAncestorContainer;
|
|
3404
|
+
let linkAncestor = null;
|
|
3405
|
+
let el = container;
|
|
3406
|
+
while (el && el !== editableRef.current) {
|
|
3407
|
+
if (el.tagName === "A") {
|
|
3408
|
+
linkAncestor = el;
|
|
3409
|
+
break;
|
|
3410
|
+
}
|
|
3411
|
+
el = el.parentElement;
|
|
3412
|
+
}
|
|
3413
|
+
if (linkAncestor) {
|
|
3414
|
+
linkAncestor.parentElement?.insertBefore(img, linkAncestor.nextSibling);
|
|
3415
|
+
}
|
|
3416
|
+
else {
|
|
3417
|
+
range.insertNode(img);
|
|
3418
|
+
}
|
|
3419
|
+
const r = document.createRange();
|
|
3420
|
+
r.setStartAfter(img);
|
|
3421
|
+
r.collapse(true);
|
|
3422
|
+
safeSelectRange(r);
|
|
3423
|
+
setSelectedImage(img);
|
|
3424
|
+
scheduleImageOverlay();
|
|
3425
|
+
handleInput();
|
|
2792
3426
|
}
|
|
2793
|
-
|
|
2794
|
-
r.setStartAfter(img);
|
|
2795
|
-
r.collapse(true);
|
|
2796
|
-
safeSelectRange(r);
|
|
2797
|
-
setSelectedImage(img);
|
|
2798
|
-
scheduleImageOverlay();
|
|
2799
|
-
handleInput();
|
|
3427
|
+
return;
|
|
2800
3428
|
}
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
const y = e.clientY;
|
|
2808
|
-
let range = null;
|
|
2809
|
-
// @ts-ignore
|
|
2810
|
-
if (document.caretRangeFromPoint) {
|
|
3429
|
+
if (media && e.dataTransfer?.files?.length) {
|
|
3430
|
+
e.preventDefault();
|
|
3431
|
+
// Try to move caret to drop point
|
|
3432
|
+
const x = e.clientX;
|
|
3433
|
+
const y = e.clientY;
|
|
3434
|
+
let range = null;
|
|
2811
3435
|
// @ts-ignore
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
if (
|
|
2817
|
-
|
|
2818
|
-
|
|
3436
|
+
if (document.caretRangeFromPoint) {
|
|
3437
|
+
// @ts-ignore
|
|
3438
|
+
range = document.caretRangeFromPoint(x, y);
|
|
3439
|
+
}
|
|
3440
|
+
else if (document.caretPositionFromPoint) {
|
|
3441
|
+
const pos = document.caretPositionFromPoint(x, y);
|
|
3442
|
+
if (pos) {
|
|
3443
|
+
range = document.createRange();
|
|
3444
|
+
range.setStart(pos.offsetNode, pos.offset);
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
if (range) {
|
|
3448
|
+
const sel = window.getSelection();
|
|
3449
|
+
sel?.removeAllRanges();
|
|
3450
|
+
sel?.addRange(range);
|
|
2819
3451
|
}
|
|
3452
|
+
handleLocalImageFiles(e.dataTransfer.files);
|
|
2820
3453
|
}
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
3454
|
+
}, onClick: (e) => {
|
|
3455
|
+
const t = e.target;
|
|
3456
|
+
if (t && t.tagName === "IMG") {
|
|
3457
|
+
setSelectedImage(t);
|
|
3458
|
+
scheduleImageOverlay();
|
|
2825
3459
|
}
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
const dt = e.dataTransfer;
|
|
2847
|
-
if (dt && typeof dt.setDragImage === "function") {
|
|
2848
|
-
const ghost = new Image();
|
|
2849
|
-
ghost.src = t.src;
|
|
2850
|
-
ghost.width = Math.min(120, t.width);
|
|
2851
|
-
ghost.height = Math.min(120, t.height);
|
|
2852
|
-
dt.setDragImage(ghost, 10, 10);
|
|
3460
|
+
else {
|
|
3461
|
+
setSelectedImage(null);
|
|
3462
|
+
setImageOverlay(null);
|
|
3463
|
+
}
|
|
3464
|
+
}, onDragStart: (e) => {
|
|
3465
|
+
const t = e.target;
|
|
3466
|
+
if (t && t.tagName === "IMG") {
|
|
3467
|
+
draggedImageRef.current = t;
|
|
3468
|
+
try {
|
|
3469
|
+
e.dataTransfer?.setData("text/plain", "moving-image");
|
|
3470
|
+
e.dataTransfer.effectAllowed = "move";
|
|
3471
|
+
// Provide a subtle drag image
|
|
3472
|
+
const dt = e.dataTransfer;
|
|
3473
|
+
if (dt && typeof dt.setDragImage === "function") {
|
|
3474
|
+
const ghost = new Image();
|
|
3475
|
+
ghost.src = t.src;
|
|
3476
|
+
ghost.width = Math.min(120, t.width);
|
|
3477
|
+
ghost.height = Math.min(120, t.height);
|
|
3478
|
+
dt.setDragImage(ghost, 10, 10);
|
|
3479
|
+
}
|
|
2853
3480
|
}
|
|
3481
|
+
catch { }
|
|
2854
3482
|
}
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
3483
|
+
else {
|
|
3484
|
+
draggedImageRef.current = null;
|
|
3485
|
+
}
|
|
3486
|
+
}, onDragEnd: () => {
|
|
2858
3487
|
draggedImageRef.current = null;
|
|
2859
|
-
}
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
if (!el.innerHTML || el.innerHTML === "<br>") {
|
|
2875
|
-
el.innerHTML = "<p><br></p>";
|
|
2876
|
-
}
|
|
2877
|
-
}, onKeyDown: (e) => {
|
|
2878
|
-
if (formula &&
|
|
2879
|
-
(e.metaKey || e.ctrlKey) &&
|
|
2880
|
-
String(e.key).toLowerCase() === "m") {
|
|
2881
|
-
e.preventDefault();
|
|
2882
|
-
setShowFormulaDialog(true);
|
|
2883
|
-
return;
|
|
2884
|
-
}
|
|
2885
|
-
// Keep Tab for indentation in lists; otherwise insert 2 spaces
|
|
2886
|
-
if (e.key === "Tab") {
|
|
2887
|
-
e.preventDefault();
|
|
2888
|
-
if (document.queryCommandState("insertUnorderedList") ||
|
|
2889
|
-
document.queryCommandState("insertOrderedList")) {
|
|
2890
|
-
exec(e.shiftKey ? "outdent" : "indent");
|
|
3488
|
+
}, style: {
|
|
3489
|
+
minHeight: typeof minHeight === "number" ? `${minHeight}px` : minHeight,
|
|
3490
|
+
maxWidth: "100%",
|
|
3491
|
+
overflowX: "visible",
|
|
3492
|
+
padding: "16px",
|
|
3493
|
+
paddingBottom: "32px",
|
|
3494
|
+
outline: "none",
|
|
3495
|
+
lineHeight: 1.6,
|
|
3496
|
+
boxSizing: "border-box",
|
|
3497
|
+
fontFamily: defaultFont || "inherit",
|
|
3498
|
+
}, "data-placeholder": placeholder, onFocus: (e) => {
|
|
3499
|
+
// Ensure the editor has at least one paragraph to type into
|
|
3500
|
+
const el = e.currentTarget;
|
|
3501
|
+
if (!el.innerHTML || el.innerHTML === "<br>") {
|
|
3502
|
+
el.innerHTML = "<p><br></p>";
|
|
2891
3503
|
}
|
|
2892
|
-
|
|
2893
|
-
|
|
3504
|
+
updateActiveState();
|
|
3505
|
+
}, onKeyDown: (e) => {
|
|
3506
|
+
if (formula &&
|
|
3507
|
+
(e.metaKey || e.ctrlKey) &&
|
|
3508
|
+
String(e.key).toLowerCase() === "m") {
|
|
3509
|
+
e.preventDefault();
|
|
3510
|
+
setShowFormulaDialog(true);
|
|
3511
|
+
return;
|
|
2894
3512
|
}
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
cell &&
|
|
2902
|
-
cell.parentElement &&
|
|
2903
|
-
cell.parentElement.parentElement) {
|
|
2904
|
-
const row = cell.parentElement;
|
|
2905
|
-
const tbody = row.parentElement;
|
|
2906
|
-
const cells = Array.from(row.children).filter((c) => c.tagName === "TD" ||
|
|
2907
|
-
c.tagName === "TH");
|
|
2908
|
-
const rows = Array.from(tbody.children);
|
|
2909
|
-
const rIdx = rows.indexOf(row);
|
|
2910
|
-
const cIdx = cells.indexOf(cell);
|
|
2911
|
-
const atStart = (sel?.anchorOffset || 0) === 0;
|
|
2912
|
-
const cellTextLen = (cell.textContent || "").length;
|
|
2913
|
-
const atEnd = (sel?.anchorOffset || 0) >= cellTextLen;
|
|
2914
|
-
let target = null;
|
|
2915
|
-
if (e.key === "ArrowLeft" && atStart && cIdx > 0) {
|
|
2916
|
-
target = row.children[cIdx - 1];
|
|
2917
|
-
}
|
|
2918
|
-
else if (e.key === "ArrowRight" &&
|
|
2919
|
-
atEnd &&
|
|
2920
|
-
cIdx < row.children.length - 1) {
|
|
2921
|
-
target = row.children[cIdx + 1];
|
|
2922
|
-
}
|
|
2923
|
-
else if (e.key === "ArrowUp" && rIdx > 0 && atStart) {
|
|
2924
|
-
target = rows[rIdx - 1].children[cIdx];
|
|
3513
|
+
// Keep Tab for indentation in lists; otherwise insert 2 spaces
|
|
3514
|
+
if (e.key === "Tab") {
|
|
3515
|
+
e.preventDefault();
|
|
3516
|
+
if (document.queryCommandState("insertUnorderedList") ||
|
|
3517
|
+
document.queryCommandState("insertOrderedList")) {
|
|
3518
|
+
exec(e.shiftKey ? "outdent" : "indent");
|
|
2925
3519
|
}
|
|
2926
|
-
else
|
|
2927
|
-
|
|
2928
|
-
atEnd) {
|
|
2929
|
-
target = rows[rIdx + 1].children[cIdx];
|
|
3520
|
+
else {
|
|
3521
|
+
document.execCommand("insertText", false, " ");
|
|
2930
3522
|
}
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
3523
|
+
}
|
|
3524
|
+
// Table navigation with arrows inside cells
|
|
3525
|
+
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
|
|
3526
|
+
const sel = window.getSelection();
|
|
3527
|
+
const cell = getClosestCell(sel?.anchorNode || null);
|
|
3528
|
+
if (table &&
|
|
3529
|
+
cell &&
|
|
3530
|
+
cell.parentElement &&
|
|
3531
|
+
cell.parentElement.parentElement) {
|
|
3532
|
+
const row = cell.parentElement;
|
|
3533
|
+
const tbody = row.parentElement;
|
|
3534
|
+
const cells = Array.from(row.children).filter((c) => c.tagName === "TD" ||
|
|
3535
|
+
c.tagName === "TH");
|
|
3536
|
+
const rows = Array.from(tbody.children);
|
|
3537
|
+
const rIdx = rows.indexOf(row);
|
|
3538
|
+
const cIdx = cells.indexOf(cell);
|
|
3539
|
+
const atStart = (sel?.anchorOffset || 0) === 0;
|
|
3540
|
+
const cellTextLen = (cell.textContent || "").length;
|
|
3541
|
+
const atEnd = (sel?.anchorOffset || 0) >= cellTextLen;
|
|
3542
|
+
let target = null;
|
|
3543
|
+
if (e.key === "ArrowLeft" && atStart && cIdx > 0) {
|
|
3544
|
+
target = row.children[cIdx - 1];
|
|
3545
|
+
}
|
|
3546
|
+
else if (e.key === "ArrowRight" &&
|
|
3547
|
+
atEnd &&
|
|
3548
|
+
cIdx < row.children.length - 1) {
|
|
3549
|
+
target = row.children[cIdx + 1];
|
|
3550
|
+
}
|
|
3551
|
+
else if (e.key === "ArrowUp" && rIdx > 0 && atStart) {
|
|
3552
|
+
target = rows[rIdx - 1].children[cIdx];
|
|
3553
|
+
}
|
|
3554
|
+
else if (e.key === "ArrowDown" &&
|
|
3555
|
+
rIdx < rows.length - 1 &&
|
|
3556
|
+
atEnd) {
|
|
3557
|
+
target = rows[rIdx + 1].children[cIdx];
|
|
3558
|
+
}
|
|
3559
|
+
if (target) {
|
|
3560
|
+
e.preventDefault();
|
|
3561
|
+
moveCaretToCell(target, e.key === "ArrowRight" || e.key === "ArrowDown");
|
|
3562
|
+
}
|
|
2934
3563
|
}
|
|
2935
3564
|
}
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
clearSelectionDecor();
|
|
2941
|
-
return;
|
|
2942
|
-
}
|
|
2943
|
-
const pos = getCellPosition(cell);
|
|
2944
|
-
if (!pos)
|
|
2945
|
-
return;
|
|
2946
|
-
selectingRef.current = { tbody: pos.tbody, start: cell };
|
|
2947
|
-
const onMove = (ev) => {
|
|
2948
|
-
const under = document.elementFromPoint(ev.clientX, ev.clientY);
|
|
2949
|
-
const overCell = getClosestCell(under);
|
|
2950
|
-
const startInfo = selectingRef.current;
|
|
2951
|
-
if (!overCell || !startInfo)
|
|
2952
|
-
return;
|
|
2953
|
-
const a = getCellPosition(startInfo.start);
|
|
2954
|
-
const b = getCellPosition(overCell);
|
|
2955
|
-
if (!a || !b || a.tbody !== b.tbody)
|
|
3565
|
+
}, onMouseDown: (e) => {
|
|
3566
|
+
const cell = getClosestCell(e.target);
|
|
3567
|
+
if (!cell) {
|
|
3568
|
+
clearSelectionDecor();
|
|
2956
3569
|
return;
|
|
2957
|
-
|
|
2958
|
-
const
|
|
2959
|
-
|
|
2960
|
-
const ec = Math.max(a.cIdx, b.cIdx);
|
|
2961
|
-
updateSelectionDecor(a.tbody, sr, sc, er, ec);
|
|
2962
|
-
};
|
|
2963
|
-
const onUp = () => {
|
|
2964
|
-
window.removeEventListener("mousemove", onMove);
|
|
2965
|
-
window.removeEventListener("mouseup", onUp);
|
|
2966
|
-
selectingRef.current = null;
|
|
2967
|
-
};
|
|
2968
|
-
window.addEventListener("mousemove", onMove);
|
|
2969
|
-
window.addEventListener("mouseup", onUp);
|
|
2970
|
-
}, onContextMenu: (e) => {
|
|
2971
|
-
const target = e.target;
|
|
2972
|
-
if (target && target.tagName === "IMG") {
|
|
2973
|
-
e.preventDefault();
|
|
2974
|
-
const vw = window.innerWidth;
|
|
2975
|
-
const vh = window.innerHeight;
|
|
2976
|
-
const menuW = 220;
|
|
2977
|
-
const menuH = 200;
|
|
2978
|
-
const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
|
|
2979
|
-
const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
|
|
2980
|
-
setImageMenu({ x, y, img: target });
|
|
2981
|
-
setTableMenu(null);
|
|
2982
|
-
return;
|
|
2983
|
-
}
|
|
2984
|
-
const cell = getClosestCell(e.target);
|
|
2985
|
-
if (cell) {
|
|
2986
|
-
e.preventDefault();
|
|
2987
|
-
const vw = window.innerWidth;
|
|
2988
|
-
const vh = window.innerHeight;
|
|
2989
|
-
const menuW = 220;
|
|
2990
|
-
const menuH = 300;
|
|
2991
|
-
const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
|
|
2992
|
-
const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
|
|
2993
|
-
setTableMenu({ x, y, cell });
|
|
2994
|
-
}
|
|
2995
|
-
else {
|
|
2996
|
-
setTableMenu(null);
|
|
2997
|
-
setImageMenu(null);
|
|
2998
|
-
}
|
|
2999
|
-
} }) }), selectedImage && imageOverlay && (_jsxs("div", { style: {
|
|
3000
|
-
position: "fixed",
|
|
3001
|
-
left: imageOverlay.left,
|
|
3002
|
-
top: imageOverlay.top,
|
|
3003
|
-
width: imageOverlay.width,
|
|
3004
|
-
height: imageOverlay.height,
|
|
3005
|
-
pointerEvents: "none",
|
|
3006
|
-
zIndex: 49,
|
|
3007
|
-
}, children: [_jsx("div", { style: {
|
|
3008
|
-
position: "absolute",
|
|
3009
|
-
inset: 0,
|
|
3010
|
-
outline: "2px solid var(--srte-accent)",
|
|
3011
|
-
outlineOffset: -2,
|
|
3012
|
-
} }), _jsx("div", { title: "Resize", onMouseDown: (e) => {
|
|
3013
|
-
e.preventDefault();
|
|
3014
|
-
if (!selectedImage)
|
|
3570
|
+
}
|
|
3571
|
+
const pos = getCellPosition(cell);
|
|
3572
|
+
if (!pos)
|
|
3015
3573
|
return;
|
|
3016
|
-
|
|
3017
|
-
side: "left",
|
|
3018
|
-
startX: e.clientX,
|
|
3019
|
-
startWidth: selectedImage.getBoundingClientRect().width,
|
|
3020
|
-
};
|
|
3574
|
+
selectingRef.current = { tbody: pos.tbody, start: cell };
|
|
3021
3575
|
const onMove = (ev) => {
|
|
3022
|
-
const
|
|
3023
|
-
|
|
3576
|
+
const under = document.elementFromPoint(ev.clientX, ev.clientY);
|
|
3577
|
+
const overCell = getClosestCell(under);
|
|
3578
|
+
const startInfo = selectingRef.current;
|
|
3579
|
+
if (!overCell || !startInfo)
|
|
3024
3580
|
return;
|
|
3025
|
-
const
|
|
3026
|
-
const
|
|
3027
|
-
|
|
3028
|
-
selectedImage.style.height = "auto";
|
|
3029
|
-
scheduleImageOverlay();
|
|
3030
|
-
};
|
|
3031
|
-
const onUp = () => {
|
|
3032
|
-
window.removeEventListener("mousemove", onMove);
|
|
3033
|
-
window.removeEventListener("mouseup", onUp);
|
|
3034
|
-
resizingRef.current = null;
|
|
3035
|
-
handleInput();
|
|
3036
|
-
};
|
|
3037
|
-
window.addEventListener("mousemove", onMove);
|
|
3038
|
-
window.addEventListener("mouseup", onUp);
|
|
3039
|
-
}, style: {
|
|
3040
|
-
position: "absolute",
|
|
3041
|
-
left: -6,
|
|
3042
|
-
top: "50%",
|
|
3043
|
-
transform: "translateY(-50%)",
|
|
3044
|
-
width: 8,
|
|
3045
|
-
height: 24,
|
|
3046
|
-
background: "var(--srte-accent)",
|
|
3047
|
-
borderRadius: 2,
|
|
3048
|
-
cursor: "ew-resize",
|
|
3049
|
-
pointerEvents: "auto",
|
|
3050
|
-
} }), _jsx("div", { title: "Resize", onMouseDown: (e) => {
|
|
3051
|
-
e.preventDefault();
|
|
3052
|
-
if (!selectedImage)
|
|
3053
|
-
return;
|
|
3054
|
-
resizingRef.current = {
|
|
3055
|
-
side: "right",
|
|
3056
|
-
startX: e.clientX,
|
|
3057
|
-
startWidth: selectedImage.getBoundingClientRect().width,
|
|
3058
|
-
};
|
|
3059
|
-
const onMove = (ev) => {
|
|
3060
|
-
const info = resizingRef.current;
|
|
3061
|
-
if (!info || !selectedImage)
|
|
3581
|
+
const a = getCellPosition(startInfo.start);
|
|
3582
|
+
const b = getCellPosition(overCell);
|
|
3583
|
+
if (!a || !b || a.tbody !== b.tbody)
|
|
3062
3584
|
return;
|
|
3063
|
-
const
|
|
3064
|
-
const
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3585
|
+
const sr = Math.min(a.rIdx, b.rIdx);
|
|
3586
|
+
const sc = Math.min(a.cIdx, b.cIdx);
|
|
3587
|
+
const er = Math.max(a.rIdx, b.rIdx);
|
|
3588
|
+
const ec = Math.max(a.cIdx, b.cIdx);
|
|
3589
|
+
updateSelectionDecor(a.tbody, sr, sc, er, ec);
|
|
3068
3590
|
};
|
|
3069
3591
|
const onUp = () => {
|
|
3070
3592
|
window.removeEventListener("mousemove", onMove);
|
|
3071
3593
|
window.removeEventListener("mouseup", onUp);
|
|
3072
|
-
|
|
3073
|
-
handleInput();
|
|
3594
|
+
selectingRef.current = null;
|
|
3074
3595
|
};
|
|
3075
3596
|
window.addEventListener("mousemove", onMove);
|
|
3076
3597
|
window.addEventListener("mouseup", onUp);
|
|
3077
|
-
},
|
|
3598
|
+
}, onContextMenu: (e) => {
|
|
3599
|
+
const target = e.target;
|
|
3600
|
+
if (target && target.tagName === "IMG") {
|
|
3601
|
+
e.preventDefault();
|
|
3602
|
+
const vw = window.innerWidth;
|
|
3603
|
+
const vh = window.innerHeight;
|
|
3604
|
+
const menuW = 220;
|
|
3605
|
+
const menuH = 200;
|
|
3606
|
+
const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
|
|
3607
|
+
const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
|
|
3608
|
+
setImageMenu({ x, y, img: target });
|
|
3609
|
+
setTableMenu(null);
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
const cell = getClosestCell(e.target);
|
|
3613
|
+
if (cell) {
|
|
3614
|
+
e.preventDefault();
|
|
3615
|
+
const vw = window.innerWidth;
|
|
3616
|
+
const vh = window.innerHeight;
|
|
3617
|
+
const menuW = 220;
|
|
3618
|
+
const menuH = 300;
|
|
3619
|
+
const x = Math.max(8, Math.min(e.clientX, vw - menuW - 8));
|
|
3620
|
+
const y = Math.max(8, Math.min(e.clientY, vh - menuH - 8));
|
|
3621
|
+
setTableMenu({ x, y, cell });
|
|
3622
|
+
}
|
|
3623
|
+
else {
|
|
3624
|
+
setTableMenu(null);
|
|
3625
|
+
setImageMenu(null);
|
|
3626
|
+
}
|
|
3627
|
+
} }), selectedImage && imageOverlay && (_jsxs("div", { style: {
|
|
3078
3628
|
position: "absolute",
|
|
3079
|
-
|
|
3080
|
-
top:
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3629
|
+
left: imageOverlay.left,
|
|
3630
|
+
top: imageOverlay.top,
|
|
3631
|
+
width: imageOverlay.width,
|
|
3632
|
+
height: imageOverlay.height,
|
|
3633
|
+
pointerEvents: "none",
|
|
3634
|
+
zIndex: 5,
|
|
3635
|
+
}, children: [_jsx("div", { style: {
|
|
3636
|
+
position: "absolute",
|
|
3637
|
+
inset: 0,
|
|
3638
|
+
outline: "2px solid var(--srte-accent)",
|
|
3639
|
+
outlineOffset: -2,
|
|
3640
|
+
} }), _jsx("div", { title: "Resize", onMouseDown: (e) => {
|
|
3641
|
+
e.preventDefault();
|
|
3642
|
+
if (!selectedImage)
|
|
3643
|
+
return;
|
|
3644
|
+
resizingRef.current = {
|
|
3645
|
+
side: "left",
|
|
3646
|
+
startX: e.clientX,
|
|
3647
|
+
startWidth: selectedImage.getBoundingClientRect().width,
|
|
3648
|
+
};
|
|
3649
|
+
const onMove = (ev) => {
|
|
3650
|
+
const info = resizingRef.current;
|
|
3651
|
+
if (!info || !selectedImage)
|
|
3652
|
+
return;
|
|
3653
|
+
const delta = info.startX - ev.clientX;
|
|
3654
|
+
const next = Math.max(80, Math.round(info.startWidth + delta));
|
|
3655
|
+
selectedImage.style.width = next + "px";
|
|
3656
|
+
selectedImage.style.height = "auto";
|
|
3657
|
+
scheduleImageOverlay();
|
|
3658
|
+
};
|
|
3659
|
+
const onUp = () => {
|
|
3660
|
+
window.removeEventListener("mousemove", onMove);
|
|
3661
|
+
window.removeEventListener("mouseup", onUp);
|
|
3662
|
+
resizingRef.current = null;
|
|
3663
|
+
handleInput();
|
|
3664
|
+
};
|
|
3665
|
+
window.addEventListener("mousemove", onMove);
|
|
3666
|
+
window.addEventListener("mouseup", onUp);
|
|
3667
|
+
}, style: {
|
|
3668
|
+
position: "absolute",
|
|
3669
|
+
left: -4,
|
|
3670
|
+
top: "50%",
|
|
3671
|
+
transform: "translateY(-50%)",
|
|
3672
|
+
width: 8,
|
|
3673
|
+
height: 24,
|
|
3674
|
+
background: "var(--srte-accent)",
|
|
3675
|
+
borderRadius: 2,
|
|
3676
|
+
cursor: "ew-resize",
|
|
3677
|
+
pointerEvents: "auto",
|
|
3678
|
+
} }), _jsx("div", { title: "Resize", onMouseDown: (e) => {
|
|
3679
|
+
e.preventDefault();
|
|
3680
|
+
if (!selectedImage)
|
|
3681
|
+
return;
|
|
3682
|
+
resizingRef.current = {
|
|
3683
|
+
side: "right",
|
|
3684
|
+
startX: e.clientX,
|
|
3685
|
+
startWidth: selectedImage.getBoundingClientRect().width,
|
|
3686
|
+
};
|
|
3687
|
+
const onMove = (ev) => {
|
|
3688
|
+
const info = resizingRef.current;
|
|
3689
|
+
if (!info || !selectedImage)
|
|
3690
|
+
return;
|
|
3691
|
+
const delta = ev.clientX - info.startX;
|
|
3692
|
+
const next = Math.max(80, Math.round(info.startWidth + delta));
|
|
3693
|
+
selectedImage.style.width = next + "px";
|
|
3694
|
+
selectedImage.style.height = "auto";
|
|
3695
|
+
scheduleImageOverlay();
|
|
3696
|
+
};
|
|
3697
|
+
const onUp = () => {
|
|
3698
|
+
window.removeEventListener("mousemove", onMove);
|
|
3699
|
+
window.removeEventListener("mouseup", onUp);
|
|
3700
|
+
resizingRef.current = null;
|
|
3701
|
+
handleInput();
|
|
3702
|
+
};
|
|
3703
|
+
window.addEventListener("mousemove", onMove);
|
|
3704
|
+
window.addEventListener("mouseup", onUp);
|
|
3705
|
+
}, style: {
|
|
3706
|
+
position: "absolute",
|
|
3707
|
+
right: -4,
|
|
3708
|
+
top: "50%",
|
|
3709
|
+
transform: "translateY(-50%)",
|
|
3710
|
+
width: 8,
|
|
3711
|
+
height: 24,
|
|
3712
|
+
background: "var(--srte-accent)",
|
|
3713
|
+
borderRadius: 2,
|
|
3714
|
+
cursor: "ew-resize",
|
|
3715
|
+
pointerEvents: "auto",
|
|
3716
|
+
} })] }))] }), tableMenu && (_jsx("div", { style: {
|
|
3089
3717
|
position: "fixed",
|
|
3090
3718
|
inset: 0,
|
|
3091
3719
|
zIndex: 60,
|
|
3720
|
+
background: "transparent",
|
|
3092
3721
|
}, onClick: () => setTableMenu(null), onContextMenu: (e) => {
|
|
3093
3722
|
// Prevent native menu while overlay is shown and reposition our menu
|
|
3094
3723
|
e.preventDefault();
|
|
@@ -3120,56 +3749,53 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3120
3749
|
maxHeight: 260,
|
|
3121
3750
|
overflowY: "auto",
|
|
3122
3751
|
color: "var(--srte-menu-text)",
|
|
3123
|
-
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, fontSize: 11, margin: "2px 6px 6px" }, children: "Table" }), _jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsxs("
|
|
3124
|
-
display: "flex",
|
|
3125
|
-
alignItems: "center",
|
|
3126
|
-
gap: 8,
|
|
3127
|
-
padding: "6px 8px",
|
|
3128
|
-
fontSize: 12,
|
|
3129
|
-
}, onClick: () => setShowTableDialog(true), children: [_jsx("span", { children: "\u2795" }), _jsx("span", { children: "Insert table\u2026" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("div", { style: {
|
|
3752
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsx("div", { style: { fontWeight: 600, fontSize: 11, margin: "2px 6px 6px" }, children: "Table" }), _jsxs("div", { style: { display: "grid", gap: 4 }, children: [_jsxs("div", { style: {
|
|
3130
3753
|
display: "flex",
|
|
3131
3754
|
gap: 8,
|
|
3132
3755
|
alignItems: "center",
|
|
3133
3756
|
padding: "4px 6px",
|
|
3134
3757
|
fontSize: 12,
|
|
3135
3758
|
}, children: [_jsx("span", { children: "Fill:" }), _jsx("input", { type: "color", defaultValue: "#ffffff", onChange: (e) => {
|
|
3136
|
-
applyBgToSelection(e.target.value,
|
|
3137
|
-
setTableMenu(null);
|
|
3759
|
+
runTableCellAction(tableMenu.cell, (cell) => applyBgToSelection(e.target.value, cell));
|
|
3138
3760
|
}, style: {
|
|
3139
3761
|
width: 28,
|
|
3140
3762
|
height: 18,
|
|
3141
3763
|
padding: 0,
|
|
3142
3764
|
border: "none",
|
|
3143
3765
|
background: "transparent",
|
|
3144
|
-
} })] }), _jsxs("button", { style: {
|
|
3766
|
+
} })] }), _jsxs("button", { title: "Show or hide border for this cell, or for the selected cell range.", style: {
|
|
3145
3767
|
display: "flex",
|
|
3146
3768
|
alignItems: "center",
|
|
3147
3769
|
gap: 8,
|
|
3148
3770
|
padding: "6px 8px",
|
|
3149
3771
|
fontSize: 12,
|
|
3150
3772
|
}, onClick: () => {
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
}, children: [_jsx("span", { children: "\u25A6" }), _jsx("span", { children: "Toggle border" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { disabled: !canMergeSelection(), style: {
|
|
3773
|
+
runTableCellAction(tableMenu.cell, toggleBorderSelection);
|
|
3774
|
+
}, children: [_jsx("span", { children: "\u25A6" }), _jsx("span", { children: "Show/hide cell border" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { disabled: !canMergeFromCell(tableMenu.cell), style: {
|
|
3154
3775
|
display: "flex",
|
|
3155
3776
|
alignItems: "center",
|
|
3156
3777
|
gap: 8,
|
|
3157
3778
|
padding: "6px 8px",
|
|
3158
3779
|
fontSize: 12,
|
|
3159
|
-
opacity:
|
|
3160
|
-
cursor:
|
|
3780
|
+
opacity: canMergeFromCell(tableMenu.cell) ? 1 : 0.45,
|
|
3781
|
+
cursor: canMergeFromCell(tableMenu.cell) ? "pointer" : "not-allowed",
|
|
3161
3782
|
}, onClick: () => {
|
|
3783
|
+
if (!canMergeFromCell(tableMenu.cell))
|
|
3784
|
+
return;
|
|
3162
3785
|
mergeSelection();
|
|
3163
3786
|
setTableMenu(null);
|
|
3164
|
-
}, children: [_jsx("span", { children: "\u21C4" }), _jsx("span", { children: "Merge cells" })] }), _jsxs("button", { style: {
|
|
3787
|
+
}, children: [_jsx("span", { children: "\u21C4" }), _jsx("span", { children: "Merge cells" })] }), _jsxs("button", { disabled: !canSplitCell(tableMenu.cell), title: canSplitCell(tableMenu.cell) ? "Split this merged cell back into individual cells." : "Only merged cells can be split.", style: {
|
|
3165
3788
|
display: "flex",
|
|
3166
3789
|
alignItems: "center",
|
|
3167
3790
|
gap: 8,
|
|
3168
3791
|
padding: "6px 8px",
|
|
3169
3792
|
fontSize: 12,
|
|
3793
|
+
opacity: canSplitCell(tableMenu.cell) ? 1 : 0.45,
|
|
3794
|
+
cursor: canSplitCell(tableMenu.cell) ? "pointer" : "not-allowed",
|
|
3170
3795
|
}, onClick: () => {
|
|
3171
|
-
|
|
3172
|
-
|
|
3796
|
+
if (!canSplitCell(tableMenu.cell))
|
|
3797
|
+
return;
|
|
3798
|
+
runTableCellAction(tableMenu.cell, splitCell);
|
|
3173
3799
|
}, children: [_jsx("span", { children: "\u2922" }), _jsx("span", { children: "Split cell" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { style: {
|
|
3174
3800
|
display: "flex",
|
|
3175
3801
|
alignItems: "center",
|
|
@@ -3177,8 +3803,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3177
3803
|
padding: "6px 8px",
|
|
3178
3804
|
fontSize: 12,
|
|
3179
3805
|
}, onClick: () => {
|
|
3180
|
-
|
|
3181
|
-
setTableMenu(null);
|
|
3806
|
+
runTableCellAction(tableMenu.cell, (cell) => addRow(cell, "above"));
|
|
3182
3807
|
}, children: [_jsx("span", { children: "\u21A5" }), _jsx("span", { children: "Row above" })] }), _jsxs("button", { style: {
|
|
3183
3808
|
display: "flex",
|
|
3184
3809
|
alignItems: "center",
|
|
@@ -3186,8 +3811,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3186
3811
|
padding: "6px 8px",
|
|
3187
3812
|
fontSize: 12,
|
|
3188
3813
|
}, onClick: () => {
|
|
3189
|
-
|
|
3190
|
-
setTableMenu(null);
|
|
3814
|
+
runTableCellAction(tableMenu.cell, (cell) => addRow(cell, "below"));
|
|
3191
3815
|
}, children: [_jsx("span", { children: "\u21A7" }), _jsx("span", { children: "Row below" })] }), _jsxs("button", { style: {
|
|
3192
3816
|
display: "flex",
|
|
3193
3817
|
alignItems: "center",
|
|
@@ -3195,8 +3819,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3195
3819
|
padding: "6px 8px",
|
|
3196
3820
|
fontSize: 12,
|
|
3197
3821
|
}, onClick: () => {
|
|
3198
|
-
|
|
3199
|
-
setTableMenu(null);
|
|
3822
|
+
runTableCellAction(tableMenu.cell, (cell) => addCol(cell, "left"));
|
|
3200
3823
|
}, children: [_jsx("span", { children: "\u2190" }), _jsx("span", { children: "Column left" })] }), _jsxs("button", { style: {
|
|
3201
3824
|
display: "flex",
|
|
3202
3825
|
alignItems: "center",
|
|
@@ -3204,8 +3827,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3204
3827
|
padding: "6px 8px",
|
|
3205
3828
|
fontSize: 12,
|
|
3206
3829
|
}, onClick: () => {
|
|
3207
|
-
|
|
3208
|
-
setTableMenu(null);
|
|
3830
|
+
runTableCellAction(tableMenu.cell, (cell) => addCol(cell, "right"));
|
|
3209
3831
|
}, children: [_jsx("span", { children: "\u2192" }), _jsx("span", { children: "Column right" })] }), _jsxs("button", { style: {
|
|
3210
3832
|
display: "flex",
|
|
3211
3833
|
alignItems: "center",
|
|
@@ -3213,8 +3835,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3213
3835
|
padding: "6px 8px",
|
|
3214
3836
|
fontSize: 12,
|
|
3215
3837
|
}, onClick: () => {
|
|
3216
|
-
|
|
3217
|
-
setTableMenu(null);
|
|
3838
|
+
runTableCellAction(tableMenu.cell, deleteRow);
|
|
3218
3839
|
}, children: [_jsx("span", { children: "\u2716" }), _jsx("span", { children: "Delete row" })] }), _jsxs("button", { style: {
|
|
3219
3840
|
display: "flex",
|
|
3220
3841
|
alignItems: "center",
|
|
@@ -3222,8 +3843,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3222
3843
|
padding: "6px 8px",
|
|
3223
3844
|
fontSize: 12,
|
|
3224
3845
|
}, onClick: () => {
|
|
3225
|
-
|
|
3226
|
-
setTableMenu(null);
|
|
3846
|
+
runTableCellAction(tableMenu.cell, deleteCol);
|
|
3227
3847
|
}, children: [_jsx("span", { children: "\u2716" }), _jsx("span", { children: "Delete column" })] }), _jsxs("button", { style: {
|
|
3228
3848
|
display: "flex",
|
|
3229
3849
|
alignItems: "center",
|
|
@@ -3231,36 +3851,37 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
|
|
|
3231
3851
|
padding: "6px 8px",
|
|
3232
3852
|
fontSize: 12,
|
|
3233
3853
|
}, onClick: () => {
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
}, children: [_jsx("span", { children: "H" }), _jsx("span", { children: "Toggle header" })] }), _jsxs("button", { style: {
|
|
3854
|
+
runTableCellAction(tableMenu.cell, toggleHeaderCell);
|
|
3855
|
+
}, children: [_jsx("span", { children: "H" }), _jsx("span", { children: tableMenu.cell.tagName === "TH" ? "Remove cell header" : "Make cell header" })] }), _jsxs("button", { style: {
|
|
3237
3856
|
display: "flex",
|
|
3238
3857
|
alignItems: "center",
|
|
3239
3858
|
gap: 8,
|
|
3240
3859
|
padding: "6px 8px",
|
|
3241
3860
|
fontSize: 12,
|
|
3242
3861
|
}, onClick: () => {
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
}, children: [_jsx("span", { children: "H\u2081" }), _jsx("span", { children: "Toggle header row" })] }), _jsxs("button", { style: {
|
|
3862
|
+
runTableCellAction(tableMenu.cell, toggleHeaderRow);
|
|
3863
|
+
}, children: [_jsx("span", { children: "H\u2081" }), _jsx("span", { children: "Make this row header" })] }), _jsxs("button", { style: {
|
|
3246
3864
|
display: "flex",
|
|
3247
3865
|
alignItems: "center",
|
|
3248
3866
|
gap: 8,
|
|
3249
3867
|
padding: "6px 8px",
|
|
3250
3868
|
fontSize: 12,
|
|
3251
3869
|
}, onClick: () => {
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
}, children: [_jsx("span", { children: "H\u2195" }), _jsx("span", { children: "Toggle header column" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { style: {
|
|
3870
|
+
runTableCellAction(tableMenu.cell, toggleHeaderColumn);
|
|
3871
|
+
}, children: [_jsx("span", { children: "H\u2195" }), _jsx("span", { children: "Make this column header" })] }), _jsx("hr", { style: { margin: "4px 0" } }), _jsxs("button", { style: {
|
|
3255
3872
|
display: "flex",
|
|
3256
3873
|
alignItems: "center",
|
|
3257
3874
|
gap: 8,
|
|
3258
3875
|
padding: "6px 8px",
|
|
3259
3876
|
fontSize: 12,
|
|
3260
3877
|
}, onClick: () => {
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3878
|
+
runTableCellAction(tableMenu.cell, deleteTable);
|
|
3879
|
+
}, children: [_jsx("span", { children: "\uD83D\uDDD1" }), _jsx("span", { children: "Delete table" })] })] })] }) })), imageMenu && (_jsx("div", { style: {
|
|
3880
|
+
position: "fixed",
|
|
3881
|
+
inset: 0,
|
|
3882
|
+
zIndex: 60,
|
|
3883
|
+
background: "transparent",
|
|
3884
|
+
}, onClick: () => setImageMenu(null), onContextMenu: (e) => {
|
|
3264
3885
|
e.preventDefault();
|
|
3265
3886
|
const vw = window.innerWidth;
|
|
3266
3887
|
const vh = window.innerHeight;
|