autumnnote 1.0.6 → 1.0.8
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/README.md +3 -3
- package/dist/autumnnote.css +168 -4
- package/dist/autumnnote.es.js +886 -115
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +886 -115
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +3 -2
- package/src/js/Context.js +6 -0
- package/src/js/core/sanitise.js +20 -2
- package/src/js/editing/Style.js +288 -24
- package/src/js/editing/Typing.js +2 -2
- package/src/js/module/Buttons.js +5 -4
- package/src/js/module/Clipboard.js +8 -0
- package/src/js/module/CodeTooltip.js +1 -0
- package/src/js/module/ContextMenu.js +146 -8
- package/src/js/module/Editor.js +73 -1
- package/src/js/module/EmojiDialog.js +11 -0
- package/src/js/module/IconDialog.js +11 -0
- package/src/js/module/ImageCropOverlay.js +2 -2
- package/src/js/module/ImageDialog.js +22 -3
- package/src/js/module/ImageResizer.js +2 -0
- package/src/js/module/ImageTooltip.js +9 -0
- package/src/js/module/LinkTooltip.js +4 -0
- package/src/js/module/Placeholder.js +7 -1
- package/src/js/module/TableTooltip.js +399 -86
- package/src/js/module/Toolbar.js +21 -3
- package/src/js/module/VideoDialog.js +5 -5
- package/src/js/module/VideoResizer.js +11 -0
- package/src/js/module/VideoTooltip.js +1 -0
- package/src/js/renderer.js +3 -0
- package/src/styles/autumnnote.scss +194 -12
package/dist/autumnnote.umd.js
CHANGED
|
@@ -293,8 +293,24 @@
|
|
|
293
293
|
}
|
|
294
294
|
/**
|
|
295
295
|
* Strikethrough / removes strikethrough.
|
|
296
|
+
* Falls back to manual DOM manipulation inside nested formats where
|
|
297
|
+
* execCommand's state detection is unreliable (mirrors underline() logic).
|
|
296
298
|
*/
|
|
297
|
-
|
|
299
|
+
function strikethrough() {
|
|
300
|
+
const sel = window.getSelection();
|
|
301
|
+
if (!sel || !sel.rangeCount) return;
|
|
302
|
+
let sc = sel.getRangeAt(0).startContainer;
|
|
303
|
+
if (sc.nodeType === 3) sc = sc.parentElement;
|
|
304
|
+
const sEl = sc && sc.closest && (sc.closest("s") || sc.closest("strike"));
|
|
305
|
+
const nativeState = document.queryCommandState("strikeThrough");
|
|
306
|
+
if (sEl && !nativeState) {
|
|
307
|
+
const parent = sEl.parentNode;
|
|
308
|
+
while (sEl.firstChild) parent.insertBefore(sEl.firstChild, sEl);
|
|
309
|
+
parent.removeChild(sEl);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
execCommand("strikeThrough");
|
|
313
|
+
}
|
|
298
314
|
/**
|
|
299
315
|
* Superscript toggle.
|
|
300
316
|
*/
|
|
@@ -325,14 +341,48 @@
|
|
|
325
341
|
* @param {HTMLElement} [editable] - scoping element to avoid touching nodes outside this editor
|
|
326
342
|
*/
|
|
327
343
|
function fontSize(size, editable = document) {
|
|
344
|
+
const sel = window.getSelection();
|
|
345
|
+
const wasCollapsed = !sel || !sel.rangeCount || sel.getRangeAt(0).collapsed;
|
|
346
|
+
if (wasCollapsed && sel && sel.rangeCount > 0) {
|
|
347
|
+
try {
|
|
348
|
+
const range = sel.getRangeAt(0);
|
|
349
|
+
const span = document.createElement("span");
|
|
350
|
+
span.style.fontSize = size;
|
|
351
|
+
const zwsNode = document.createTextNode("");
|
|
352
|
+
span.appendChild(zwsNode);
|
|
353
|
+
range.insertNode(span);
|
|
354
|
+
const nr = document.createRange();
|
|
355
|
+
nr.setStart(zwsNode, zwsNode.textContent.length);
|
|
356
|
+
nr.collapse(true);
|
|
357
|
+
sel.removeAllRanges();
|
|
358
|
+
sel.addRange(nr);
|
|
359
|
+
} catch (_) {}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
328
362
|
execCommand("fontSize", "7");
|
|
329
|
-
editable
|
|
363
|
+
const scope = editable instanceof HTMLElement ? editable : document;
|
|
364
|
+
const newSpans = [];
|
|
365
|
+
scope.querySelectorAll("font[size=\"7\"]").forEach((el) => {
|
|
330
366
|
const span = document.createElement("span");
|
|
331
367
|
span.style.fontSize = size;
|
|
332
368
|
el.parentNode.insertBefore(span, el);
|
|
333
369
|
while (el.firstChild) span.appendChild(el.firstChild);
|
|
334
370
|
el.parentNode.removeChild(el);
|
|
371
|
+
newSpans.push(span);
|
|
335
372
|
});
|
|
373
|
+
if (!wasCollapsed && sel && newSpans.length > 0) {
|
|
374
|
+
const first = newSpans[0];
|
|
375
|
+
const last = newSpans[newSpans.length - 1];
|
|
376
|
+
try {
|
|
377
|
+
const nr = document.createRange();
|
|
378
|
+
const startNode = first.firstChild || first;
|
|
379
|
+
const endNode = last.lastChild || last;
|
|
380
|
+
nr.setStart(startNode, 0);
|
|
381
|
+
nr.setEnd(endNode, endNode.nodeType === Node.TEXT_NODE ? endNode.textContent.length : endNode.childNodes.length);
|
|
382
|
+
sel.removeAllRanges();
|
|
383
|
+
sel.addRange(nr);
|
|
384
|
+
} catch (_) {}
|
|
385
|
+
}
|
|
336
386
|
}
|
|
337
387
|
/**
|
|
338
388
|
* Wraps the selection in the given block tag (p, h1-h6, blockquote, pre).
|
|
@@ -361,8 +411,57 @@
|
|
|
361
411
|
var indent = () => execCommand("indent");
|
|
362
412
|
/**
|
|
363
413
|
* Outdents the list or block.
|
|
414
|
+
* G.5: When cursor is inside a checklist item, "outdent" means converting
|
|
415
|
+
* that item back to a regular <p> element rather than calling execCommand
|
|
416
|
+
* (which would destroy the ul > li checklist structure).
|
|
417
|
+
*/
|
|
418
|
+
function outdent() {
|
|
419
|
+
const sel = window.getSelection();
|
|
420
|
+
if (sel && sel.rangeCount) {
|
|
421
|
+
let container = sel.getRangeAt(0).commonAncestorContainer;
|
|
422
|
+
if (container.nodeType === 3) container = container.parentElement;
|
|
423
|
+
const checkLi = container && container.closest && container.closest(".an-checklist li");
|
|
424
|
+
if (checkLi) {
|
|
425
|
+
_checklistItemToP(checkLi);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
execCommand("outdent");
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* G.5 helper: splits a checklist at checkLi, converts it to a <p>,
|
|
433
|
+
* and keeps items before/after as separate checklists.
|
|
434
|
+
* @param {HTMLElement} checkLi
|
|
364
435
|
*/
|
|
365
|
-
|
|
436
|
+
function _checklistItemToP(checkLi) {
|
|
437
|
+
const checkUl = checkLi.closest(".an-checklist");
|
|
438
|
+
if (!checkUl) return;
|
|
439
|
+
const allLis = Array.from(checkUl.children);
|
|
440
|
+
const liIndex = allLis.indexOf(checkLi);
|
|
441
|
+
const afterLis = allLis.slice(liIndex + 1);
|
|
442
|
+
const p = document.createElement("p");
|
|
443
|
+
p.textContent = Array.from(checkLi.childNodes).filter((n) => !(n.nodeType === 1 && n.tagName === "INPUT")).map((n) => n.textContent).join("").replace(/\u200B/g, "").trim() || "\xA0";
|
|
444
|
+
if (afterLis.length > 0) {
|
|
445
|
+
const newUl = document.createElement("ul");
|
|
446
|
+
newUl.className = "an-checklist";
|
|
447
|
+
afterLis.forEach((li) => newUl.appendChild(li));
|
|
448
|
+
checkUl.parentNode.insertBefore(newUl, checkUl.nextSibling);
|
|
449
|
+
}
|
|
450
|
+
checkUl.parentNode.insertBefore(p, checkUl.nextSibling);
|
|
451
|
+
checkUl.removeChild(checkLi);
|
|
452
|
+
if (checkUl.children.length === 0) checkUl.parentNode.removeChild(checkUl);
|
|
453
|
+
try {
|
|
454
|
+
const nr = document.createRange();
|
|
455
|
+
const firstChild = p.firstChild;
|
|
456
|
+
nr.setStart(firstChild && firstChild.nodeType === 3 ? firstChild : p, 0);
|
|
457
|
+
nr.collapse(true);
|
|
458
|
+
const s = window.getSelection();
|
|
459
|
+
if (s) {
|
|
460
|
+
s.removeAllRanges();
|
|
461
|
+
s.addRange(nr);
|
|
462
|
+
}
|
|
463
|
+
} catch {}
|
|
464
|
+
}
|
|
366
465
|
/**
|
|
367
466
|
* Inserts an unordered list or converts selection.
|
|
368
467
|
*/
|
|
@@ -437,9 +536,24 @@
|
|
|
437
536
|
const codeEl = container && container.closest ? container.closest("code") : null;
|
|
438
537
|
if (codeEl && !codeEl.closest("pre")) {
|
|
439
538
|
const parent = codeEl.parentNode;
|
|
539
|
+
const prevSibling = codeEl.previousSibling;
|
|
540
|
+
const movedChildren = Array.from(codeEl.childNodes);
|
|
440
541
|
while (codeEl.firstChild) parent.insertBefore(codeEl.firstChild, codeEl);
|
|
441
542
|
parent.removeChild(codeEl);
|
|
442
|
-
if (
|
|
543
|
+
if (parent && parent.normalize) parent.normalize();
|
|
544
|
+
if (movedChildren.length > 0) try {
|
|
545
|
+
const firstMoved = movedChildren[0];
|
|
546
|
+
const lastMoved = movedChildren[movedChildren.length - 1];
|
|
547
|
+
const nr = document.createRange();
|
|
548
|
+
const anchorNode = firstMoved.parentNode === parent ? firstMoved : prevSibling ? prevSibling.nextSibling : parent.firstChild;
|
|
549
|
+
if (anchorNode) {
|
|
550
|
+
nr.setStart(anchorNode, 0);
|
|
551
|
+
const endAnchor = lastMoved.parentNode === parent ? lastMoved : anchorNode;
|
|
552
|
+
nr.setEnd(endAnchor, endAnchor.nodeType === Node.TEXT_NODE ? endAnchor.textContent.length : endAnchor.childNodes.length);
|
|
553
|
+
sel.removeAllRanges();
|
|
554
|
+
sel.addRange(nr);
|
|
555
|
+
}
|
|
556
|
+
} catch (_) {}
|
|
443
557
|
} else {
|
|
444
558
|
if (range.collapsed) return;
|
|
445
559
|
try {
|
|
@@ -464,14 +578,17 @@
|
|
|
464
578
|
/**
|
|
465
579
|
* Returns true when the cursor / selection is inside an inline <code>
|
|
466
580
|
* (not nested in a <pre>).
|
|
581
|
+
* Uses startContainer for reliable cross-browser detection regardless of
|
|
582
|
+
* whether the selection is collapsed or a range (commonAncestorContainer
|
|
583
|
+
* can behave inconsistently for range selections on some browsers).
|
|
467
584
|
* @returns {boolean}
|
|
468
585
|
*/
|
|
469
586
|
function isInlineCode() {
|
|
470
587
|
const sel = window.getSelection();
|
|
471
588
|
if (!sel || !sel.rangeCount) return false;
|
|
472
|
-
let
|
|
473
|
-
if (
|
|
474
|
-
const code =
|
|
589
|
+
let sc = sel.getRangeAt(0).startContainer;
|
|
590
|
+
if (sc.nodeType === 3) sc = sc.parentElement;
|
|
591
|
+
const code = sc && sc.closest ? sc.closest("code") : null;
|
|
475
592
|
return !!(code && !code.closest("pre"));
|
|
476
593
|
}
|
|
477
594
|
/**
|
|
@@ -482,7 +599,8 @@
|
|
|
482
599
|
function toggleChecklist() {
|
|
483
600
|
const sel = window.getSelection();
|
|
484
601
|
if (!sel || !sel.rangeCount) return;
|
|
485
|
-
|
|
602
|
+
const range = sel.getRangeAt(0);
|
|
603
|
+
let container = range.commonAncestorContainer;
|
|
486
604
|
if (container.nodeType === 3) container = container.parentElement;
|
|
487
605
|
const ul = container.closest && container.closest(".an-checklist");
|
|
488
606
|
if (ul) {
|
|
@@ -508,10 +626,99 @@
|
|
|
508
626
|
return;
|
|
509
627
|
}
|
|
510
628
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
629
|
+
if (range.collapsed) {
|
|
630
|
+
const BLOCK_TAGS = new Set([
|
|
631
|
+
"P",
|
|
632
|
+
"DIV",
|
|
633
|
+
"H1",
|
|
634
|
+
"H2",
|
|
635
|
+
"H3",
|
|
636
|
+
"H4",
|
|
637
|
+
"H5",
|
|
638
|
+
"H6",
|
|
639
|
+
"BLOCKQUOTE",
|
|
640
|
+
"LI"
|
|
641
|
+
]);
|
|
642
|
+
let block = container;
|
|
643
|
+
while (block && block.parentNode && !BLOCK_TAGS.has(block.tagName)) block = block.parentNode;
|
|
644
|
+
const itemText = block && BLOCK_TAGS.has(block.tagName) ? Array.from(block.childNodes).map((n) => n.textContent).join("").replace(/\u00a0/g, " ") : "";
|
|
645
|
+
const ul = document.createElement("ul");
|
|
646
|
+
ul.className = "an-checklist";
|
|
647
|
+
const li = document.createElement("li");
|
|
648
|
+
const checkbox = document.createElement("input");
|
|
649
|
+
checkbox.type = "checkbox";
|
|
650
|
+
checkbox.contentEditable = "false";
|
|
651
|
+
li.appendChild(checkbox);
|
|
652
|
+
li.appendChild(document.createTextNode(itemText || ""));
|
|
653
|
+
ul.appendChild(li);
|
|
654
|
+
if (block && BLOCK_TAGS.has(block.tagName)) block.parentNode.replaceChild(ul, block);
|
|
655
|
+
else {
|
|
656
|
+
document.execCommand("insertHTML", false, ul.outerHTML);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const textNode = li.lastChild;
|
|
660
|
+
const nr = document.createRange();
|
|
661
|
+
const offset = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent.length : 0;
|
|
662
|
+
nr.setStart(textNode, offset);
|
|
663
|
+
nr.collapse(true);
|
|
664
|
+
sel.removeAllRanges();
|
|
665
|
+
sel.addRange(nr);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (!sel.toString().replace(/[\u00a0\u200B]/g, " ").trim()) return;
|
|
669
|
+
const BLOCK_TAGS_MULTI = new Set([
|
|
670
|
+
"P",
|
|
671
|
+
"DIV",
|
|
672
|
+
"H1",
|
|
673
|
+
"H2",
|
|
674
|
+
"H3",
|
|
675
|
+
"H4",
|
|
676
|
+
"H5",
|
|
677
|
+
"H6",
|
|
678
|
+
"BLOCKQUOTE",
|
|
679
|
+
"PRE",
|
|
680
|
+
"LI"
|
|
681
|
+
]);
|
|
682
|
+
const blocks = [];
|
|
683
|
+
const seenBlocks = /* @__PURE__ */ new Set();
|
|
684
|
+
const commonAncestor = range.commonAncestorContainer;
|
|
685
|
+
const iter = document.createNodeIterator(commonAncestor.nodeType === Node.TEXT_NODE ? commonAncestor.parentNode : commonAncestor, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null);
|
|
686
|
+
let node;
|
|
687
|
+
while (node = iter.nextNode()) {
|
|
688
|
+
if (!range.intersectsNode(node)) continue;
|
|
689
|
+
let block = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
|
|
690
|
+
while (block && !BLOCK_TAGS_MULTI.has(block.tagName)) block = block.parentElement;
|
|
691
|
+
if (block && !seenBlocks.has(block)) {
|
|
692
|
+
seenBlocks.add(block);
|
|
693
|
+
blocks.push(block);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (blocks.length === 0) return;
|
|
697
|
+
const newUl = document.createElement("ul");
|
|
698
|
+
newUl.className = "an-checklist";
|
|
699
|
+
let lastTextNode = null;
|
|
700
|
+
blocks.forEach((block) => {
|
|
701
|
+
const li = document.createElement("li");
|
|
702
|
+
const cb = document.createElement("input");
|
|
703
|
+
cb.type = "checkbox";
|
|
704
|
+
cb.setAttribute("contenteditable", "false");
|
|
705
|
+
li.appendChild(cb);
|
|
706
|
+
const blockText = Array.from(block.childNodes).map((n) => n.textContent).join("").replace(/[\u00a0\u200B]/g, " ").trim();
|
|
707
|
+
const tn = document.createTextNode(blockText || "");
|
|
708
|
+
li.appendChild(tn);
|
|
709
|
+
newUl.appendChild(li);
|
|
710
|
+
lastTextNode = tn;
|
|
711
|
+
});
|
|
712
|
+
const firstBlock = blocks[0];
|
|
713
|
+
firstBlock.parentNode.insertBefore(newUl, firstBlock);
|
|
714
|
+
blocks.forEach((block) => block.parentNode && block.parentNode.removeChild(block));
|
|
715
|
+
if (lastTextNode) {
|
|
716
|
+
const nr = document.createRange();
|
|
717
|
+
nr.setStart(lastTextNode, lastTextNode.textContent.length);
|
|
718
|
+
nr.collapse(true);
|
|
719
|
+
sel.removeAllRanges();
|
|
720
|
+
sel.addRange(nr);
|
|
721
|
+
}
|
|
515
722
|
}
|
|
516
723
|
/**
|
|
517
724
|
* Returns true when the cursor is inside a checklist item.
|
|
@@ -576,9 +783,9 @@
|
|
|
576
783
|
if (document.queryCommandState("underline")) return true;
|
|
577
784
|
const sel = window.getSelection();
|
|
578
785
|
if (!sel || !sel.rangeCount) return false;
|
|
579
|
-
let
|
|
580
|
-
if (
|
|
581
|
-
return !!(
|
|
786
|
+
let sc = sel.getRangeAt(0).startContainer;
|
|
787
|
+
if (sc.nodeType === 3) sc = sc.parentElement;
|
|
788
|
+
return !!(sc && sc.closest && sc.closest("u"));
|
|
582
789
|
});
|
|
583
790
|
var strikeBtn = btn("strikethrough", "strikethrough", "Strikethrough", () => strikethrough(), () => document.queryCommandState("strikeThrough"));
|
|
584
791
|
var superscriptBtn = btn("superscript", "superscript", "Superscript", () => superscript(), () => document.queryCommandState("superscript"));
|
|
@@ -972,7 +1179,6 @@
|
|
|
972
1179
|
"object",
|
|
973
1180
|
"embed",
|
|
974
1181
|
"form",
|
|
975
|
-
"input",
|
|
976
1182
|
"button"
|
|
977
1183
|
];
|
|
978
1184
|
/** Attributes whose values must be sanitised as URLs. */
|
|
@@ -996,7 +1202,8 @@
|
|
|
996
1202
|
* Uses DOMParser so the sanitisation follows normal browser parsing rules —
|
|
997
1203
|
* no regex shortcuts that can be bypassed by encoding tricks.
|
|
998
1204
|
*
|
|
999
|
-
* - Strips PROHIBITED_TAGS (script, style, iframe, object, embed, form,
|
|
1205
|
+
* - Strips PROHIBITED_TAGS (script, style, iframe, object, embed, form, button)
|
|
1206
|
+
* - Allows input[type="checkbox"] only inside ul.an-checklist li; removes all other <input>
|
|
1000
1207
|
* - Removes all on* event-handler attributes
|
|
1001
1208
|
* - Rejects javascript: and vbscript: URLs in URL attributes
|
|
1002
1209
|
* - Rejects data: URIs everywhere except img[src] (base64 uploads)
|
|
@@ -1035,6 +1242,16 @@
|
|
|
1035
1242
|
}
|
|
1036
1243
|
});
|
|
1037
1244
|
});
|
|
1245
|
+
doc.querySelectorAll("input").forEach((el) => {
|
|
1246
|
+
if (!(el.closest("ul.an-checklist") !== null && el.closest("li") !== null) || el.getAttribute("type") !== "checkbox") el.remove();
|
|
1247
|
+
else Array.from(el.attributes).forEach((attr) => {
|
|
1248
|
+
if (![
|
|
1249
|
+
"type",
|
|
1250
|
+
"checked",
|
|
1251
|
+
"contenteditable"
|
|
1252
|
+
].includes(attr.name)) el.removeAttribute(attr.name);
|
|
1253
|
+
});
|
|
1254
|
+
});
|
|
1038
1255
|
return doc.body.innerHTML;
|
|
1039
1256
|
}
|
|
1040
1257
|
/**
|
|
@@ -1115,7 +1332,12 @@
|
|
|
1115
1332
|
if (options.maxHeight) editable.style.maxHeight = `${options.maxHeight}px`;
|
|
1116
1333
|
container.appendChild(editable);
|
|
1117
1334
|
if (options.theme === "dark") container.classList.add("an-theme-dark");
|
|
1118
|
-
if (options.readOnly)
|
|
1335
|
+
if (options.readOnly) {
|
|
1336
|
+
container.classList.add("an-disabled");
|
|
1337
|
+
editable.querySelectorAll("ul.an-checklist input[type=\"checkbox\"]").forEach((cb) => {
|
|
1338
|
+
cb.setAttribute("disabled", "");
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1119
1341
|
if (options.direction === "rtl") {
|
|
1120
1342
|
editable.setAttribute("dir", "rtl");
|
|
1121
1343
|
container.classList.add("an-dir-rtl");
|
|
@@ -1575,7 +1797,7 @@
|
|
|
1575
1797
|
const para = closestPara(range.sc, editable);
|
|
1576
1798
|
if (para && isLi(para)) {
|
|
1577
1799
|
event.preventDefault();
|
|
1578
|
-
if (event.shiftKey)
|
|
1800
|
+
if (event.shiftKey) outdent();
|
|
1579
1801
|
else execCommand("indent");
|
|
1580
1802
|
return true;
|
|
1581
1803
|
}
|
|
@@ -1994,7 +2216,42 @@
|
|
|
1994
2216
|
sel.removeAllRanges();
|
|
1995
2217
|
sel.addRange(nr);
|
|
1996
2218
|
};
|
|
1997
|
-
|
|
2219
|
+
const isReadOnly = () => this.context.layoutInfo.container.classList.contains("an-disabled");
|
|
2220
|
+
this._disposers.push(on(editable, "keydown", onKeydown), on(editable, "beforeinput", onBeforeInput), on(editable, "input", onInput), on(document, "selectionchange", onSelChange), on(editable, "click", onCheckboxClick), on(editable, "mouseup", fixChecklistCursor), on(editable, "keyup", fixChecklistCursor), on(editable, "dragstart", (e) => {
|
|
2221
|
+
if (isReadOnly()) {
|
|
2222
|
+
e.preventDefault();
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
const target = e.target;
|
|
2226
|
+
if (target && (target.nodeName === "IFRAME" || target.closest && target.closest(".an-video-wrapper"))) e.preventDefault();
|
|
2227
|
+
}), on(editable, "drop", (e) => {
|
|
2228
|
+
if (isReadOnly()) e.preventDefault();
|
|
2229
|
+
}));
|
|
2230
|
+
/** @type {string|null} 'superscript' | 'subscript' | null */
|
|
2231
|
+
let _compositionSupSub = null;
|
|
2232
|
+
const onCompositionStart = () => {
|
|
2233
|
+
const sel = window.getSelection();
|
|
2234
|
+
if (!sel || !sel.rangeCount) {
|
|
2235
|
+
_compositionSupSub = null;
|
|
2236
|
+
return;
|
|
2237
|
+
}
|
|
2238
|
+
let node = sel.getRangeAt(0).startContainer;
|
|
2239
|
+
if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
|
|
2240
|
+
if (node && node.closest) if (node.closest("sup")) _compositionSupSub = "superscript";
|
|
2241
|
+
else if (node.closest("sub")) _compositionSupSub = "subscript";
|
|
2242
|
+
else _compositionSupSub = null;
|
|
2243
|
+
};
|
|
2244
|
+
const onCompositionEnd = () => {
|
|
2245
|
+
const tag = _compositionSupSub;
|
|
2246
|
+
_compositionSupSub = null;
|
|
2247
|
+
if (!tag) return;
|
|
2248
|
+
const sel = window.getSelection();
|
|
2249
|
+
if (!sel || !sel.rangeCount) return;
|
|
2250
|
+
let node = sel.getRangeAt(0).startContainer;
|
|
2251
|
+
if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
|
|
2252
|
+
if (!(node && node.closest && (tag === "superscript" ? node.closest("sup") : node.closest("sub")))) document.execCommand(tag);
|
|
2253
|
+
};
|
|
2254
|
+
this._disposers.push(on(editable, "compositionstart", onCompositionStart), on(editable, "compositionend", onCompositionEnd));
|
|
1998
2255
|
}
|
|
1999
2256
|
_onKeydown(event) {
|
|
2000
2257
|
const editable = this.context.layoutInfo.editable;
|
|
@@ -2080,6 +2337,7 @@
|
|
|
2080
2337
|
}
|
|
2081
2338
|
}
|
|
2082
2339
|
afterCommand() {
|
|
2340
|
+
this._cleanOrphanedFigures();
|
|
2083
2341
|
this.context.invoke("toolbar.refresh");
|
|
2084
2342
|
this.context.invoke("statusbar.update");
|
|
2085
2343
|
this._scheduleSnapshot();
|
|
@@ -2096,6 +2354,16 @@
|
|
|
2096
2354
|
this.context.triggerEvent("change", this.getHTML());
|
|
2097
2355
|
}, 400);
|
|
2098
2356
|
}
|
|
2357
|
+
/**
|
|
2358
|
+
* C4: Removes figure.an-figure elements that no longer contain an <img>.
|
|
2359
|
+
* This happens when a user selects only the image (not the whole figure)
|
|
2360
|
+
* and deletes or replaces it, leaving a dangling figcaption.
|
|
2361
|
+
*/
|
|
2362
|
+
_cleanOrphanedFigures() {
|
|
2363
|
+
this.context.layoutInfo.editable.querySelectorAll("figure.an-figure").forEach((fig) => {
|
|
2364
|
+
if (!fig.querySelector("img")) fig.parentNode.removeChild(fig);
|
|
2365
|
+
});
|
|
2366
|
+
}
|
|
2099
2367
|
focus() {
|
|
2100
2368
|
this.context.layoutInfo.editable.focus();
|
|
2101
2369
|
}
|
|
@@ -2112,7 +2380,7 @@
|
|
|
2112
2380
|
* @param {string} html - HTML string (will be sanitised)
|
|
2113
2381
|
*/
|
|
2114
2382
|
setHTML(html) {
|
|
2115
|
-
this.context.layoutInfo.editable.innerHTML = sanitiseHTML(html);
|
|
2383
|
+
this.context.layoutInfo.editable.innerHTML = sanitiseHTML(html, { allowIframes: true });
|
|
2116
2384
|
if (this._history) this._history.reset();
|
|
2117
2385
|
this.afterCommand();
|
|
2118
2386
|
}
|
|
@@ -2827,15 +3095,28 @@
|
|
|
2827
3095
|
if (def.name === "fontFamily" && !isHeader) opt.style.fontFamily = value;
|
|
2828
3096
|
select.appendChild(opt);
|
|
2829
3097
|
});
|
|
3098
|
+
/** @type {Range|null} */
|
|
3099
|
+
let _savedRange = null;
|
|
3100
|
+
const dMousedown = on(select, "mousedown", () => {
|
|
3101
|
+
const sel = window.getSelection();
|
|
3102
|
+
_savedRange = sel && sel.rangeCount ? sel.getRangeAt(0).cloneRange() : null;
|
|
3103
|
+
});
|
|
2830
3104
|
const disposer = on(select, "change", (e) => {
|
|
2831
3105
|
const value = e.target.value;
|
|
2832
3106
|
const selectedOpt = e.target.options[e.target.selectedIndex];
|
|
2833
3107
|
if (!value || selectedOpt.disabled) return;
|
|
2834
3108
|
this.context.invoke("editor.focus");
|
|
3109
|
+
if (_savedRange) try {
|
|
3110
|
+
const sel = window.getSelection();
|
|
3111
|
+
if (sel) {
|
|
3112
|
+
sel.removeAllRanges();
|
|
3113
|
+
sel.addRange(_savedRange);
|
|
3114
|
+
}
|
|
3115
|
+
} catch (_) {}
|
|
2835
3116
|
def.action(this.context, value);
|
|
2836
3117
|
this.context.invoke("editor.afterCommand");
|
|
2837
3118
|
});
|
|
2838
|
-
this._disposers.push(disposer);
|
|
3119
|
+
this._disposers.push(dMousedown, disposer);
|
|
2839
3120
|
return select;
|
|
2840
3121
|
}
|
|
2841
3122
|
/**
|
|
@@ -3292,9 +3573,25 @@
|
|
|
3292
3573
|
this.options.onImageUpload(files);
|
|
3293
3574
|
return;
|
|
3294
3575
|
}
|
|
3576
|
+
const UNSUPPORTED = [
|
|
3577
|
+
"image/tiff",
|
|
3578
|
+
"image/x-tiff",
|
|
3579
|
+
"image/bmp",
|
|
3580
|
+
"image/x-bmp",
|
|
3581
|
+
"image/x-ms-bmp"
|
|
3582
|
+
];
|
|
3295
3583
|
const maxBytes = (this.options.maxImageSize || 5) * 1024 * 1024;
|
|
3296
3584
|
files.forEach((file) => {
|
|
3297
3585
|
if (!file || !file.type.startsWith("image/")) return;
|
|
3586
|
+
if (UNSUPPORTED.includes(file.type)) {
|
|
3587
|
+
const message = `Image format "${file.type}" is not supported for display in web browsers. Please convert to PNG, JPEG, or WebP first.`;
|
|
3588
|
+
this.context.triggerEvent("imageError", {
|
|
3589
|
+
file,
|
|
3590
|
+
message
|
|
3591
|
+
});
|
|
3592
|
+
console.warn("[AutumnNote]", message);
|
|
3593
|
+
return;
|
|
3594
|
+
}
|
|
3298
3595
|
if (file.size > maxBytes) {
|
|
3299
3596
|
const message = `Image "${file.name}" exceeds the ${this.options.maxImageSize || 5} MB size limit.`;
|
|
3300
3597
|
this.context.triggerEvent("imageError", {
|
|
@@ -3458,7 +3755,7 @@
|
|
|
3458
3755
|
_update() {
|
|
3459
3756
|
const editable = this.context.layoutInfo.editable;
|
|
3460
3757
|
const isFocused = document.activeElement === editable;
|
|
3461
|
-
const isEmpty = !editable.textContent.trim() && !editable.querySelector("img, table, hr");
|
|
3758
|
+
const isEmpty = !(editable.textContent.replace(/\u200B/g, "").trim().length > 0) && !editable.querySelector("img, table, hr, .an-video-wrapper");
|
|
3462
3759
|
editable.classList.toggle("an-placeholder", isEmpty && !isFocused);
|
|
3463
3760
|
}
|
|
3464
3761
|
};
|
|
@@ -3893,12 +4190,14 @@
|
|
|
3893
4190
|
const fileInput = createElement("input", {
|
|
3894
4191
|
type: "file",
|
|
3895
4192
|
class: "an-input",
|
|
3896
|
-
accept: "image
|
|
4193
|
+
accept: "image/jpeg,image/png,image/gif,image/webp,image/svg+xml,image/avif"
|
|
3897
4194
|
});
|
|
3898
4195
|
this._fileInput = fileInput;
|
|
4196
|
+
const fileHint = createElement("p", { class: "an-dialog-hint" });
|
|
4197
|
+
this._fileHint = fileHint;
|
|
3899
4198
|
const d = on(fileInput, "change", () => this._onFileChange());
|
|
3900
4199
|
this._disposers.push(d);
|
|
3901
|
-
box.append(fileLabel, fileInput);
|
|
4200
|
+
box.append(fileLabel, fileInput, fileHint);
|
|
3902
4201
|
}
|
|
3903
4202
|
const btnRow = createElement("div", { class: "an-dialog-actions" });
|
|
3904
4203
|
const insertBtn = createElement("button", {
|
|
@@ -3938,9 +4237,28 @@
|
|
|
3938
4237
|
_onFileChange() {
|
|
3939
4238
|
const file = this._fileInput && this._fileInput.files && this._fileInput.files[0];
|
|
3940
4239
|
if (!file || !file.type.startsWith("image/")) return;
|
|
4240
|
+
if (!new Set([
|
|
4241
|
+
"image/jpeg",
|
|
4242
|
+
"image/png",
|
|
4243
|
+
"image/gif",
|
|
4244
|
+
"image/webp",
|
|
4245
|
+
"image/svg+xml",
|
|
4246
|
+
"image/avif"
|
|
4247
|
+
]).has(file.type)) {
|
|
4248
|
+
const message = `Format "${file.type}" is not supported for display in web browsers. Please convert to JPEG, PNG, or WebP first.`;
|
|
4249
|
+
if (this._fileHint) this._fileHint.textContent = message;
|
|
4250
|
+
this.context.triggerEvent("imageError", {
|
|
4251
|
+
file,
|
|
4252
|
+
message
|
|
4253
|
+
});
|
|
4254
|
+
this._fileInput.value = "";
|
|
4255
|
+
return;
|
|
4256
|
+
}
|
|
4257
|
+
if (this._fileHint) this._fileHint.textContent = "";
|
|
3941
4258
|
const maxSize = (this.options.maxImageSize || 5) * 1024 * 1024;
|
|
3942
4259
|
if (file.size > maxSize) {
|
|
3943
4260
|
const message = `Image file is too large. Maximum allowed size is ${this.options.maxImageSize || 5} MB.`;
|
|
4261
|
+
if (this._fileHint) this._fileHint.textContent = message;
|
|
3944
4262
|
console.warn("[AutumnNote] ImageDialog:", message);
|
|
3945
4263
|
this.context.triggerEvent("imageError", {
|
|
3946
4264
|
file,
|
|
@@ -4252,6 +4570,7 @@
|
|
|
4252
4570
|
_resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
|
|
4253
4571
|
};
|
|
4254
4572
|
this._disposers.push(on(editable, "click", (e) => this._onEditorClick(e)), on(editable, "contextmenu", (e) => {
|
|
4573
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
4255
4574
|
const img = e.target.closest("img");
|
|
4256
4575
|
if (img) this._select(img);
|
|
4257
4576
|
}), on(document, "click", (e) => this._onDocClick(e)), on(window, "scroll", () => this._updateOverlayPosition(), { passive: true }), on(window, "resize", onWindowResize, { passive: true }), on(editable, "scroll", () => this._updateOverlayPosition(), { passive: true }));
|
|
@@ -4302,6 +4621,7 @@
|
|
|
4302
4621
|
return overlay;
|
|
4303
4622
|
}
|
|
4304
4623
|
_onEditorClick(e) {
|
|
4624
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
4305
4625
|
const img = e.target.closest("img");
|
|
4306
4626
|
if (img) {
|
|
4307
4627
|
e.preventDefault();
|
|
@@ -4458,9 +4778,12 @@
|
|
|
4458
4778
|
_resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
|
|
4459
4779
|
};
|
|
4460
4780
|
this._disposers.push(on(editable, "click", (e) => this._onEditorClick(e)), on(editable, "contextmenu", (e) => {
|
|
4781
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
4461
4782
|
const wrapper = this._findWrapper(e.target);
|
|
4462
4783
|
if (wrapper) this._select(wrapper);
|
|
4463
|
-
}), on(document, "click", (e) => this._onDocClick(e)), on(window, "scroll", () => this._updateOverlayPosition(), { passive: true }), on(window, "resize", onWindowResize), on(editable, "scroll", () => this._updateOverlayPosition(), { passive: true }))
|
|
4784
|
+
}), on(document, "click", (e) => this._onDocClick(e)), on(window, "scroll", () => this._updateOverlayPosition(), { passive: true }), on(window, "resize", onWindowResize), on(editable, "scroll", () => this._updateOverlayPosition(), { passive: true }), on(editable, "dragstart", (e) => {
|
|
4785
|
+
if (e.target instanceof Element && e.target.closest(".an-video-wrapper")) e.preventDefault();
|
|
4786
|
+
}));
|
|
4464
4787
|
return this;
|
|
4465
4788
|
}
|
|
4466
4789
|
destroy() {
|
|
@@ -4519,6 +4842,7 @@
|
|
|
4519
4842
|
return overlay;
|
|
4520
4843
|
}
|
|
4521
4844
|
_onEditorClick(e) {
|
|
4845
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
4522
4846
|
const wrapper = this._findWrapper(e.target);
|
|
4523
4847
|
if (wrapper) {
|
|
4524
4848
|
e.preventDefault();
|
|
@@ -4717,9 +5041,12 @@
|
|
|
4717
5041
|
}, HIDE_DELAY$4);
|
|
4718
5042
|
}
|
|
4719
5043
|
_show(anchor) {
|
|
5044
|
+
const isReadOnly = this.context.layoutInfo.container.classList.contains("an-disabled");
|
|
4720
5045
|
const url = anchor.getAttribute("href") || "";
|
|
4721
5046
|
this._urlLabel.textContent = this._truncateUrl(url);
|
|
4722
5047
|
this._urlLabel.title = url;
|
|
5048
|
+
this._editBtn.style.display = isReadOnly ? "none" : "";
|
|
5049
|
+
this._unlinkBtn.style.display = isReadOnly ? "none" : "";
|
|
4723
5050
|
this._el.style.display = "flex";
|
|
4724
5051
|
this._positionNear(anchor);
|
|
4725
5052
|
}
|
|
@@ -4833,6 +5160,7 @@
|
|
|
4833
5160
|
document.body.appendChild(this._el);
|
|
4834
5161
|
const editable = this.context.layoutInfo.editable;
|
|
4835
5162
|
this._disposers.push(on(editable, "mouseover", (e) => {
|
|
5163
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
4836
5164
|
const img = e.target.closest("img");
|
|
4837
5165
|
if (img && editable.contains(img) && !img.closest("a[href]")) this._scheduleShow(img);
|
|
4838
5166
|
}, { passive: true }), on(editable, "mouseout", (e) => {
|
|
@@ -4959,6 +5287,7 @@
|
|
|
4959
5287
|
const target = img.closest("figure.an-figure") || img;
|
|
4960
5288
|
target.style.float = value;
|
|
4961
5289
|
target.style.display = "";
|
|
5290
|
+
if (target !== img) target.style.width = "";
|
|
4962
5291
|
target.style.marginLeft = value === "right" ? "12px" : "";
|
|
4963
5292
|
target.style.marginRight = value === "left" ? "12px" : "";
|
|
4964
5293
|
this.context.invoke("editor.afterCommand");
|
|
@@ -4973,6 +5302,7 @@
|
|
|
4973
5302
|
const target = img.closest("figure.an-figure") || img;
|
|
4974
5303
|
target.style.float = "";
|
|
4975
5304
|
target.style.display = "block";
|
|
5305
|
+
if (target !== img) target.style.width = "fit-content";
|
|
4976
5306
|
target.style.marginLeft = "auto";
|
|
4977
5307
|
target.style.marginRight = "auto";
|
|
4978
5308
|
this.context.invoke("editor.afterCommand");
|
|
@@ -5057,6 +5387,7 @@
|
|
|
5057
5387
|
img.style.marginRight = "";
|
|
5058
5388
|
} else if (img.style.display === "block" && img.style.marginLeft === "auto") {
|
|
5059
5389
|
figure.style.display = "block";
|
|
5390
|
+
figure.style.width = "fit-content";
|
|
5060
5391
|
figure.style.marginLeft = "auto";
|
|
5061
5392
|
figure.style.marginRight = "auto";
|
|
5062
5393
|
img.style.display = "";
|
|
@@ -5111,6 +5442,7 @@
|
|
|
5111
5442
|
document.body.appendChild(this._el);
|
|
5112
5443
|
const editable = this.context.layoutInfo.editable;
|
|
5113
5444
|
this._disposers.push(on(editable, "mouseover", (e) => {
|
|
5445
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
5114
5446
|
const wrapper = e.target.closest(".an-video-wrapper");
|
|
5115
5447
|
if (wrapper && editable.contains(wrapper)) this._scheduleShow(wrapper);
|
|
5116
5448
|
}, { passive: true }), on(editable, "mouseout", (e) => {
|
|
@@ -5367,6 +5699,47 @@
|
|
|
5367
5699
|
}
|
|
5368
5700
|
return null;
|
|
5369
5701
|
}
|
|
5702
|
+
/**
|
|
5703
|
+
* Build a 2D grid map of the table, accounting for both rowspan and colspan.
|
|
5704
|
+
*
|
|
5705
|
+
* gridMap[r][c] = the DOM cell occupying visual grid position (r, c).
|
|
5706
|
+
* cellPos = WeakMap: cell → { r, c, rs, cs } (top-left grid origin + span).
|
|
5707
|
+
*
|
|
5708
|
+
* Uses HTMLTableElement.rows which is scoped to the table itself and never
|
|
5709
|
+
* includes rows from nested tables.
|
|
5710
|
+
*
|
|
5711
|
+
* @param {HTMLTableElement} table
|
|
5712
|
+
* @returns {{ gridMap: Object, cellPos: WeakMap }}
|
|
5713
|
+
*/
|
|
5714
|
+
function buildGridMap(table) {
|
|
5715
|
+
const rows = Array.from(table.rows);
|
|
5716
|
+
const gridMap = {};
|
|
5717
|
+
const cellPos = /* @__PURE__ */ new WeakMap();
|
|
5718
|
+
rows.forEach((row, r) => {
|
|
5719
|
+
if (!gridMap[r]) gridMap[r] = {};
|
|
5720
|
+
let c = 0;
|
|
5721
|
+
for (const cell of row.cells) {
|
|
5722
|
+
while (gridMap[r][c]) c++;
|
|
5723
|
+
const rs = cell.rowSpan || 1;
|
|
5724
|
+
const cs = cell.colSpan || 1;
|
|
5725
|
+
cellPos.set(cell, {
|
|
5726
|
+
r,
|
|
5727
|
+
c,
|
|
5728
|
+
rs,
|
|
5729
|
+
cs
|
|
5730
|
+
});
|
|
5731
|
+
for (let dr = 0; dr < rs; dr++) {
|
|
5732
|
+
if (!gridMap[r + dr]) gridMap[r + dr] = {};
|
|
5733
|
+
for (let dc = 0; dc < cs; dc++) gridMap[r + dr][c + dc] = cell;
|
|
5734
|
+
}
|
|
5735
|
+
c += cs;
|
|
5736
|
+
}
|
|
5737
|
+
});
|
|
5738
|
+
return {
|
|
5739
|
+
gridMap,
|
|
5740
|
+
cellPos
|
|
5741
|
+
};
|
|
5742
|
+
}
|
|
5370
5743
|
var ICONS$2 = {
|
|
5371
5744
|
rowAbove: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="12" x2="21" y2="12"/><path d="M12 3v7"/><path d="M9 7l3-4 3 4"/></svg>`,
|
|
5372
5745
|
rowBelow: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="12" x2="21" y2="12"/><path d="M12 12v7"/><path d="M9 17l3 4 3-4"/></svg>`,
|
|
@@ -5375,10 +5748,12 @@
|
|
|
5375
5748
|
colRight: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="12" y1="3" x2="12" y2="21"/><path d="M12 12h9"/><path d="M17 8l4 4-4 4"/></svg>`,
|
|
5376
5749
|
deleteCol: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="12" y1="3" x2="12" y2="21"/><line x1="15" y1="6" x2="21" y2="12"/><line x1="21" y1="6" x2="15" y2="12"/></svg>`,
|
|
5377
5750
|
mergeCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="8" height="10" rx="1"/><rect x="14" y="7" width="8" height="10" rx="1"/><path d="M10 12h4"/><path d="M12 10l2 2-2 2"/></svg>`,
|
|
5751
|
+
unmergeCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="5" width="20" height="14" rx="1"/><line x1="12" y1="5" x2="12" y2="19" stroke-dasharray="2.5 2"/><line x1="2" y1="12" x2="22" y2="12" stroke-dasharray="2.5 2"/><path d="M9 9 L6 12 L9 15"/><path d="M15 9 L18 12 L15 15"/></svg>`,
|
|
5378
5752
|
colWidth: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="7" y1="4" x2="7" y2="20"/><line x1="17" y1="4" x2="17" y2="20"/><line x1="7" y1="12" x2="17" y2="12"/><path d="M10 9l-3 3 3 3"/><path d="M14 9l3 3-3 3"/></svg>`,
|
|
5379
5753
|
rowHeight: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="17" x2="20" y2="17"/><line x1="12" y1="7" x2="12" y2="17"/><path d="M9 10l3-3 3 3"/><path d="M9 14l3 3 3-3"/></svg>`,
|
|
5380
5754
|
tableBorder: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6" stroke-width="1"/><line x1="3" y1="13" x2="21" y2="13" stroke-width="2"/><line x1="3" y1="20" x2="21" y2="20" stroke-width="3"/></svg>`,
|
|
5381
|
-
deleteTable: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><line x1="16" y1="16" x2="22" y2="22" stroke="#ef4444"/><line x1="22" y1="16" x2="16" y2="22" stroke="#ef4444"/></svg
|
|
5755
|
+
deleteTable: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><line x1="16" y1="16" x2="22" y2="22" stroke="#ef4444"/><line x1="22" y1="16" x2="16" y2="22" stroke="#ef4444"/></svg>`,
|
|
5756
|
+
selectCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z" fill="currentColor" opacity="0.15"/><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z"/></svg>`
|
|
5382
5757
|
};
|
|
5383
5758
|
var TableTooltip = class {
|
|
5384
5759
|
/** @param {import('../Context.js').Context} context */
|
|
@@ -5394,6 +5769,12 @@
|
|
|
5394
5769
|
this._sizeApply = null;
|
|
5395
5770
|
this._sizeTitleEl = null;
|
|
5396
5771
|
this._sizeInputEl = null;
|
|
5772
|
+
this._selectMode = false;
|
|
5773
|
+
this._selectedCells = [];
|
|
5774
|
+
this._selectStart = null;
|
|
5775
|
+
this._selectDragging = false;
|
|
5776
|
+
this._selectBtn = null;
|
|
5777
|
+
this._editable = null;
|
|
5397
5778
|
}
|
|
5398
5779
|
initialize() {
|
|
5399
5780
|
this._el = this._buildTooltip();
|
|
@@ -5401,7 +5782,31 @@
|
|
|
5401
5782
|
this._sizePopover = this._buildSizePopover();
|
|
5402
5783
|
document.body.appendChild(this._sizePopover);
|
|
5403
5784
|
const editable = this.context.layoutInfo.editable;
|
|
5785
|
+
this._editable = editable;
|
|
5786
|
+
const onSelMousedown = (e) => {
|
|
5787
|
+
if (!this._selectMode) return;
|
|
5788
|
+
const cell = e.target.closest("td, th");
|
|
5789
|
+
if (!cell || !editable.contains(cell)) return;
|
|
5790
|
+
if (cell.style.cursor === "col-resize" || cell.style.cursor === "row-resize") return;
|
|
5791
|
+
e.preventDefault();
|
|
5792
|
+
this._activeTable = cell.closest("table");
|
|
5793
|
+
this._selectStart = cell;
|
|
5794
|
+
this._selectDragging = true;
|
|
5795
|
+
this._setSelection([cell]);
|
|
5796
|
+
};
|
|
5797
|
+
const onSelMousemove = (e) => {
|
|
5798
|
+
if (!this._selectMode || !this._selectDragging || !this._selectStart) return;
|
|
5799
|
+
const cell = e.target.closest("td, th");
|
|
5800
|
+
if (!cell || !editable.contains(cell)) return;
|
|
5801
|
+
if (cell.closest("table") !== this._activeTable) return;
|
|
5802
|
+
this._setSelection(this._getRectCells(this._selectStart, cell));
|
|
5803
|
+
};
|
|
5804
|
+
const onSelMouseup = () => {
|
|
5805
|
+
this._selectDragging = false;
|
|
5806
|
+
};
|
|
5807
|
+
this._disposers.push(on(editable, "mousedown", onSelMousedown), on(editable, "mousemove", onSelMousemove), on(document, "mouseup", onSelMouseup));
|
|
5404
5808
|
this._disposers.push(on(editable, "mouseover", (e) => {
|
|
5809
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
5405
5810
|
const table = e.target.closest("table");
|
|
5406
5811
|
if (table && editable.contains(table)) {
|
|
5407
5812
|
const cell = e.target.closest("td, th");
|
|
@@ -5409,9 +5814,11 @@
|
|
|
5409
5814
|
this._scheduleShow(table);
|
|
5410
5815
|
}
|
|
5411
5816
|
}, { passive: true }), on(editable, "mouseout", (e) => {
|
|
5817
|
+
if (this._selectMode) return;
|
|
5412
5818
|
const to = e.relatedTarget;
|
|
5413
5819
|
if (!to || !editable.contains(to) && !this._el.contains(to) && !(this._sizePopover && this._sizePopover.contains(to))) this._scheduleHide();
|
|
5414
5820
|
}, { passive: true }), on(document, "click", (e) => {
|
|
5821
|
+
if (this._selectMode && this._activeTable && this._activeTable.contains(e.target)) return;
|
|
5415
5822
|
if (this._activeTable && !this._activeTable.contains(e.target) && !this._el.contains(e.target) && !(this._sizePopover && this._sizePopover.contains(e.target))) this._hide();
|
|
5416
5823
|
}));
|
|
5417
5824
|
this._initResize();
|
|
@@ -5443,6 +5850,10 @@
|
|
|
5443
5850
|
};
|
|
5444
5851
|
const onEditorMove = (e) => {
|
|
5445
5852
|
if (_resizing) return;
|
|
5853
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) {
|
|
5854
|
+
clearHover();
|
|
5855
|
+
return;
|
|
5856
|
+
}
|
|
5446
5857
|
if (_rafEditorMove !== null) return;
|
|
5447
5858
|
const target = e.target;
|
|
5448
5859
|
const clientX = e.clientX;
|
|
@@ -5470,6 +5881,7 @@
|
|
|
5470
5881
|
});
|
|
5471
5882
|
};
|
|
5472
5883
|
const onEditorDown = (e) => {
|
|
5884
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
5473
5885
|
if (!_nearCell || !_nearEdge) return;
|
|
5474
5886
|
_resizing = true;
|
|
5475
5887
|
_edge = _nearEdge;
|
|
@@ -5479,7 +5891,7 @@
|
|
|
5479
5891
|
if (_edge === "col") {
|
|
5480
5892
|
_startW = _nearCell.offsetWidth;
|
|
5481
5893
|
_colIdx = getVisualColIndex(_nearCell);
|
|
5482
|
-
_colCells = _colIdx >= 0 ? Array.from(_table.querySelectorAll("tr")).map((r) => getCellAtVisualCol(r, _colIdx)).filter(Boolean) : [];
|
|
5894
|
+
_colCells = _colIdx >= 0 ? Array.from(_table.querySelectorAll("tr")).map((r) => getCellAtVisualCol(r, _colIdx)).filter(Boolean).filter((c) => (c.colSpan || 1) === 1) : [];
|
|
5483
5895
|
document.body.style.cursor = "col-resize";
|
|
5484
5896
|
} else {
|
|
5485
5897
|
_row = _nearCell.closest("tr");
|
|
@@ -5550,6 +5962,9 @@
|
|
|
5550
5962
|
this._label.textContent = "Table";
|
|
5551
5963
|
el.appendChild(this._label);
|
|
5552
5964
|
el.appendChild(this._sep());
|
|
5965
|
+
this._selectBtn = this._makeBtn(ICONS$2.selectCells, "Select Cells", () => this._toggleSelectMode());
|
|
5966
|
+
el.appendChild(this._selectBtn);
|
|
5967
|
+
el.appendChild(this._sep());
|
|
5553
5968
|
el.appendChild(this._makeBtn(ICONS$2.rowAbove, "Add Row Above", () => this._addRow("above")));
|
|
5554
5969
|
el.appendChild(this._makeBtn(ICONS$2.rowBelow, "Add Row Below", () => this._addRow("below")));
|
|
5555
5970
|
el.appendChild(this._makeBtn(ICONS$2.deleteRow, "Delete Row", () => this._deleteRow()));
|
|
@@ -5559,6 +5974,7 @@
|
|
|
5559
5974
|
el.appendChild(this._makeBtn(ICONS$2.deleteCol, "Delete Column", () => this._deleteColumn()));
|
|
5560
5975
|
el.appendChild(this._sep());
|
|
5561
5976
|
el.appendChild(this._makeBtn(ICONS$2.mergeCells, "Merge Cells", () => this._mergeCells()));
|
|
5977
|
+
el.appendChild(this._makeBtn(ICONS$2.unmergeCells, "Unmerge Cells", () => this._unmergeCells()));
|
|
5562
5978
|
el.appendChild(this._sep());
|
|
5563
5979
|
el.appendChild(this._makeBtn(ICONS$2.colWidth, "Column Width", () => this._openSizePopover("col")));
|
|
5564
5980
|
el.appendChild(this._makeBtn(ICONS$2.rowHeight, "Row Height", () => this._openSizePopover("row")));
|
|
@@ -5566,6 +5982,7 @@
|
|
|
5566
5982
|
el.appendChild(this._sep());
|
|
5567
5983
|
el.appendChild(this._makeBtn(ICONS$2.deleteTable, "Delete Table", () => this._deleteTable(), true));
|
|
5568
5984
|
this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => {
|
|
5985
|
+
if (this._selectMode) return;
|
|
5569
5986
|
if (this._sizePopover && this._sizePopover.style.display !== "none") return;
|
|
5570
5987
|
this._scheduleHide();
|
|
5571
5988
|
}));
|
|
@@ -5621,6 +6038,12 @@
|
|
|
5621
6038
|
this._el.style.display = "none";
|
|
5622
6039
|
this._activeTable = null;
|
|
5623
6040
|
this._activeCell = null;
|
|
6041
|
+
if (this._selectMode) {
|
|
6042
|
+
this._selectMode = false;
|
|
6043
|
+
if (this._selectBtn) this._selectBtn.classList.remove("an-link-tooltip-btn--active");
|
|
6044
|
+
if (this._editable) this._editable.classList.remove("an-table-select-mode");
|
|
6045
|
+
}
|
|
6046
|
+
this._clearSelection();
|
|
5624
6047
|
this._clearTimers();
|
|
5625
6048
|
this._hideSizePopover();
|
|
5626
6049
|
}
|
|
@@ -5654,14 +6077,111 @@
|
|
|
5654
6077
|
}
|
|
5655
6078
|
return this._activeCell || this._activeTable && this._activeTable.querySelector("td, th");
|
|
5656
6079
|
}
|
|
6080
|
+
_toggleSelectMode() {
|
|
6081
|
+
this._selectMode = !this._selectMode;
|
|
6082
|
+
if (this._selectBtn) this._selectBtn.classList.toggle("an-link-tooltip-btn--active", this._selectMode);
|
|
6083
|
+
if (this._editable) this._editable.classList.toggle("an-table-select-mode", this._selectMode);
|
|
6084
|
+
if (!this._selectMode) this._clearSelection();
|
|
6085
|
+
}
|
|
6086
|
+
_clearSelection() {
|
|
6087
|
+
this._selectedCells.forEach((c) => c.classList.remove("an-cell-selected"));
|
|
6088
|
+
this._selectedCells = [];
|
|
6089
|
+
this._selectStart = null;
|
|
6090
|
+
}
|
|
6091
|
+
_setSelection(cells) {
|
|
6092
|
+
this._selectedCells.forEach((c) => {
|
|
6093
|
+
if (!cells.includes(c)) c.classList.remove("an-cell-selected");
|
|
6094
|
+
});
|
|
6095
|
+
this._selectedCells = cells;
|
|
6096
|
+
cells.forEach((c) => c.classList.add("an-cell-selected"));
|
|
6097
|
+
}
|
|
6098
|
+
/**
|
|
6099
|
+
* Returns all cells in the rectangular area between startCell and endCell,
|
|
6100
|
+
* correctly handling rowspan/colspan by using the grid map.
|
|
6101
|
+
* The rect is expanded iteratively until it is stable — this ensures any
|
|
6102
|
+
* merged cell that starts outside the initial rect but spans into it is
|
|
6103
|
+
* fully included.
|
|
6104
|
+
*/
|
|
6105
|
+
_getRectCells(startCell, endCell) {
|
|
6106
|
+
if (!startCell) return [];
|
|
6107
|
+
if (!endCell || startCell === endCell) return [startCell];
|
|
6108
|
+
const table = startCell.closest("table");
|
|
6109
|
+
if (!table || !table.contains(endCell)) return [startCell];
|
|
6110
|
+
const { gridMap, cellPos } = buildGridMap(table);
|
|
6111
|
+
const sp = cellPos.get(startCell);
|
|
6112
|
+
const ep = cellPos.get(endCell);
|
|
6113
|
+
if (!sp || !ep) return [startCell];
|
|
6114
|
+
let minR = Math.min(sp.r, ep.r);
|
|
6115
|
+
let maxR = Math.max(sp.r + sp.rs - 1, ep.r + ep.rs - 1);
|
|
6116
|
+
let minC = Math.min(sp.c, ep.c);
|
|
6117
|
+
let maxC = Math.max(sp.c + sp.cs - 1, ep.c + ep.cs - 1);
|
|
6118
|
+
let changed = true;
|
|
6119
|
+
while (changed) {
|
|
6120
|
+
changed = false;
|
|
6121
|
+
for (let r = minR; r <= maxR; r++) {
|
|
6122
|
+
const rowMap = gridMap[r];
|
|
6123
|
+
if (!rowMap) continue;
|
|
6124
|
+
for (let c = minC; c <= maxC; c++) {
|
|
6125
|
+
const cell = rowMap[c];
|
|
6126
|
+
if (!cell) continue;
|
|
6127
|
+
const pos = cellPos.get(cell);
|
|
6128
|
+
if (!pos) continue;
|
|
6129
|
+
if (pos.r < minR) {
|
|
6130
|
+
minR = pos.r;
|
|
6131
|
+
changed = true;
|
|
6132
|
+
}
|
|
6133
|
+
if (pos.r + pos.rs - 1 > maxR) {
|
|
6134
|
+
maxR = pos.r + pos.rs - 1;
|
|
6135
|
+
changed = true;
|
|
6136
|
+
}
|
|
6137
|
+
if (pos.c < minC) {
|
|
6138
|
+
minC = pos.c;
|
|
6139
|
+
changed = true;
|
|
6140
|
+
}
|
|
6141
|
+
if (pos.c + pos.cs - 1 > maxC) {
|
|
6142
|
+
maxC = pos.c + pos.cs - 1;
|
|
6143
|
+
changed = true;
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6149
|
+
const result = [];
|
|
6150
|
+
for (let r = minR; r <= maxR; r++) {
|
|
6151
|
+
const rowMap = gridMap[r];
|
|
6152
|
+
if (!rowMap) continue;
|
|
6153
|
+
for (let c = minC; c <= maxC; c++) {
|
|
6154
|
+
const cell = rowMap[c];
|
|
6155
|
+
if (cell && !seen.has(cell)) {
|
|
6156
|
+
seen.add(cell);
|
|
6157
|
+
result.push(cell);
|
|
6158
|
+
}
|
|
6159
|
+
}
|
|
6160
|
+
}
|
|
6161
|
+
return result.length > 0 ? result : [startCell];
|
|
6162
|
+
}
|
|
6163
|
+
/**
|
|
6164
|
+
* Returns the active cell set: user-selected cells when available,
|
|
6165
|
+
* otherwise the single active/cursor cell.
|
|
6166
|
+
* @returns {HTMLTableCellElement[]}
|
|
6167
|
+
*/
|
|
6168
|
+
_getSelectedCells() {
|
|
6169
|
+
return this._selectedCells.length > 0 ? this._selectedCells : [this._getCell()].filter(Boolean);
|
|
6170
|
+
}
|
|
5657
6171
|
_addRow(position) {
|
|
5658
|
-
const
|
|
5659
|
-
if (!
|
|
5660
|
-
const
|
|
5661
|
-
if (!
|
|
5662
|
-
const
|
|
6172
|
+
const cells = this._getSelectedCells();
|
|
6173
|
+
if (!cells.length) return;
|
|
6174
|
+
const table = cells[0].closest("table");
|
|
6175
|
+
if (!table) return;
|
|
6176
|
+
const allRows = Array.from(table.querySelectorAll("tr"));
|
|
6177
|
+
const refRow = [...new Set(cells.map((c) => c.closest("tr")).filter(Boolean))].reduce((best, r) => {
|
|
6178
|
+
const bi = allRows.indexOf(best);
|
|
6179
|
+
const ri = allRows.indexOf(r);
|
|
6180
|
+
return position === "above" ? ri < bi ? r : best : ri > bi ? r : best;
|
|
6181
|
+
});
|
|
6182
|
+
const colCount = Array.from(refRow.cells).reduce((sum, c) => sum + (c.colSpan || 1), 0);
|
|
5663
6183
|
const newRow = document.createElement("tr");
|
|
5664
|
-
const refCells = Array.from(
|
|
6184
|
+
const refCells = Array.from(refRow.cells);
|
|
5665
6185
|
for (let i = 0; i < colCount; i++) {
|
|
5666
6186
|
const td = createElement("td", {}, ["\xA0"]);
|
|
5667
6187
|
const ref = refCells[i];
|
|
@@ -5669,19 +6189,20 @@
|
|
|
5669
6189
|
if (ref && ref.style.minWidth) td.style.minWidth = ref.style.minWidth;
|
|
5670
6190
|
newRow.appendChild(td);
|
|
5671
6191
|
}
|
|
5672
|
-
if (position === "above")
|
|
5673
|
-
else
|
|
6192
|
+
if (position === "above") refRow.parentElement?.insertBefore(newRow, refRow);
|
|
6193
|
+
else refRow.insertAdjacentElement("afterend", newRow);
|
|
5674
6194
|
requestAnimationFrame(() => this._positionNear(this._activeTable));
|
|
5675
6195
|
this.context.invoke("editor.afterCommand");
|
|
5676
6196
|
}
|
|
5677
6197
|
_addColumn(position) {
|
|
5678
|
-
const
|
|
5679
|
-
if (!
|
|
5680
|
-
const table =
|
|
6198
|
+
const cells = this._getSelectedCells();
|
|
6199
|
+
if (!cells.length) return;
|
|
6200
|
+
const table = cells[0].closest("table");
|
|
5681
6201
|
if (!table) return;
|
|
5682
|
-
const
|
|
6202
|
+
const colIndices = cells.map((c) => getVisualColIndex(c));
|
|
6203
|
+
const targetColIdx = position === "left" ? Math.min(...colIndices) : Math.max(...colIndices);
|
|
5683
6204
|
const rows = Array.from(table.querySelectorAll("tr"));
|
|
5684
|
-
const refs = rows.map((r) => position === "left" ? getCellAtVisualCol(r,
|
|
6205
|
+
const refs = rows.map((r) => position === "left" ? getCellAtVisualCol(r, targetColIdx) : getCellAfterVisualCol(r, targetColIdx));
|
|
5685
6206
|
const isHeaders = rows.map((r) => r.closest("thead") !== null);
|
|
5686
6207
|
rows.forEach((r, i) => {
|
|
5687
6208
|
r.insertBefore(createElement(isHeaders[i] ? "th" : "td", {}, ["\xA0"]), refs[i]);
|
|
@@ -5690,68 +6211,93 @@
|
|
|
5690
6211
|
this.context.invoke("editor.afterCommand");
|
|
5691
6212
|
}
|
|
5692
6213
|
_deleteRow() {
|
|
5693
|
-
const
|
|
5694
|
-
if (!
|
|
5695
|
-
const
|
|
5696
|
-
|
|
5697
|
-
if (!row || !table) return;
|
|
6214
|
+
const cells = this._getSelectedCells();
|
|
6215
|
+
if (!cells.length) return;
|
|
6216
|
+
const table = cells[0].closest("table");
|
|
6217
|
+
if (!table) return;
|
|
5698
6218
|
const tbody = table.querySelector("tbody");
|
|
5699
|
-
|
|
6219
|
+
const totalBodyRows = tbody ? tbody.querySelectorAll("tr").length : table.querySelectorAll("tr").length;
|
|
6220
|
+
const selectedRows = [...new Set(cells.map((c) => c.closest("tr")).filter(Boolean))];
|
|
6221
|
+
if (selectedRows.filter((r) => r.closest("tbody")).length >= totalBodyRows) return;
|
|
5700
6222
|
this._activeCell = null;
|
|
5701
|
-
|
|
6223
|
+
this._clearSelection();
|
|
6224
|
+
selectedRows.forEach((r) => r.parentElement?.removeChild(r));
|
|
5702
6225
|
requestAnimationFrame(() => this._positionNear(this._activeTable));
|
|
5703
6226
|
this.context.invoke("editor.afterCommand");
|
|
5704
6227
|
}
|
|
5705
6228
|
_deleteColumn() {
|
|
5706
|
-
const
|
|
5707
|
-
if (!
|
|
5708
|
-
const table =
|
|
6229
|
+
const cells = this._getSelectedCells();
|
|
6230
|
+
if (!cells.length) return;
|
|
6231
|
+
const table = cells[0].closest("table");
|
|
5709
6232
|
if (!table) return;
|
|
5710
|
-
const
|
|
5711
|
-
if (
|
|
5712
|
-
const
|
|
5713
|
-
|
|
5714
|
-
const
|
|
5715
|
-
|
|
5716
|
-
|
|
6233
|
+
const tableRows = Array.from(table.querySelectorAll("tr"));
|
|
6234
|
+
if (tableRows[0] && tableRows[0].cells.length <= 1) return;
|
|
6235
|
+
const colIndices = [...new Set(cells.map((c) => getVisualColIndex(c)))];
|
|
6236
|
+
if (colIndices.length >= (tableRows[0]?.cells.length ?? 1)) return;
|
|
6237
|
+
const cellsToDelete = [];
|
|
6238
|
+
colIndices.forEach((colIdx) => {
|
|
6239
|
+
tableRows.forEach((r) => {
|
|
6240
|
+
const c = getCellAtVisualCol(r, colIdx);
|
|
6241
|
+
if (c) cellsToDelete.push(c);
|
|
6242
|
+
});
|
|
5717
6243
|
});
|
|
6244
|
+
this._activeCell = null;
|
|
6245
|
+
this._clearSelection();
|
|
6246
|
+
cellsToDelete.forEach((c) => c.parentElement?.removeChild(c));
|
|
5718
6247
|
requestAnimationFrame(() => this._positionNear(this._activeTable));
|
|
5719
6248
|
this.context.invoke("editor.afterCommand");
|
|
5720
6249
|
}
|
|
5721
6250
|
_mergeCells() {
|
|
5722
6251
|
const cell = this._getCell();
|
|
5723
6252
|
if (!cell) return;
|
|
5724
|
-
const sel = window.getSelection();
|
|
5725
|
-
if (!sel || sel.rangeCount === 0) return;
|
|
5726
|
-
const range = sel.getRangeAt(0);
|
|
5727
6253
|
const table = cell.closest("table");
|
|
5728
6254
|
if (!table) return;
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
const rowSelected = Array.from(row.cells).filter((c) => selected.includes(c));
|
|
5741
|
-
if (rowSelected.length < 2) return;
|
|
5742
|
-
const first = rowSelected[0];
|
|
5743
|
-
first.colSpan = rowSelected.reduce((sum, c) => sum + (c.colSpan || 1), 0);
|
|
5744
|
-
first.innerHTML = rowSelected.map((c) => c.innerHTML).join("");
|
|
5745
|
-
rowSelected.slice(1).forEach((c) => row.removeChild(c));
|
|
5746
|
-
} else {
|
|
5747
|
-
if ([...new Set(selected.map((c) => getVisualColIndex(c)))].length !== 1) return;
|
|
5748
|
-
const first = selected[0];
|
|
5749
|
-
first.rowSpan = selected.reduce((sum, c) => sum + (c.rowSpan || 1), 0);
|
|
5750
|
-
first.innerHTML = selected.map((c) => c.innerHTML).join("");
|
|
5751
|
-
selected.slice(1).forEach((c) => {
|
|
5752
|
-
if (c.closest("tr")) c.closest("tr").removeChild(c);
|
|
6255
|
+
let selected = this._getSelectedCells().filter((c) => table.contains(c));
|
|
6256
|
+
if (selected.length < 2) {
|
|
6257
|
+
const sel = window.getSelection();
|
|
6258
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
6259
|
+
const range = sel.getRangeAt(0);
|
|
6260
|
+
selected = Array.from(table.querySelectorAll("td, th")).filter((c) => {
|
|
6261
|
+
try {
|
|
6262
|
+
return range.intersectsNode(c);
|
|
6263
|
+
} catch {
|
|
6264
|
+
return false;
|
|
6265
|
+
}
|
|
5753
6266
|
});
|
|
6267
|
+
if (selected.length < 2) return;
|
|
6268
|
+
}
|
|
6269
|
+
const { gridMap, cellPos } = buildGridMap(table);
|
|
6270
|
+
let minR = Infinity, maxR = -Infinity, minC = Infinity, maxC = -Infinity;
|
|
6271
|
+
selected.forEach((c) => {
|
|
6272
|
+
const pos = cellPos.get(c);
|
|
6273
|
+
if (!pos) return;
|
|
6274
|
+
if (pos.r < minR) minR = pos.r;
|
|
6275
|
+
if (pos.r + pos.rs - 1 > maxR) maxR = pos.r + pos.rs - 1;
|
|
6276
|
+
if (pos.c < minC) minC = pos.c;
|
|
6277
|
+
if (pos.c + pos.cs - 1 > maxC) maxC = pos.c + pos.cs - 1;
|
|
6278
|
+
});
|
|
6279
|
+
if (minR === Infinity) return;
|
|
6280
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6281
|
+
const rectCells = [];
|
|
6282
|
+
for (let r = minR; r <= maxR; r++) {
|
|
6283
|
+
const rowMap = gridMap[r];
|
|
6284
|
+
if (!rowMap) continue;
|
|
6285
|
+
for (let c = minC; c <= maxC; c++) {
|
|
6286
|
+
const tc = rowMap[c];
|
|
6287
|
+
if (tc && !seen.has(tc)) {
|
|
6288
|
+
seen.add(tc);
|
|
6289
|
+
rectCells.push(tc);
|
|
6290
|
+
}
|
|
6291
|
+
}
|
|
5754
6292
|
}
|
|
6293
|
+
if (rectCells.length < 2) return;
|
|
6294
|
+
const first = rectCells[0];
|
|
6295
|
+
first.colSpan = maxC - minC + 1;
|
|
6296
|
+
first.rowSpan = maxR - minR + 1;
|
|
6297
|
+
first.style.verticalAlign = "middle";
|
|
6298
|
+
first.innerHTML = rectCells.map((c) => c.innerHTML).join("");
|
|
6299
|
+
rectCells.slice(1).forEach((c) => c.parentElement?.removeChild(c));
|
|
6300
|
+
this._clearSelection();
|
|
5755
6301
|
this.context.invoke("editor.afterCommand");
|
|
5756
6302
|
}
|
|
5757
6303
|
_deleteTable() {
|
|
@@ -5761,6 +6307,57 @@
|
|
|
5761
6307
|
if (table.parentNode) table.parentNode.removeChild(table);
|
|
5762
6308
|
this.context.invoke("editor.afterCommand");
|
|
5763
6309
|
}
|
|
6310
|
+
_unmergeCells() {
|
|
6311
|
+
const cells = this._getSelectedCells();
|
|
6312
|
+
if (!cells.length) return;
|
|
6313
|
+
const table = cells[0].closest("table");
|
|
6314
|
+
if (!table) return;
|
|
6315
|
+
const mergedCells = cells.filter((c) => table.contains(c) && ((c.colSpan || 1) > 1 || (c.rowSpan || 1) > 1));
|
|
6316
|
+
if (!mergedCells.length) return;
|
|
6317
|
+
mergedCells.forEach((cell) => {
|
|
6318
|
+
if (table.contains(cell)) this._unmergeOne(cell, table);
|
|
6319
|
+
});
|
|
6320
|
+
this._clearSelection();
|
|
6321
|
+
requestAnimationFrame(() => this._positionNear(this._activeTable));
|
|
6322
|
+
this.context.invoke("editor.afterCommand");
|
|
6323
|
+
}
|
|
6324
|
+
/**
|
|
6325
|
+
* Split a single merged cell (colspan/rowspan > 1) back into individual cells.
|
|
6326
|
+
* New cells are empty ( ); the original cell retains its content.
|
|
6327
|
+
* @param {HTMLTableCellElement} cell
|
|
6328
|
+
* @param {HTMLTableElement} table
|
|
6329
|
+
*/
|
|
6330
|
+
_unmergeOne(cell, table) {
|
|
6331
|
+
const cs = cell.colSpan || 1;
|
|
6332
|
+
const rs = cell.rowSpan || 1;
|
|
6333
|
+
if (cs === 1 && rs === 1) return;
|
|
6334
|
+
const { cellPos } = buildGridMap(table);
|
|
6335
|
+
const pos = cellPos.get(cell);
|
|
6336
|
+
if (!pos) return;
|
|
6337
|
+
const { r, c } = pos;
|
|
6338
|
+
const tableRows = Array.from(table.rows);
|
|
6339
|
+
const tag = cell.tagName.toLowerCase();
|
|
6340
|
+
cell.rowSpan = 1;
|
|
6341
|
+
cell.colSpan = 1;
|
|
6342
|
+
cell.style.verticalAlign = "";
|
|
6343
|
+
if (cs > 1) {
|
|
6344
|
+
const insertRef = cell.nextElementSibling;
|
|
6345
|
+
for (let dc = 1; dc < cs; dc++) tableRows[r].insertBefore(createElement(tag, {}, ["\xA0"]), insertRef);
|
|
6346
|
+
}
|
|
6347
|
+
for (let dr = 1; dr < rs; dr++) {
|
|
6348
|
+
const targetRow = tableRows[r + dr];
|
|
6349
|
+
if (!targetRow) continue;
|
|
6350
|
+
let ref = null;
|
|
6351
|
+
for (const tc of targetRow.cells) {
|
|
6352
|
+
const tp = cellPos.get(tc);
|
|
6353
|
+
if (tp && tp.c > c) {
|
|
6354
|
+
ref = tc;
|
|
6355
|
+
break;
|
|
6356
|
+
}
|
|
6357
|
+
}
|
|
6358
|
+
for (let dc = 0; dc < cs; dc++) targetRow.insertBefore(createElement(tag, {}, ["\xA0"]), ref);
|
|
6359
|
+
}
|
|
6360
|
+
}
|
|
5764
6361
|
_buildSizePopover() {
|
|
5765
6362
|
const popover = createElement("div", { class: "an-size-popover" });
|
|
5766
6363
|
popover.style.display = "none";
|
|
@@ -5842,27 +6439,35 @@
|
|
|
5842
6439
|
};
|
|
5843
6440
|
} else {
|
|
5844
6441
|
const isCol = type === "col";
|
|
6442
|
+
const activeCells = this._getSelectedCells().filter((c) => {
|
|
6443
|
+
const t = c.closest("table");
|
|
6444
|
+
return t && t === cell.closest("table");
|
|
6445
|
+
});
|
|
5845
6446
|
this._sizeTitleEl.textContent = isCol ? "Column Width (px)" : "Row Height (px)";
|
|
5846
6447
|
this._sizeInputEl.min = "1";
|
|
5847
6448
|
this._sizeInputEl.max = "2000";
|
|
5848
6449
|
this._sizeInputEl.value = isCol ? cell.offsetWidth || 120 : cell.closest("tr") ? cell.closest("tr").offsetHeight || 40 : 40;
|
|
5849
6450
|
this._sizeApply = (val) => {
|
|
6451
|
+
const table = cell.closest("table");
|
|
6452
|
+
if (!table) return;
|
|
5850
6453
|
if (isCol) {
|
|
5851
|
-
const
|
|
5852
|
-
const
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
c
|
|
5856
|
-
c.
|
|
5857
|
-
|
|
6454
|
+
const colIndices = [...new Set(activeCells.map((c) => getVisualColIndex(c)))];
|
|
6455
|
+
const tableRows = Array.from(table.querySelectorAll("tr"));
|
|
6456
|
+
colIndices.forEach((colIdx) => {
|
|
6457
|
+
tableRows.forEach((r) => {
|
|
6458
|
+
const c = getCellAtVisualCol(r, colIdx);
|
|
6459
|
+
if (c && (c.colSpan || 1) === 1) {
|
|
6460
|
+
c.style.width = `${val}px`;
|
|
6461
|
+
c.style.minWidth = `${val}px`;
|
|
6462
|
+
}
|
|
6463
|
+
});
|
|
5858
6464
|
});
|
|
5859
|
-
} else {
|
|
5860
|
-
const
|
|
5861
|
-
if (row) for (const c of row.cells) {
|
|
6465
|
+
} else [...new Set(activeCells.map((c) => c.closest("tr")).filter(Boolean))].forEach((row) => {
|
|
6466
|
+
for (const c of row.cells) {
|
|
5862
6467
|
c.style.height = `${val}px`;
|
|
5863
6468
|
c.style.minHeight = `${val}px`;
|
|
5864
6469
|
}
|
|
5865
|
-
}
|
|
6470
|
+
});
|
|
5866
6471
|
this.context.invoke("editor.afterCommand");
|
|
5867
6472
|
};
|
|
5868
6473
|
}
|
|
@@ -5919,6 +6524,7 @@
|
|
|
5919
6524
|
this._ensurePrism();
|
|
5920
6525
|
const editable = this.context.layoutInfo.editable;
|
|
5921
6526
|
this._disposers.push(on(editable, "mouseover", (e) => {
|
|
6527
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
5922
6528
|
const pre = e.target.closest("pre");
|
|
5923
6529
|
if (pre && editable.contains(pre)) this._scheduleShow(pre);
|
|
5924
6530
|
}), on(editable, "mouseout", (e) => {
|
|
@@ -8716,7 +9322,13 @@
|
|
|
8716
9322
|
range.selectNodeContents(editable);
|
|
8717
9323
|
range.collapse(false);
|
|
8718
9324
|
}
|
|
9325
|
+
const _sc = range.startContainer;
|
|
9326
|
+
const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest?.("td, th");
|
|
8719
9327
|
range.deleteContents();
|
|
9328
|
+
if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
|
|
9329
|
+
range.setStart(_tdAnchor, 0);
|
|
9330
|
+
range.collapse(true);
|
|
9331
|
+
}
|
|
8720
9332
|
const textNode = document.createTextNode(char);
|
|
8721
9333
|
range.insertNode(textNode);
|
|
8722
9334
|
range.setStartAfter(textNode);
|
|
@@ -9285,7 +9897,13 @@
|
|
|
9285
9897
|
range.selectNodeContents(editable);
|
|
9286
9898
|
range.collapse(false);
|
|
9287
9899
|
}
|
|
9900
|
+
const _sc = range.startContainer;
|
|
9901
|
+
const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest?.("td, th");
|
|
9288
9902
|
range.deleteContents();
|
|
9903
|
+
if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
|
|
9904
|
+
range.setStart(_tdAnchor, 0);
|
|
9905
|
+
range.collapse(true);
|
|
9906
|
+
}
|
|
9289
9907
|
range.insertNode(iconEl);
|
|
9290
9908
|
let caretTextNode = iconEl.nextSibling;
|
|
9291
9909
|
if (!caretTextNode || caretTextNode.nodeType !== Node.TEXT_NODE) {
|
|
@@ -9349,22 +9967,50 @@
|
|
|
9349
9967
|
copyFormat: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>`,
|
|
9350
9968
|
pasteFormat: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><rect x="8" y="2" width="8" height="4" rx="1"/><path d="m9 14 2 2 4-4"/></svg>`,
|
|
9351
9969
|
removeFormat: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21"/><path d="M22 21H7"/><path d="m5 11 9 9"/></svg>`,
|
|
9352
|
-
table: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg
|
|
9970
|
+
table: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/></svg>`,
|
|
9971
|
+
textColor: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20L12 4L20 20"/><line x1="7.5" y1="14" x2="16.5" y2="14"/></svg>`,
|
|
9972
|
+
highlightColor: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21v-4l9-9 4 4-9 9z"/><path d="M12 8l4 4"/><line x1="3" y1="21" x2="21" y2="21"/></svg>`,
|
|
9973
|
+
noColor: `<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="4" y1="4" x2="20" y2="20"/><line x1="20" y1="4" x2="4" y2="20"/></svg>`,
|
|
9974
|
+
back: `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>`
|
|
9353
9975
|
};
|
|
9976
|
+
var COLOR_PRESETS = [
|
|
9977
|
+
"#000000",
|
|
9978
|
+
"#434343",
|
|
9979
|
+
"#666666",
|
|
9980
|
+
"#999999",
|
|
9981
|
+
"#b7b7b7",
|
|
9982
|
+
"#cccccc",
|
|
9983
|
+
"#efefef",
|
|
9984
|
+
"#ffffff",
|
|
9985
|
+
"#ff0000",
|
|
9986
|
+
"#ff9900",
|
|
9987
|
+
"#ffff00",
|
|
9988
|
+
"#00ff00",
|
|
9989
|
+
"#00ffff",
|
|
9990
|
+
"#4a86e8",
|
|
9991
|
+
"#9900ff",
|
|
9992
|
+
"#ff00ff",
|
|
9993
|
+
"#f4cccc",
|
|
9994
|
+
"#fce5cd",
|
|
9995
|
+
"#fff2cc",
|
|
9996
|
+
"#d9ead3",
|
|
9997
|
+
"#d0e0e3",
|
|
9998
|
+
"#c9daf8",
|
|
9999
|
+
"#d9d2e9",
|
|
10000
|
+
"#ead1dc"
|
|
10001
|
+
];
|
|
10002
|
+
function makeColorSubItems(colorType) {
|
|
10003
|
+
const label = colorType === "foreColor" ? "Text Color" : "Highlight Color";
|
|
10004
|
+
return () => [{
|
|
10005
|
+
back: true,
|
|
10006
|
+
label,
|
|
10007
|
+
navigate: () => defaultItems
|
|
10008
|
+
}, {
|
|
10009
|
+
colorPalette: true,
|
|
10010
|
+
colorType
|
|
10011
|
+
}];
|
|
10012
|
+
}
|
|
9354
10013
|
var defaultItems = [
|
|
9355
|
-
{
|
|
9356
|
-
name: "undo",
|
|
9357
|
-
label: "Undo",
|
|
9358
|
-
icon: ICONS.undo,
|
|
9359
|
-
action: (ctx) => ctx.invoke("editor.undo")
|
|
9360
|
-
},
|
|
9361
|
-
{
|
|
9362
|
-
name: "redo",
|
|
9363
|
-
label: "Redo",
|
|
9364
|
-
icon: ICONS.redo,
|
|
9365
|
-
action: (ctx) => ctx.invoke("editor.redo")
|
|
9366
|
-
},
|
|
9367
|
-
{ separator: true },
|
|
9368
10014
|
{
|
|
9369
10015
|
name: "cut",
|
|
9370
10016
|
label: "Cut",
|
|
@@ -9425,6 +10071,21 @@
|
|
|
9425
10071
|
action: (ctx) => ctx.invoke("editor.underline")
|
|
9426
10072
|
},
|
|
9427
10073
|
{ separator: true },
|
|
10074
|
+
{
|
|
10075
|
+
name: "textColor",
|
|
10076
|
+
label: "Text Color",
|
|
10077
|
+
icon: ICONS.textColor,
|
|
10078
|
+
colorStrip: "foreColor",
|
|
10079
|
+
navigate: makeColorSubItems("foreColor")
|
|
10080
|
+
},
|
|
10081
|
+
{
|
|
10082
|
+
name: "highlightColor",
|
|
10083
|
+
label: "Highlight Color",
|
|
10084
|
+
icon: ICONS.highlightColor,
|
|
10085
|
+
colorStrip: "hiliteColor",
|
|
10086
|
+
navigate: makeColorSubItems("hiliteColor")
|
|
10087
|
+
},
|
|
10088
|
+
{ separator: true },
|
|
9428
10089
|
{
|
|
9429
10090
|
name: "copyFormat",
|
|
9430
10091
|
label: "Copy Format",
|
|
@@ -9542,8 +10203,10 @@
|
|
|
9542
10203
|
backBtn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || "Back"]));
|
|
9543
10204
|
const off = on(backBtn, "click", (e) => {
|
|
9544
10205
|
e.stopPropagation();
|
|
10206
|
+
const curLeft = parseFloat(this.el.style.left);
|
|
10207
|
+
const curTop = parseFloat(this.el.style.top);
|
|
9545
10208
|
this._renderItems(it.navigate());
|
|
9546
|
-
this._reposition();
|
|
10209
|
+
this._reposition(curLeft, curTop);
|
|
9547
10210
|
});
|
|
9548
10211
|
this._menuDisposers.push(off);
|
|
9549
10212
|
this.el.appendChild(backBtn);
|
|
@@ -9555,7 +10218,19 @@
|
|
|
9555
10218
|
class: "an-context-item an-context-submenu",
|
|
9556
10219
|
"data-name": it.name || ""
|
|
9557
10220
|
});
|
|
9558
|
-
if (it.icon) {
|
|
10221
|
+
if (it.icon) if (it.colorStrip) {
|
|
10222
|
+
const iconWrap = createElement("span", {
|
|
10223
|
+
class: "an-context-icon an-context-icon--color",
|
|
10224
|
+
"aria-hidden": "true"
|
|
10225
|
+
});
|
|
10226
|
+
const svgSpan = createElement("span", { class: "an-context-icon-svg" });
|
|
10227
|
+
svgSpan.innerHTML = it.icon;
|
|
10228
|
+
const strip = createElement("span", { class: "an-context-color-strip" });
|
|
10229
|
+
strip.style.background = this._getSelectionColor(it.colorStrip);
|
|
10230
|
+
iconWrap.appendChild(svgSpan);
|
|
10231
|
+
iconWrap.appendChild(strip);
|
|
10232
|
+
btn.appendChild(iconWrap);
|
|
10233
|
+
} else {
|
|
9559
10234
|
const iconSpan = createElement("span", {
|
|
9560
10235
|
class: "an-context-icon",
|
|
9561
10236
|
"aria-hidden": "true"
|
|
@@ -9572,13 +10247,63 @@
|
|
|
9572
10247
|
btn.appendChild(chevron);
|
|
9573
10248
|
const off = on(btn, "click", (e) => {
|
|
9574
10249
|
e.stopPropagation();
|
|
10250
|
+
const curLeft = parseFloat(this.el.style.left);
|
|
10251
|
+
const curTop = parseFloat(this.el.style.top);
|
|
9575
10252
|
this._renderItems(it.navigate());
|
|
9576
|
-
this._reposition();
|
|
10253
|
+
this._reposition(curLeft, curTop);
|
|
9577
10254
|
});
|
|
9578
10255
|
this._menuDisposers.push(off);
|
|
9579
10256
|
this.el.appendChild(btn);
|
|
9580
10257
|
return;
|
|
9581
10258
|
}
|
|
10259
|
+
if (it.colorPalette) {
|
|
10260
|
+
const palette = createElement("div", { class: "an-context-color-palette" });
|
|
10261
|
+
COLOR_PRESETS.forEach((color) => {
|
|
10262
|
+
const sw = createElement("div", {
|
|
10263
|
+
class: "an-context-color-swatch",
|
|
10264
|
+
title: color,
|
|
10265
|
+
role: "button",
|
|
10266
|
+
"aria-label": color
|
|
10267
|
+
});
|
|
10268
|
+
sw.style.background = color;
|
|
10269
|
+
const offSw = on(sw, "click", (e) => {
|
|
10270
|
+
e.stopPropagation();
|
|
10271
|
+
this._applyColor(it.colorType, color);
|
|
10272
|
+
});
|
|
10273
|
+
this._menuDisposers.push(offSw);
|
|
10274
|
+
palette.appendChild(sw);
|
|
10275
|
+
});
|
|
10276
|
+
if (it.colorType === "hiliteColor") {
|
|
10277
|
+
const noColor = createElement("div", {
|
|
10278
|
+
class: "an-context-color-swatch an-context-color-none",
|
|
10279
|
+
title: "No highlight",
|
|
10280
|
+
role: "button",
|
|
10281
|
+
"aria-label": "No highlight"
|
|
10282
|
+
});
|
|
10283
|
+
noColor.innerHTML = ICONS.noColor;
|
|
10284
|
+
const offNo = on(noColor, "click", (e) => {
|
|
10285
|
+
e.stopPropagation();
|
|
10286
|
+
this._applyColor("hiliteColor", "transparent");
|
|
10287
|
+
});
|
|
10288
|
+
this._menuDisposers.push(offNo);
|
|
10289
|
+
palette.appendChild(noColor);
|
|
10290
|
+
}
|
|
10291
|
+
this.el.appendChild(palette);
|
|
10292
|
+
const customRow = createElement("div", { class: "an-context-color-custom" });
|
|
10293
|
+
const colorInput = createElement("input", {
|
|
10294
|
+
type: "color",
|
|
10295
|
+
value: it.colorType === "foreColor" ? "#000000" : "#ffff00",
|
|
10296
|
+
title: "Custom color",
|
|
10297
|
+
"aria-label": "Custom color"
|
|
10298
|
+
});
|
|
10299
|
+
const customLabel = createElement("span", {}, ["Custom…"]);
|
|
10300
|
+
const offCustom = on(colorInput, "change", () => this._applyColor(it.colorType, colorInput.value));
|
|
10301
|
+
this._menuDisposers.push(offCustom);
|
|
10302
|
+
customRow.appendChild(colorInput);
|
|
10303
|
+
customRow.appendChild(customLabel);
|
|
10304
|
+
this.el.appendChild(customRow);
|
|
10305
|
+
return;
|
|
10306
|
+
}
|
|
9582
10307
|
if (it.tableGrid) {
|
|
9583
10308
|
const GRID_ROWS = 8, GRID_COLS = 8;
|
|
9584
10309
|
const wrapper = createElement("div", { class: "an-context-table-wrap" });
|
|
@@ -9696,6 +10421,7 @@
|
|
|
9696
10421
|
const editable = this.context.layoutInfo && this.context.layoutInfo.editable;
|
|
9697
10422
|
if (!editable) return;
|
|
9698
10423
|
if (!editable.contains(event.target)) return;
|
|
10424
|
+
if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
|
|
9699
10425
|
event.preventDefault();
|
|
9700
10426
|
this._lastX = event.clientX;
|
|
9701
10427
|
this._lastY = event.clientY;
|
|
@@ -9722,7 +10448,19 @@
|
|
|
9722
10448
|
let left = rx;
|
|
9723
10449
|
let top = ry;
|
|
9724
10450
|
if (left + rect.width > window.innerWidth) left = window.innerWidth - rect.width - 8;
|
|
10451
|
+
if (left < 8) left = 8;
|
|
9725
10452
|
if (top + rect.height > window.innerHeight) top = window.innerHeight - rect.height - 8;
|
|
10453
|
+
if (top < 8) top = 8;
|
|
10454
|
+
if (this._savedRange) try {
|
|
10455
|
+
const sel = this._savedRange.getBoundingClientRect();
|
|
10456
|
+
if (sel.width > 0 || sel.height > 0) {
|
|
10457
|
+
if (top < sel.bottom && top + rect.height > sel.top && left < sel.right && left + rect.width > sel.left) {
|
|
10458
|
+
const belowTop = sel.bottom + 6;
|
|
10459
|
+
if (belowTop + rect.height <= window.innerHeight - 8) top = belowTop;
|
|
10460
|
+
else top = Math.max(8, sel.top - rect.height - 6);
|
|
10461
|
+
}
|
|
10462
|
+
}
|
|
10463
|
+
} catch (_) {}
|
|
9726
10464
|
this.el.style.left = `${left}px`;
|
|
9727
10465
|
this.el.style.top = `${top}px`;
|
|
9728
10466
|
}
|
|
@@ -9731,6 +10469,33 @@
|
|
|
9731
10469
|
this.el.style.display = "none";
|
|
9732
10470
|
this.el.setAttribute("aria-hidden", "true");
|
|
9733
10471
|
}
|
|
10472
|
+
/** Read the current selection's text or highlight color for the strip.
|
|
10473
|
+
* @param {'foreColor'|'hiliteColor'} type
|
|
10474
|
+
* @returns {string} CSS color string
|
|
10475
|
+
*/
|
|
10476
|
+
_getSelectionColor(type) {
|
|
10477
|
+
const range = this._savedRange;
|
|
10478
|
+
if (!range) return type === "foreColor" ? "#000000" : "transparent";
|
|
10479
|
+
let node = range.startContainer;
|
|
10480
|
+
if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
|
|
10481
|
+
if (!node) return type === "foreColor" ? "#000000" : "transparent";
|
|
10482
|
+
const cs = window.getComputedStyle(node);
|
|
10483
|
+
if (type === "foreColor") return cs.color || "#000000";
|
|
10484
|
+
const bg = cs.backgroundColor;
|
|
10485
|
+
return !bg || bg === "rgba(0, 0, 0, 0)" || bg === "transparent" ? "transparent" : bg;
|
|
10486
|
+
}
|
|
10487
|
+
/** Restore selection, apply a color command, then hide the menu. */
|
|
10488
|
+
_applyColor(type, color) {
|
|
10489
|
+
const editable = this.context.layoutInfo && this.context.layoutInfo.editable;
|
|
10490
|
+
if (!editable || !this._savedRange) return;
|
|
10491
|
+
editable.focus();
|
|
10492
|
+
const sel = window.getSelection();
|
|
10493
|
+
sel.removeAllRanges();
|
|
10494
|
+
sel.addRange(this._savedRange.cloneRange());
|
|
10495
|
+
document.execCommand(type, false, color);
|
|
10496
|
+
this.context.invoke("editor.afterCommand");
|
|
10497
|
+
this.hide();
|
|
10498
|
+
}
|
|
9734
10499
|
/** Returns true if a format has been copied — used to disable Paste Format. */
|
|
9735
10500
|
hasCopiedFormat() {
|
|
9736
10501
|
return !!this._copiedFormat;
|
|
@@ -10398,7 +11163,7 @@
|
|
|
10398
11163
|
resolve(null);
|
|
10399
11164
|
}
|
|
10400
11165
|
};
|
|
10401
|
-
if (img.src.startsWith("data:") || img.src.startsWith("blob:") || img.src.startsWith(location.origin)) {
|
|
11166
|
+
if (img.src.startsWith("data:") || img.src.startsWith("blob:") || img.src.startsWith(window.location.origin)) {
|
|
10402
11167
|
tryDraw(img);
|
|
10403
11168
|
return;
|
|
10404
11169
|
}
|
|
@@ -10774,7 +11539,7 @@
|
|
|
10774
11539
|
height: natH
|
|
10775
11540
|
}, w, h);
|
|
10776
11541
|
if (!canvas) {
|
|
10777
|
-
alert("Cannot crop this image: the image server does not allow cross-origin access.\nUpload the image directly to use the crop tool.");
|
|
11542
|
+
window.alert("Cannot crop this image: the image server does not allow cross-origin access.\nUpload the image directly to use the crop tool.");
|
|
10778
11543
|
this._close(false);
|
|
10779
11544
|
return;
|
|
10780
11545
|
}
|
|
@@ -11135,9 +11900,15 @@
|
|
|
11135
11900
|
if (disabled) {
|
|
11136
11901
|
editable.setAttribute("contenteditable", "false");
|
|
11137
11902
|
this.layoutInfo.container.classList.add("an-disabled");
|
|
11903
|
+
editable.querySelectorAll("ul.an-checklist input[type=\"checkbox\"]").forEach((cb) => {
|
|
11904
|
+
cb.setAttribute("disabled", "");
|
|
11905
|
+
});
|
|
11138
11906
|
} else {
|
|
11139
11907
|
editable.setAttribute("contenteditable", "true");
|
|
11140
11908
|
this.layoutInfo.container.classList.remove("an-disabled");
|
|
11909
|
+
editable.querySelectorAll("ul.an-checklist input[type=\"checkbox\"]").forEach((cb) => {
|
|
11910
|
+
cb.removeAttribute("disabled");
|
|
11911
|
+
});
|
|
11141
11912
|
}
|
|
11142
11913
|
}
|
|
11143
11914
|
/**
|