autumnnote 1.0.6 → 1.0.7

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.
@@ -598,14 +598,42 @@ var fontName = (name) => execCommand("fontName", name);
598
598
  * @param {HTMLElement} [editable] - scoping element to avoid touching nodes outside this editor
599
599
  */
600
600
  function fontSize(size, editable = document) {
601
+ const sel = window.getSelection();
602
+ const wasCollapsed = !sel || !sel.rangeCount || sel.getRangeAt(0).collapsed;
601
603
  execCommand("fontSize", "7");
602
- editable.querySelectorAll("font[size=\"7\"]").forEach((el) => {
604
+ const scope = editable instanceof HTMLElement ? editable : document;
605
+ const newSpans = [];
606
+ scope.querySelectorAll("font[size=\"7\"]").forEach((el) => {
603
607
  const span = document.createElement("span");
604
608
  span.style.fontSize = size;
605
609
  el.parentNode.insertBefore(span, el);
606
610
  while (el.firstChild) span.appendChild(el.firstChild);
607
611
  el.parentNode.removeChild(el);
612
+ newSpans.push(span);
608
613
  });
614
+ if (sel && newSpans.length > 0) {
615
+ const first = newSpans[0];
616
+ const last = newSpans[newSpans.length - 1];
617
+ try {
618
+ if (wasCollapsed) {
619
+ if (!first.firstChild) first.appendChild(document.createTextNode("​"));
620
+ const nr = document.createRange();
621
+ const anchor = first.firstChild;
622
+ nr.setStart(anchor, anchor.textContent.length);
623
+ nr.collapse(true);
624
+ sel.removeAllRanges();
625
+ sel.addRange(nr);
626
+ } else {
627
+ const nr = document.createRange();
628
+ const startNode = first.firstChild || first;
629
+ const endNode = last.lastChild || last;
630
+ nr.setStart(startNode, 0);
631
+ nr.setEnd(endNode, endNode.nodeType === Node.TEXT_NODE ? endNode.textContent.length : endNode.childNodes.length);
632
+ sel.removeAllRanges();
633
+ sel.addRange(nr);
634
+ }
635
+ } catch (_) {}
636
+ }
609
637
  }
610
638
  /**
611
639
  * Wraps the selection in the given block tag (p, h1-h6, blockquote, pre).
@@ -710,9 +738,24 @@ function toggleInlineCode(editable) {
710
738
  const codeEl = container && container.closest ? container.closest("code") : null;
711
739
  if (codeEl && !codeEl.closest("pre")) {
712
740
  const parent = codeEl.parentNode;
741
+ const prevSibling = codeEl.previousSibling;
742
+ const movedChildren = Array.from(codeEl.childNodes);
713
743
  while (codeEl.firstChild) parent.insertBefore(codeEl.firstChild, codeEl);
714
744
  parent.removeChild(codeEl);
715
- if (editable) editable.normalize();
745
+ if (parent && parent.normalize) parent.normalize();
746
+ if (movedChildren.length > 0) try {
747
+ const firstMoved = movedChildren[0];
748
+ const lastMoved = movedChildren[movedChildren.length - 1];
749
+ const nr = document.createRange();
750
+ const anchorNode = firstMoved.parentNode === parent ? firstMoved : prevSibling ? prevSibling.nextSibling : parent.firstChild;
751
+ if (anchorNode) {
752
+ nr.setStart(anchorNode, 0);
753
+ const endAnchor = lastMoved.parentNode === parent ? lastMoved : anchorNode;
754
+ nr.setEnd(endAnchor, endAnchor.nodeType === Node.TEXT_NODE ? endAnchor.textContent.length : endAnchor.childNodes.length);
755
+ sel.removeAllRanges();
756
+ sel.addRange(nr);
757
+ }
758
+ } catch (_) {}
716
759
  } else {
717
760
  if (range.collapsed) return;
718
761
  try {
@@ -737,14 +780,17 @@ function toggleInlineCode(editable) {
737
780
  /**
738
781
  * Returns true when the cursor / selection is inside an inline <code>
739
782
  * (not nested in a <pre>).
783
+ * Uses startContainer for reliable cross-browser detection regardless of
784
+ * whether the selection is collapsed or a range (commonAncestorContainer
785
+ * can behave inconsistently for range selections on some browsers).
740
786
  * @returns {boolean}
741
787
  */
742
788
  function isInlineCode() {
743
789
  const sel = window.getSelection();
744
790
  if (!sel || !sel.rangeCount) return false;
745
- let container = sel.getRangeAt(0).commonAncestorContainer;
746
- if (container.nodeType === 3) container = container.parentElement;
747
- const code = container && container.closest ? container.closest("code") : null;
791
+ let sc = sel.getRangeAt(0).startContainer;
792
+ if (sc.nodeType === 3) sc = sc.parentElement;
793
+ const code = sc && sc.closest ? sc.closest("code") : null;
748
794
  return !!(code && !code.closest("pre"));
749
795
  }
750
796
  /**
@@ -755,7 +801,8 @@ function isInlineCode() {
755
801
  function toggleChecklist() {
756
802
  const sel = window.getSelection();
757
803
  if (!sel || !sel.rangeCount) return;
758
- let container = sel.getRangeAt(0).commonAncestorContainer;
804
+ const range = sel.getRangeAt(0);
805
+ let container = range.commonAncestorContainer;
759
806
  if (container.nodeType === 3) container = container.parentElement;
760
807
  const ul = container.closest && container.closest(".an-checklist");
761
808
  if (ul) {
@@ -781,6 +828,45 @@ function toggleChecklist() {
781
828
  return;
782
829
  }
783
830
  }
831
+ if (range.collapsed) {
832
+ const BLOCK_TAGS = new Set([
833
+ "P",
834
+ "DIV",
835
+ "H1",
836
+ "H2",
837
+ "H3",
838
+ "H4",
839
+ "H5",
840
+ "H6",
841
+ "BLOCKQUOTE",
842
+ "LI"
843
+ ]);
844
+ let block = container;
845
+ while (block && block.parentNode && !BLOCK_TAGS.has(block.tagName)) block = block.parentNode;
846
+ const itemText = block && BLOCK_TAGS.has(block.tagName) ? Array.from(block.childNodes).map((n) => n.textContent).join("").replace(/\u00a0/g, " ") : "";
847
+ const ul = document.createElement("ul");
848
+ ul.className = "an-checklist";
849
+ const li = document.createElement("li");
850
+ const checkbox = document.createElement("input");
851
+ checkbox.type = "checkbox";
852
+ checkbox.contentEditable = "false";
853
+ li.appendChild(checkbox);
854
+ li.appendChild(document.createTextNode(itemText || "​"));
855
+ ul.appendChild(li);
856
+ if (block && BLOCK_TAGS.has(block.tagName)) block.parentNode.replaceChild(ul, block);
857
+ else {
858
+ document.execCommand("insertHTML", false, ul.outerHTML);
859
+ return;
860
+ }
861
+ const textNode = li.lastChild;
862
+ const nr = document.createRange();
863
+ const offset = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent.length : 0;
864
+ nr.setStart(textNode, offset);
865
+ nr.collapse(true);
866
+ sel.removeAllRanges();
867
+ sel.addRange(nr);
868
+ return;
869
+ }
784
870
  const lines = sel.toString().split(/\r?\n/).filter((l) => l.trim().length > 0);
785
871
  if (lines.length === 0) return;
786
872
  const items = lines.map((l) => `<li><input type="checkbox" contenteditable="false">${l || "​"}</li>`).join("");
@@ -849,9 +935,9 @@ var underlineBtn = btn("underline", "underline", "Underline (Ctrl+U)", () => und
849
935
  if (document.queryCommandState("underline")) return true;
850
936
  const sel = window.getSelection();
851
937
  if (!sel || !sel.rangeCount) return false;
852
- let container = sel.getRangeAt(0).commonAncestorContainer;
853
- if (container.nodeType === 3) container = container.parentElement;
854
- return !!(container && container.closest && container.closest("u"));
938
+ let sc = sel.getRangeAt(0).startContainer;
939
+ if (sc.nodeType === 3) sc = sc.parentElement;
940
+ return !!(sc && sc.closest && sc.closest("u"));
855
941
  });
856
942
  var strikeBtn = btn("strikethrough", "strikethrough", "Strikethrough", () => strikethrough(), () => document.queryCommandState("strikeThrough"));
857
943
  var superscriptBtn = btn("superscript", "superscript", "Superscript", () => superscript(), () => document.queryCommandState("superscript"));
@@ -1254,7 +1340,6 @@ var PROHIBITED_TAGS = [
1254
1340
  "object",
1255
1341
  "embed",
1256
1342
  "form",
1257
- "input",
1258
1343
  "button"
1259
1344
  ];
1260
1345
  /** Attributes whose values must be sanitised as URLs. */
@@ -1278,7 +1363,8 @@ var TRUSTED_IFRAME_HOSTS = new Set([
1278
1363
  * Uses DOMParser so the sanitisation follows normal browser parsing rules —
1279
1364
  * no regex shortcuts that can be bypassed by encoding tricks.
1280
1365
  *
1281
- * - Strips PROHIBITED_TAGS (script, style, iframe, object, embed, form, input, button)
1366
+ * - Strips PROHIBITED_TAGS (script, style, iframe, object, embed, form, button)
1367
+ * - Allows input[type="checkbox"] only inside ul.an-checklist li; removes all other <input>
1282
1368
  * - Removes all on* event-handler attributes
1283
1369
  * - Rejects javascript: and vbscript: URLs in URL attributes
1284
1370
  * - Rejects data: URIs everywhere except img[src] (base64 uploads)
@@ -1317,6 +1403,16 @@ function sanitiseHTML(html, { allowIframes = false } = {}) {
1317
1403
  }
1318
1404
  });
1319
1405
  });
1406
+ doc.querySelectorAll("input").forEach((el) => {
1407
+ if (!(el.closest("ul.an-checklist") !== null && el.closest("li") !== null) || el.getAttribute("type") !== "checkbox") el.remove();
1408
+ else Array.from(el.attributes).forEach((attr) => {
1409
+ if (![
1410
+ "type",
1411
+ "checked",
1412
+ "contenteditable"
1413
+ ].includes(attr.name)) el.removeAttribute(attr.name);
1414
+ });
1415
+ });
1320
1416
  return doc.body.innerHTML;
1321
1417
  }
1322
1418
  /**
@@ -1397,7 +1493,12 @@ function renderLayout(targetEl, options) {
1397
1493
  if (options.maxHeight) editable.style.maxHeight = `${options.maxHeight}px`;
1398
1494
  container.appendChild(editable);
1399
1495
  if (options.theme === "dark") container.classList.add("an-theme-dark");
1400
- if (options.readOnly) container.classList.add("an-disabled");
1496
+ if (options.readOnly) {
1497
+ container.classList.add("an-disabled");
1498
+ editable.querySelectorAll("ul.an-checklist input[type=\"checkbox\"]").forEach((cb) => {
1499
+ cb.setAttribute("disabled", "");
1500
+ });
1501
+ }
1401
1502
  if (options.direction === "rtl") {
1402
1503
  editable.setAttribute("dir", "rtl");
1403
1504
  container.classList.add("an-dir-rtl");
@@ -2276,7 +2377,37 @@ var Editor = class {
2276
2377
  sel.removeAllRanges();
2277
2378
  sel.addRange(nr);
2278
2379
  };
2279
- 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));
2380
+ const isReadOnly = () => this.context.layoutInfo.container.classList.contains("an-disabled");
2381
+ 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) => {
2382
+ if (isReadOnly()) e.preventDefault();
2383
+ }), on(editable, "drop", (e) => {
2384
+ if (isReadOnly()) e.preventDefault();
2385
+ }));
2386
+ /** @type {string|null} 'superscript' | 'subscript' | null */
2387
+ let _compositionSupSub = null;
2388
+ const onCompositionStart = () => {
2389
+ const sel = window.getSelection();
2390
+ if (!sel || !sel.rangeCount) {
2391
+ _compositionSupSub = null;
2392
+ return;
2393
+ }
2394
+ let node = sel.getRangeAt(0).startContainer;
2395
+ if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
2396
+ if (node && node.closest) if (node.closest("sup")) _compositionSupSub = "superscript";
2397
+ else if (node.closest("sub")) _compositionSupSub = "subscript";
2398
+ else _compositionSupSub = null;
2399
+ };
2400
+ const onCompositionEnd = () => {
2401
+ const tag = _compositionSupSub;
2402
+ _compositionSupSub = null;
2403
+ if (!tag) return;
2404
+ const sel = window.getSelection();
2405
+ if (!sel || !sel.rangeCount) return;
2406
+ let node = sel.getRangeAt(0).startContainer;
2407
+ if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
2408
+ if (!(node && node.closest && (tag === "superscript" ? node.closest("sup") : node.closest("sub")))) document.execCommand(tag);
2409
+ };
2410
+ this._disposers.push(on(editable, "compositionstart", onCompositionStart), on(editable, "compositionend", onCompositionEnd));
2280
2411
  }
2281
2412
  _onKeydown(event) {
2282
2413
  const editable = this.context.layoutInfo.editable;
@@ -2362,6 +2493,7 @@ var Editor = class {
2362
2493
  }
2363
2494
  }
2364
2495
  afterCommand() {
2496
+ this._cleanOrphanedFigures();
2365
2497
  this.context.invoke("toolbar.refresh");
2366
2498
  this.context.invoke("statusbar.update");
2367
2499
  this._scheduleSnapshot();
@@ -2378,6 +2510,16 @@ var Editor = class {
2378
2510
  this.context.triggerEvent("change", this.getHTML());
2379
2511
  }, 400);
2380
2512
  }
2513
+ /**
2514
+ * C4: Removes figure.an-figure elements that no longer contain an <img>.
2515
+ * This happens when a user selects only the image (not the whole figure)
2516
+ * and deletes or replaces it, leaving a dangling figcaption.
2517
+ */
2518
+ _cleanOrphanedFigures() {
2519
+ this.context.layoutInfo.editable.querySelectorAll("figure.an-figure").forEach((fig) => {
2520
+ if (!fig.querySelector("img")) fig.parentNode.removeChild(fig);
2521
+ });
2522
+ }
2381
2523
  focus() {
2382
2524
  this.context.layoutInfo.editable.focus();
2383
2525
  }
@@ -3109,15 +3251,28 @@ var Toolbar = class {
3109
3251
  if (def.name === "fontFamily" && !isHeader) opt.style.fontFamily = value;
3110
3252
  select.appendChild(opt);
3111
3253
  });
3254
+ /** @type {Range|null} */
3255
+ let _savedRange = null;
3256
+ const dMousedown = on(select, "mousedown", () => {
3257
+ const sel = window.getSelection();
3258
+ _savedRange = sel && sel.rangeCount ? sel.getRangeAt(0).cloneRange() : null;
3259
+ });
3112
3260
  const disposer = on(select, "change", (e) => {
3113
3261
  const value = e.target.value;
3114
3262
  const selectedOpt = e.target.options[e.target.selectedIndex];
3115
3263
  if (!value || selectedOpt.disabled) return;
3116
3264
  this.context.invoke("editor.focus");
3265
+ if (_savedRange) try {
3266
+ const sel = window.getSelection();
3267
+ if (sel) {
3268
+ sel.removeAllRanges();
3269
+ sel.addRange(_savedRange);
3270
+ }
3271
+ } catch (_) {}
3117
3272
  def.action(this.context, value);
3118
3273
  this.context.invoke("editor.afterCommand");
3119
3274
  });
3120
- this._disposers.push(disposer);
3275
+ this._disposers.push(dMousedown, disposer);
3121
3276
  return select;
3122
3277
  }
3123
3278
  /**
@@ -3574,9 +3729,25 @@ var Clipboard = class {
3574
3729
  this.options.onImageUpload(files);
3575
3730
  return;
3576
3731
  }
3732
+ const UNSUPPORTED = [
3733
+ "image/tiff",
3734
+ "image/x-tiff",
3735
+ "image/bmp",
3736
+ "image/x-bmp",
3737
+ "image/x-ms-bmp"
3738
+ ];
3577
3739
  const maxBytes = (this.options.maxImageSize || 5) * 1024 * 1024;
3578
3740
  files.forEach((file) => {
3579
3741
  if (!file || !file.type.startsWith("image/")) return;
3742
+ if (UNSUPPORTED.includes(file.type)) {
3743
+ const message = `Image format "${file.type}" is not supported for display in web browsers. Please convert to PNG, JPEG, or WebP first.`;
3744
+ this.context.triggerEvent("imageError", {
3745
+ file,
3746
+ message
3747
+ });
3748
+ console.warn("[AutumnNote]", message);
3749
+ return;
3750
+ }
3580
3751
  if (file.size > maxBytes) {
3581
3752
  const message = `Image "${file.name}" exceeds the ${this.options.maxImageSize || 5} MB size limit.`;
3582
3753
  this.context.triggerEvent("imageError", {
@@ -3740,7 +3911,7 @@ var Placeholder = class {
3740
3911
  _update() {
3741
3912
  const editable = this.context.layoutInfo.editable;
3742
3913
  const isFocused = document.activeElement === editable;
3743
- const isEmpty = !editable.textContent.trim() && !editable.querySelector("img, table, hr");
3914
+ const isEmpty = !editable.textContent.trim() && !editable.querySelector("img, table, hr, .an-video-wrapper");
3744
3915
  editable.classList.toggle("an-placeholder", isEmpty && !isFocused);
3745
3916
  }
3746
3917
  };
@@ -4178,9 +4349,11 @@ var ImageDialog = class {
4178
4349
  accept: "image/*"
4179
4350
  });
4180
4351
  this._fileInput = fileInput;
4352
+ const fileHint = createElement("p", { class: "an-dialog-hint" });
4353
+ this._fileHint = fileHint;
4181
4354
  const d = on(fileInput, "change", () => this._onFileChange());
4182
4355
  this._disposers.push(d);
4183
- box.append(fileLabel, fileInput);
4356
+ box.append(fileLabel, fileInput, fileHint);
4184
4357
  }
4185
4358
  const btnRow = createElement("div", { class: "an-dialog-actions" });
4186
4359
  const insertBtn = createElement("button", {
@@ -4220,9 +4393,27 @@ var ImageDialog = class {
4220
4393
  _onFileChange() {
4221
4394
  const file = this._fileInput && this._fileInput.files && this._fileInput.files[0];
4222
4395
  if (!file || !file.type.startsWith("image/")) return;
4396
+ if ([
4397
+ "image/tiff",
4398
+ "image/x-tiff",
4399
+ "image/bmp",
4400
+ "image/x-bmp",
4401
+ "image/x-ms-bmp"
4402
+ ].includes(file.type)) {
4403
+ const message = `Format "${file.type}" is not supported for display in web browsers. Please convert to PNG, JPEG, or WebP first.`;
4404
+ if (this._fileHint) this._fileHint.textContent = message;
4405
+ this.context.triggerEvent("imageError", {
4406
+ file,
4407
+ message
4408
+ });
4409
+ this._fileInput.value = "";
4410
+ return;
4411
+ }
4412
+ if (this._fileHint) this._fileHint.textContent = "";
4223
4413
  const maxSize = (this.options.maxImageSize || 5) * 1024 * 1024;
4224
4414
  if (file.size > maxSize) {
4225
4415
  const message = `Image file is too large. Maximum allowed size is ${this.options.maxImageSize || 5} MB.`;
4416
+ if (this._fileHint) this._fileHint.textContent = message;
4226
4417
  console.warn("[AutumnNote] ImageDialog:", message);
4227
4418
  this.context.triggerEvent("imageError", {
4228
4419
  file,
@@ -4534,6 +4725,7 @@ var ImageResizer = class {
4534
4725
  _resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
4535
4726
  };
4536
4727
  this._disposers.push(on(editable, "click", (e) => this._onEditorClick(e)), on(editable, "contextmenu", (e) => {
4728
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
4537
4729
  const img = e.target.closest("img");
4538
4730
  if (img) this._select(img);
4539
4731
  }), 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 }));
@@ -4584,6 +4776,7 @@ var ImageResizer = class {
4584
4776
  return overlay;
4585
4777
  }
4586
4778
  _onEditorClick(e) {
4779
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
4587
4780
  const img = e.target.closest("img");
4588
4781
  if (img) {
4589
4782
  e.preventDefault();
@@ -4740,9 +4933,12 @@ var VideoResizer = class {
4740
4933
  _resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
4741
4934
  };
4742
4935
  this._disposers.push(on(editable, "click", (e) => this._onEditorClick(e)), on(editable, "contextmenu", (e) => {
4936
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
4743
4937
  const wrapper = this._findWrapper(e.target);
4744
4938
  if (wrapper) this._select(wrapper);
4745
- }), 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 }));
4939
+ }), 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) => {
4940
+ if (e.target instanceof Element && e.target.closest(".an-video-wrapper")) e.preventDefault();
4941
+ }));
4746
4942
  return this;
4747
4943
  }
4748
4944
  destroy() {
@@ -4801,6 +4997,7 @@ var VideoResizer = class {
4801
4997
  return overlay;
4802
4998
  }
4803
4999
  _onEditorClick(e) {
5000
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
4804
5001
  const wrapper = this._findWrapper(e.target);
4805
5002
  if (wrapper) {
4806
5003
  e.preventDefault();
@@ -4999,9 +5196,12 @@ var LinkTooltip = class {
4999
5196
  }, HIDE_DELAY$4);
5000
5197
  }
5001
5198
  _show(anchor) {
5199
+ const isReadOnly = this.context.layoutInfo.container.classList.contains("an-disabled");
5002
5200
  const url = anchor.getAttribute("href") || "";
5003
5201
  this._urlLabel.textContent = this._truncateUrl(url);
5004
5202
  this._urlLabel.title = url;
5203
+ this._editBtn.style.display = isReadOnly ? "none" : "";
5204
+ this._unlinkBtn.style.display = isReadOnly ? "none" : "";
5005
5205
  this._el.style.display = "flex";
5006
5206
  this._positionNear(anchor);
5007
5207
  }
@@ -5115,6 +5315,7 @@ var ImageTooltip = class {
5115
5315
  document.body.appendChild(this._el);
5116
5316
  const editable = this.context.layoutInfo.editable;
5117
5317
  this._disposers.push(on(editable, "mouseover", (e) => {
5318
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
5118
5319
  const img = e.target.closest("img");
5119
5320
  if (img && editable.contains(img) && !img.closest("a[href]")) this._scheduleShow(img);
5120
5321
  }, { passive: true }), on(editable, "mouseout", (e) => {
@@ -5241,6 +5442,7 @@ var ImageTooltip = class {
5241
5442
  const target = img.closest("figure.an-figure") || img;
5242
5443
  target.style.float = value;
5243
5444
  target.style.display = "";
5445
+ if (target !== img) target.style.width = "";
5244
5446
  target.style.marginLeft = value === "right" ? "12px" : "";
5245
5447
  target.style.marginRight = value === "left" ? "12px" : "";
5246
5448
  this.context.invoke("editor.afterCommand");
@@ -5255,6 +5457,7 @@ var ImageTooltip = class {
5255
5457
  const target = img.closest("figure.an-figure") || img;
5256
5458
  target.style.float = "";
5257
5459
  target.style.display = "block";
5460
+ if (target !== img) target.style.width = "fit-content";
5258
5461
  target.style.marginLeft = "auto";
5259
5462
  target.style.marginRight = "auto";
5260
5463
  this.context.invoke("editor.afterCommand");
@@ -5339,6 +5542,7 @@ var ImageTooltip = class {
5339
5542
  img.style.marginRight = "";
5340
5543
  } else if (img.style.display === "block" && img.style.marginLeft === "auto") {
5341
5544
  figure.style.display = "block";
5545
+ figure.style.width = "fit-content";
5342
5546
  figure.style.marginLeft = "auto";
5343
5547
  figure.style.marginRight = "auto";
5344
5548
  img.style.display = "";
@@ -5393,6 +5597,7 @@ var VideoTooltip = class {
5393
5597
  document.body.appendChild(this._el);
5394
5598
  const editable = this.context.layoutInfo.editable;
5395
5599
  this._disposers.push(on(editable, "mouseover", (e) => {
5600
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
5396
5601
  const wrapper = e.target.closest(".an-video-wrapper");
5397
5602
  if (wrapper && editable.contains(wrapper)) this._scheduleShow(wrapper);
5398
5603
  }, { passive: true }), on(editable, "mouseout", (e) => {
@@ -5684,6 +5889,7 @@ var TableTooltip = class {
5684
5889
  document.body.appendChild(this._sizePopover);
5685
5890
  const editable = this.context.layoutInfo.editable;
5686
5891
  this._disposers.push(on(editable, "mouseover", (e) => {
5892
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
5687
5893
  const table = e.target.closest("table");
5688
5894
  if (table && editable.contains(table)) {
5689
5895
  const cell = e.target.closest("td, th");
@@ -5725,6 +5931,10 @@ var TableTooltip = class {
5725
5931
  };
5726
5932
  const onEditorMove = (e) => {
5727
5933
  if (_resizing) return;
5934
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) {
5935
+ clearHover();
5936
+ return;
5937
+ }
5728
5938
  if (_rafEditorMove !== null) return;
5729
5939
  const target = e.target;
5730
5940
  const clientX = e.clientX;
@@ -5752,6 +5962,7 @@ var TableTooltip = class {
5752
5962
  });
5753
5963
  };
5754
5964
  const onEditorDown = (e) => {
5965
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
5755
5966
  if (!_nearCell || !_nearEdge) return;
5756
5967
  _resizing = true;
5757
5968
  _edge = _nearEdge;
@@ -6201,6 +6412,7 @@ var CodeTooltip = class {
6201
6412
  this._ensurePrism();
6202
6413
  const editable = this.context.layoutInfo.editable;
6203
6414
  this._disposers.push(on(editable, "mouseover", (e) => {
6415
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
6204
6416
  const pre = e.target.closest("pre");
6205
6417
  if (pre && editable.contains(pre)) this._scheduleShow(pre);
6206
6418
  }), on(editable, "mouseout", (e) => {
@@ -9631,22 +9843,50 @@ var ICONS = {
9631
9843
  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>`,
9632
9844
  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>`,
9633
9845
  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>`,
9634
- 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>`
9846
+ 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>`,
9847
+ 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>`,
9848
+ 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>`,
9849
+ 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>`,
9850
+ 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>`
9635
9851
  };
9852
+ var COLOR_PRESETS = [
9853
+ "#000000",
9854
+ "#434343",
9855
+ "#666666",
9856
+ "#999999",
9857
+ "#b7b7b7",
9858
+ "#cccccc",
9859
+ "#efefef",
9860
+ "#ffffff",
9861
+ "#ff0000",
9862
+ "#ff9900",
9863
+ "#ffff00",
9864
+ "#00ff00",
9865
+ "#00ffff",
9866
+ "#4a86e8",
9867
+ "#9900ff",
9868
+ "#ff00ff",
9869
+ "#f4cccc",
9870
+ "#fce5cd",
9871
+ "#fff2cc",
9872
+ "#d9ead3",
9873
+ "#d0e0e3",
9874
+ "#c9daf8",
9875
+ "#d9d2e9",
9876
+ "#ead1dc"
9877
+ ];
9878
+ function makeColorSubItems(colorType) {
9879
+ const label = colorType === "foreColor" ? "Text Color" : "Highlight Color";
9880
+ return () => [{
9881
+ back: true,
9882
+ label,
9883
+ navigate: () => defaultItems
9884
+ }, {
9885
+ colorPalette: true,
9886
+ colorType
9887
+ }];
9888
+ }
9636
9889
  var defaultItems = [
9637
- {
9638
- name: "undo",
9639
- label: "Undo",
9640
- icon: ICONS.undo,
9641
- action: (ctx) => ctx.invoke("editor.undo")
9642
- },
9643
- {
9644
- name: "redo",
9645
- label: "Redo",
9646
- icon: ICONS.redo,
9647
- action: (ctx) => ctx.invoke("editor.redo")
9648
- },
9649
- { separator: true },
9650
9890
  {
9651
9891
  name: "cut",
9652
9892
  label: "Cut",
@@ -9707,6 +9947,21 @@ var defaultItems = [
9707
9947
  action: (ctx) => ctx.invoke("editor.underline")
9708
9948
  },
9709
9949
  { separator: true },
9950
+ {
9951
+ name: "textColor",
9952
+ label: "Text Color",
9953
+ icon: ICONS.textColor,
9954
+ colorStrip: "foreColor",
9955
+ navigate: makeColorSubItems("foreColor")
9956
+ },
9957
+ {
9958
+ name: "highlightColor",
9959
+ label: "Highlight Color",
9960
+ icon: ICONS.highlightColor,
9961
+ colorStrip: "hiliteColor",
9962
+ navigate: makeColorSubItems("hiliteColor")
9963
+ },
9964
+ { separator: true },
9710
9965
  {
9711
9966
  name: "copyFormat",
9712
9967
  label: "Copy Format",
@@ -9824,8 +10079,10 @@ var ContextMenu = class {
9824
10079
  backBtn.appendChild(createElement("span", { class: "an-context-label" }, [it.label || "Back"]));
9825
10080
  const off = on(backBtn, "click", (e) => {
9826
10081
  e.stopPropagation();
10082
+ const curLeft = parseFloat(this.el.style.left);
10083
+ const curTop = parseFloat(this.el.style.top);
9827
10084
  this._renderItems(it.navigate());
9828
- this._reposition();
10085
+ this._reposition(curLeft, curTop);
9829
10086
  });
9830
10087
  this._menuDisposers.push(off);
9831
10088
  this.el.appendChild(backBtn);
@@ -9837,7 +10094,19 @@ var ContextMenu = class {
9837
10094
  class: "an-context-item an-context-submenu",
9838
10095
  "data-name": it.name || ""
9839
10096
  });
9840
- if (it.icon) {
10097
+ if (it.icon) if (it.colorStrip) {
10098
+ const iconWrap = createElement("span", {
10099
+ class: "an-context-icon an-context-icon--color",
10100
+ "aria-hidden": "true"
10101
+ });
10102
+ const svgSpan = createElement("span", { class: "an-context-icon-svg" });
10103
+ svgSpan.innerHTML = it.icon;
10104
+ const strip = createElement("span", { class: "an-context-color-strip" });
10105
+ strip.style.background = this._getSelectionColor(it.colorStrip);
10106
+ iconWrap.appendChild(svgSpan);
10107
+ iconWrap.appendChild(strip);
10108
+ btn.appendChild(iconWrap);
10109
+ } else {
9841
10110
  const iconSpan = createElement("span", {
9842
10111
  class: "an-context-icon",
9843
10112
  "aria-hidden": "true"
@@ -9854,13 +10123,63 @@ var ContextMenu = class {
9854
10123
  btn.appendChild(chevron);
9855
10124
  const off = on(btn, "click", (e) => {
9856
10125
  e.stopPropagation();
10126
+ const curLeft = parseFloat(this.el.style.left);
10127
+ const curTop = parseFloat(this.el.style.top);
9857
10128
  this._renderItems(it.navigate());
9858
- this._reposition();
10129
+ this._reposition(curLeft, curTop);
9859
10130
  });
9860
10131
  this._menuDisposers.push(off);
9861
10132
  this.el.appendChild(btn);
9862
10133
  return;
9863
10134
  }
10135
+ if (it.colorPalette) {
10136
+ const palette = createElement("div", { class: "an-context-color-palette" });
10137
+ COLOR_PRESETS.forEach((color) => {
10138
+ const sw = createElement("div", {
10139
+ class: "an-context-color-swatch",
10140
+ title: color,
10141
+ role: "button",
10142
+ "aria-label": color
10143
+ });
10144
+ sw.style.background = color;
10145
+ const offSw = on(sw, "click", (e) => {
10146
+ e.stopPropagation();
10147
+ this._applyColor(it.colorType, color);
10148
+ });
10149
+ this._menuDisposers.push(offSw);
10150
+ palette.appendChild(sw);
10151
+ });
10152
+ if (it.colorType === "hiliteColor") {
10153
+ const noColor = createElement("div", {
10154
+ class: "an-context-color-swatch an-context-color-none",
10155
+ title: "No highlight",
10156
+ role: "button",
10157
+ "aria-label": "No highlight"
10158
+ });
10159
+ noColor.innerHTML = ICONS.noColor;
10160
+ const offNo = on(noColor, "click", (e) => {
10161
+ e.stopPropagation();
10162
+ this._applyColor("hiliteColor", "transparent");
10163
+ });
10164
+ this._menuDisposers.push(offNo);
10165
+ palette.appendChild(noColor);
10166
+ }
10167
+ this.el.appendChild(palette);
10168
+ const customRow = createElement("div", { class: "an-context-color-custom" });
10169
+ const colorInput = createElement("input", {
10170
+ type: "color",
10171
+ value: it.colorType === "foreColor" ? "#000000" : "#ffff00",
10172
+ title: "Custom color",
10173
+ "aria-label": "Custom color"
10174
+ });
10175
+ const customLabel = createElement("span", {}, ["Custom…"]);
10176
+ const offCustom = on(colorInput, "change", () => this._applyColor(it.colorType, colorInput.value));
10177
+ this._menuDisposers.push(offCustom);
10178
+ customRow.appendChild(colorInput);
10179
+ customRow.appendChild(customLabel);
10180
+ this.el.appendChild(customRow);
10181
+ return;
10182
+ }
9864
10183
  if (it.tableGrid) {
9865
10184
  const GRID_ROWS = 8, GRID_COLS = 8;
9866
10185
  const wrapper = createElement("div", { class: "an-context-table-wrap" });
@@ -9978,6 +10297,7 @@ var ContextMenu = class {
9978
10297
  const editable = this.context.layoutInfo && this.context.layoutInfo.editable;
9979
10298
  if (!editable) return;
9980
10299
  if (!editable.contains(event.target)) return;
10300
+ if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
9981
10301
  event.preventDefault();
9982
10302
  this._lastX = event.clientX;
9983
10303
  this._lastY = event.clientY;
@@ -10004,7 +10324,19 @@ var ContextMenu = class {
10004
10324
  let left = rx;
10005
10325
  let top = ry;
10006
10326
  if (left + rect.width > window.innerWidth) left = window.innerWidth - rect.width - 8;
10327
+ if (left < 8) left = 8;
10007
10328
  if (top + rect.height > window.innerHeight) top = window.innerHeight - rect.height - 8;
10329
+ if (top < 8) top = 8;
10330
+ if (this._savedRange) try {
10331
+ const sel = this._savedRange.getBoundingClientRect();
10332
+ if (sel.width > 0 || sel.height > 0) {
10333
+ if (top < sel.bottom && top + rect.height > sel.top && left < sel.right && left + rect.width > sel.left) {
10334
+ const belowTop = sel.bottom + 6;
10335
+ if (belowTop + rect.height <= window.innerHeight - 8) top = belowTop;
10336
+ else top = Math.max(8, sel.top - rect.height - 6);
10337
+ }
10338
+ }
10339
+ } catch (_) {}
10008
10340
  this.el.style.left = `${left}px`;
10009
10341
  this.el.style.top = `${top}px`;
10010
10342
  }
@@ -10013,6 +10345,33 @@ var ContextMenu = class {
10013
10345
  this.el.style.display = "none";
10014
10346
  this.el.setAttribute("aria-hidden", "true");
10015
10347
  }
10348
+ /** Read the current selection's text or highlight color for the strip.
10349
+ * @param {'foreColor'|'hiliteColor'} type
10350
+ * @returns {string} CSS color string
10351
+ */
10352
+ _getSelectionColor(type) {
10353
+ const range = this._savedRange;
10354
+ if (!range) return type === "foreColor" ? "#000000" : "transparent";
10355
+ let node = range.startContainer;
10356
+ if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
10357
+ if (!node) return type === "foreColor" ? "#000000" : "transparent";
10358
+ const cs = window.getComputedStyle(node);
10359
+ if (type === "foreColor") return cs.color || "#000000";
10360
+ const bg = cs.backgroundColor;
10361
+ return !bg || bg === "rgba(0, 0, 0, 0)" || bg === "transparent" ? "transparent" : bg;
10362
+ }
10363
+ /** Restore selection, apply a color command, then hide the menu. */
10364
+ _applyColor(type, color) {
10365
+ const editable = this.context.layoutInfo && this.context.layoutInfo.editable;
10366
+ if (!editable || !this._savedRange) return;
10367
+ editable.focus();
10368
+ const sel = window.getSelection();
10369
+ sel.removeAllRanges();
10370
+ sel.addRange(this._savedRange.cloneRange());
10371
+ document.execCommand(type, false, color);
10372
+ this.context.invoke("editor.afterCommand");
10373
+ this.hide();
10374
+ }
10016
10375
  /** Returns true if a format has been copied — used to disable Paste Format. */
10017
10376
  hasCopiedFormat() {
10018
10377
  return !!this._copiedFormat;
@@ -10680,7 +11039,7 @@ function drawCropToCanvas(img, naturalRect, renderW, renderH) {
10680
11039
  resolve(null);
10681
11040
  }
10682
11041
  };
10683
- if (img.src.startsWith("data:") || img.src.startsWith("blob:") || img.src.startsWith(location.origin)) {
11042
+ if (img.src.startsWith("data:") || img.src.startsWith("blob:") || img.src.startsWith(window.location.origin)) {
10684
11043
  tryDraw(img);
10685
11044
  return;
10686
11045
  }
@@ -11056,7 +11415,7 @@ var ImageCropOverlay = class {
11056
11415
  height: natH
11057
11416
  }, w, h);
11058
11417
  if (!canvas) {
11059
- alert("Cannot crop this image: the image server does not allow cross-origin access.\nUpload the image directly to use the crop tool.");
11418
+ window.alert("Cannot crop this image: the image server does not allow cross-origin access.\nUpload the image directly to use the crop tool.");
11060
11419
  this._close(false);
11061
11420
  return;
11062
11421
  }
@@ -11417,9 +11776,15 @@ var Context = class {
11417
11776
  if (disabled) {
11418
11777
  editable.setAttribute("contenteditable", "false");
11419
11778
  this.layoutInfo.container.classList.add("an-disabled");
11779
+ editable.querySelectorAll("ul.an-checklist input[type=\"checkbox\"]").forEach((cb) => {
11780
+ cb.setAttribute("disabled", "");
11781
+ });
11420
11782
  } else {
11421
11783
  editable.setAttribute("contenteditable", "true");
11422
11784
  this.layoutInfo.container.classList.remove("an-disabled");
11785
+ editable.querySelectorAll("ul.an-checklist input[type=\"checkbox\"]").forEach((cb) => {
11786
+ cb.removeAttribute("disabled");
11787
+ });
11423
11788
  }
11424
11789
  }
11425
11790
  /**