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.
@@ -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
- var strikethrough = () => execCommand("strikeThrough");
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.querySelectorAll("font[size=\"7\"]").forEach((el) => {
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
- var outdent = () => execCommand("outdent");
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 (editable) editable.normalize();
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 container = sel.getRangeAt(0).commonAncestorContainer;
473
- if (container.nodeType === 3) container = container.parentElement;
474
- const code = container && container.closest ? container.closest("code") : null;
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
- let container = sel.getRangeAt(0).commonAncestorContainer;
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
- const lines = sel.toString().split(/\r?\n/).filter((l) => l.trim().length > 0);
512
- if (lines.length === 0) return;
513
- const items = lines.map((l) => `<li><input type="checkbox" contenteditable="false">${l || "​"}</li>`).join("");
514
- document.execCommand("insertHTML", false, `<ul class="an-checklist">${items}</ul>`);
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 container = sel.getRangeAt(0).commonAncestorContainer;
580
- if (container.nodeType === 3) container = container.parentElement;
581
- return !!(container && container.closest && container.closest("u"));
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, input, button)
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) container.classList.add("an-disabled");
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) execCommand("outdent");
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
- 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));
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 cell = this._getCell();
5659
- if (!cell) return;
5660
- const row = cell.closest("tr");
5661
- if (!row) return;
5662
- const colCount = Array.from(row.cells).reduce((sum, c) => sum + (c.colSpan || 1), 0);
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(row.cells);
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") row.parentElement?.insertBefore(newRow, row);
5673
- else row.insertAdjacentElement("afterend", newRow);
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 cell = this._getCell();
5679
- if (!cell) return;
5680
- const table = cell.closest("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 visualColIdx = getVisualColIndex(cell);
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, visualColIdx) : getCellAfterVisualCol(r, visualColIdx));
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 cell = this._getCell();
5694
- if (!cell) return;
5695
- const row = cell.closest("tr");
5696
- const table = cell.closest("table");
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
- if ((tbody ? tbody.querySelectorAll("tr").length : table.querySelectorAll("tr").length) <= 1 && row.closest("tbody")) return;
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
- row.parentElement?.removeChild(row);
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 cell = this._getCell();
5707
- if (!cell) return;
5708
- const table = cell.closest("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 row = cell.closest("tr");
5711
- if (row && row.cells.length <= 1) return;
5712
- const visualColIdx = getVisualColIndex(cell);
5713
- this._activeCell = null;
5714
- const rows = Array.from(table.querySelectorAll("tr"));
5715
- rows.map((r) => getCellAtVisualCol(r, visualColIdx)).forEach((c, i) => {
5716
- if (c) rows[i].removeChild(c);
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
- const selected = Array.from(table.querySelectorAll("td, th")).filter((c) => {
5730
- try {
5731
- return range.intersectsNode(c);
5732
- } catch {
5733
- return false;
5734
- }
5735
- });
5736
- if (selected.length < 2) return;
5737
- const rows = [...new Set(selected.map((c) => c.closest("tr")))];
5738
- if (rows.length === 1) {
5739
- const row = rows[0];
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 (&nbsp;); 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 table = cell.closest("table");
5852
- const visualColIdx = getVisualColIndex(cell);
5853
- Array.from(table.querySelectorAll("tr")).map((r) => getCellAtVisualCol(r, visualColIdx)).forEach((c) => {
5854
- if (c) {
5855
- c.style.width = `${val}px`;
5856
- c.style.minWidth = `${val}px`;
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 row = cell.closest("tr");
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
  /**