autumnnote 1.4.2 → 1.6.0

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.
Files changed (46) hide show
  1. package/README.md +102 -21
  2. package/dist/autumnnote.css +324 -2
  3. package/dist/autumnnote.es.js +700 -228
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +691 -235
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +8 -1
  8. package/src/js/Context.js +8 -3
  9. package/src/js/core/detectLang.js +98 -0
  10. package/src/js/core/dom.js +62 -6
  11. package/src/js/core/markdown.js +14 -13
  12. package/src/js/core/range.js +2 -2
  13. package/src/js/editing/History.js +6 -6
  14. package/src/js/editing/Style.js +19 -19
  15. package/src/js/editing/Table.js +4 -6
  16. package/src/js/editing/Typing.js +6 -6
  17. package/src/js/i18n/en.js +2 -0
  18. package/src/js/i18n/vi.js +2 -0
  19. package/src/js/index.js +4 -3
  20. package/src/js/module/BubbleToolbar.js +30 -13
  21. package/src/js/module/Buttons.js +16 -14
  22. package/src/js/module/Clipboard.js +5 -5
  23. package/src/js/module/CodeTooltip.js +42 -16
  24. package/src/js/module/Codeview.js +2 -2
  25. package/src/js/module/ContextMenu.js +12 -12
  26. package/src/js/module/Editor.js +64 -12
  27. package/src/js/module/EmojiDialog.js +19 -13
  28. package/src/js/module/FindReplace.js +85 -59
  29. package/src/js/module/Fullscreen.js +1 -1
  30. package/src/js/module/IconDialog.js +26 -20
  31. package/src/js/module/ImageCropOverlay.js +6 -6
  32. package/src/js/module/ImageDialog.js +18 -12
  33. package/src/js/module/ImageResizer.js +1 -1
  34. package/src/js/module/ImageTooltip.js +8 -4
  35. package/src/js/module/LinkDialog.js +18 -12
  36. package/src/js/module/LinkTooltip.js +5 -2
  37. package/src/js/module/Mention.js +3 -3
  38. package/src/js/module/Statusbar.js +2 -2
  39. package/src/js/module/TableTooltip.js +180 -18
  40. package/src/js/module/Toolbar.js +16 -15
  41. package/src/js/module/VideoDialog.js +8 -2
  42. package/src/js/module/VideoResizer.js +3 -3
  43. package/src/js/module/VideoTooltip.js +11 -6
  44. package/src/js/renderer.js +4 -2
  45. package/src/js/settings.js +53 -36
  46. package/src/styles/autumnnote.scss +332 -4
@@ -125,16 +125,60 @@
125
125
  if (e.shiftKey) {
126
126
  if (document.activeElement === first) {
127
127
  e.preventDefault();
128
- last.focus();
128
+ /** @type {HTMLElement} */ last.focus();
129
129
  }
130
130
  } else if (document.activeElement === last) {
131
131
  e.preventDefault();
132
- first.focus();
132
+ /** @type {HTMLElement} */ first.focus();
133
133
  }
134
134
  };
135
135
  document.addEventListener("keydown", handler);
136
136
  return () => document.removeEventListener("keydown", handler);
137
137
  }
138
+ /**
139
+ * Makes a dialog box draggable by its handle element.
140
+ * On first drag the box is pinned to its current viewport coordinates via
141
+ * `position:fixed`, freeing it from the parent flex container's centering.
142
+ * The position is clamped to the visible viewport.
143
+ *
144
+ * @param {HTMLElement} handle Element the user grabs (title bar / header)
145
+ * @param {HTMLElement} box Element that actually moves
146
+ * @returns {Function} Cleanup function (removes the mousedown listener)
147
+ */
148
+ function makeDraggable(handle, box) {
149
+ handle.style.cursor = "grab";
150
+ const onMousedown = (e) => {
151
+ if (e.button !== 0) return;
152
+ if (e.target.closest("button, input, select, textarea, a")) return;
153
+ e.preventDefault();
154
+ if (!box.dataset.anDragPinned) {
155
+ const r = box.getBoundingClientRect();
156
+ box.style.position = "fixed";
157
+ box.style.margin = "0";
158
+ box.style.left = `${r.left}px`;
159
+ box.style.top = `${r.top}px`;
160
+ box.dataset.anDragPinned = "1";
161
+ }
162
+ const startX = e.clientX - parseFloat(box.style.left);
163
+ const startY = e.clientY - parseFloat(box.style.top);
164
+ handle.style.cursor = "grabbing";
165
+ const onMove = (ev) => {
166
+ const bw = box.offsetWidth;
167
+ const bh = box.offsetHeight;
168
+ box.style.left = `${Math.max(0, Math.min(ev.clientX - startX, window.innerWidth - bw))}px`;
169
+ box.style.top = `${Math.max(0, Math.min(ev.clientY - startY, window.innerHeight - bh))}px`;
170
+ };
171
+ const onUp = () => {
172
+ handle.style.cursor = "grab";
173
+ document.removeEventListener("mousemove", onMove);
174
+ document.removeEventListener("mouseup", onUp);
175
+ };
176
+ document.addEventListener("mousemove", onMove);
177
+ document.addEventListener("mouseup", onUp);
178
+ };
179
+ handle.addEventListener("mousedown", onMousedown);
180
+ return () => handle.removeEventListener("mousedown", onMousedown);
181
+ }
138
182
  //#endregion
139
183
  //#region src/js/core/range.js
140
184
  /**
@@ -280,7 +324,7 @@
280
324
  if (!sel || !sel.rangeCount) return;
281
325
  let container = sel.getRangeAt(0).commonAncestorContainer;
282
326
  if (container.nodeType === 3) container = container.parentElement;
283
- const uEl = container && container.closest && container.closest("u");
327
+ const uEl = container && container.closest("u");
284
328
  const nativeState = document.queryCommandState("underline");
285
329
  if (uEl && !nativeState) {
286
330
  const parent = uEl.parentNode;
@@ -300,7 +344,7 @@
300
344
  if (!sel || !sel.rangeCount) return;
301
345
  let sc = sel.getRangeAt(0).startContainer;
302
346
  if (sc.nodeType === 3) sc = sc.parentElement;
303
- const sEl = sc && sc.closest && (sc.closest("s") || sc.closest("strike"));
347
+ const sEl = sc && (sc.closest("s") || sc.closest("strike"));
304
348
  const nativeState = document.queryCommandState("strikeThrough");
305
349
  if (sEl && !nativeState) {
306
350
  const parent = sEl.parentNode;
@@ -337,7 +381,7 @@
337
381
  * Sets the font size (in pt or with unit) for the selection.
338
382
  * Uses a span-based approach to set px sizes precisely.
339
383
  * @param {string} size - e.g. '14px'
340
- * @param {HTMLElement} [editable] - scoping element to avoid touching nodes outside this editor
384
+ * @param {HTMLElement|Document} [editable] - scoping element to avoid touching nodes outside this editor
341
385
  */
342
386
  function fontSize(size, editable = document) {
343
387
  const sel = window.getSelection();
@@ -419,7 +463,7 @@
419
463
  if (sel && sel.rangeCount) {
420
464
  let container = sel.getRangeAt(0).commonAncestorContainer;
421
465
  if (container.nodeType === 3) container = container.parentElement;
422
- const checkLi = container && container.closest && container.closest(".an-checklist li");
466
+ const checkLi = container && container.closest(".an-checklist li");
423
467
  if (checkLi) {
424
468
  _checklistItemToP(checkLi);
425
469
  return;
@@ -541,15 +585,15 @@
541
585
  /**
542
586
  * Wraps the selection in an inline <code> element, or unwraps it if the
543
587
  * cursor is already inside a <code> that is not inside a <pre>.
544
- * @param {HTMLElement} [editable]
588
+ * @param {HTMLElement} [_editable]
545
589
  */
546
- function toggleInlineCode(editable) {
590
+ function toggleInlineCode(_editable) {
547
591
  const sel = window.getSelection();
548
592
  if (!sel || !sel.rangeCount) return;
549
593
  const range = sel.getRangeAt(0);
550
594
  let container = range.commonAncestorContainer;
551
595
  if (container.nodeType === 3) container = container.parentElement;
552
- const codeEl = container && container.closest ? container.closest("code") : null;
596
+ const codeEl = container && container.closest("code");
553
597
  if (codeEl && !codeEl.closest("pre")) {
554
598
  const parent = codeEl.parentNode;
555
599
  const prevSibling = codeEl.previousSibling;
@@ -604,7 +648,7 @@
604
648
  if (!sel || !sel.rangeCount) return false;
605
649
  let sc = sel.getRangeAt(0).startContainer;
606
650
  if (sc.nodeType === 3) sc = sc.parentElement;
607
- const code = sc && sc.closest ? sc.closest("code") : null;
651
+ const code = sc && sc.closest("code");
608
652
  return !!(code && !code.closest("pre"));
609
653
  }
610
654
  /**
@@ -627,11 +671,11 @@
627
671
  const range = sel.getRangeAt(0);
628
672
  let container = range.commonAncestorContainer;
629
673
  if (container.nodeType === 3) container = container.parentElement;
630
- const ul = container.closest && container.closest(".an-checklist");
674
+ const ul = container && container.closest(".an-checklist");
631
675
  if (ul) {
632
676
  const selectedLis = Array.from(ul.querySelectorAll("li")).filter((li) => sel.containsNode(li, true));
633
677
  if (selectedLis.length > 0) {
634
- let firstP = null;
678
+ /** @type {HTMLElement|null} */ let firstP = null;
635
679
  selectedLis.forEach((li) => {
636
680
  const p = document.createElement("p");
637
681
  for (const child of li.childNodes) {
@@ -729,7 +773,7 @@
729
773
  if (blocks.length === 0) return;
730
774
  const newUl = document.createElement("ul");
731
775
  newUl.className = "an-checklist";
732
- let lastTextNode = null;
776
+ /** @type {Text|null} */ let lastTextNode = null;
733
777
  blocks.forEach((block) => {
734
778
  const li = document.createElement("li");
735
779
  const cb = document.createElement("input");
@@ -762,7 +806,7 @@
762
806
  if (!sel || !sel.rangeCount) return false;
763
807
  let container = sel.getRangeAt(0).commonAncestorContainer;
764
808
  if (container.nodeType === 3) container = container.parentElement;
765
- return !!(container && container.closest && container.closest(".an-checklist li"));
809
+ return !!(container && container.closest(".an-checklist li"));
766
810
  }
767
811
  //#endregion
768
812
  //#region src/js/module/Buttons.js
@@ -773,12 +817,14 @@
773
817
  */
774
818
  /**
775
819
  * @typedef {object} DropdownDef
776
- * @property {string} name - unique identifier
777
- * @property {'select'} type - discriminator for Toolbar renderer
820
+ * @property {string} name - unique identifier
821
+ * @property {'select'} type - discriminator for Toolbar renderer
778
822
  * @property {string} tooltip
779
- * @property {string[]} [items] - overridden at render time from options
780
- * @property {Function} action - called with (context, value)
781
- * @property {Function} [getValue] - called with (context) to get current value
823
+ * @property {Array<string|{value:string,label:string,disabled?:boolean}>} [items] - overridden at render time from options
824
+ * @property {Function} action - called with (context, value)
825
+ * @property {Function} [getValue] - called with (context) to get current value
826
+ * @property {string} [selectClass] - extra CSS class(es) for the <select>
827
+ * @property {string} [placeholder] - placeholder text for the empty option
782
828
  */
783
829
  /**
784
830
  * @typedef {object} ButtonDef
@@ -846,7 +892,7 @@
846
892
  if (!sel || !sel.rangeCount) return false;
847
893
  let sc = sel.getRangeAt(0).startContainer;
848
894
  if (sc.nodeType === 3) sc = sc.parentElement;
849
- return !!(sc && sc.closest && sc.closest("u"));
895
+ return !!(sc && sc.closest("u"));
850
896
  });
851
897
  var strikeBtn = btn("strikethrough", "strikethrough", "Strikethrough", () => strikethrough(), () => document.queryCommandState("strikeThrough"));
852
898
  var superscriptBtn = btn("superscript", "superscript", "Superscript", () => superscript(), () => document.queryCommandState("superscript"));
@@ -908,9 +954,9 @@
908
954
  const sel = window.getSelection();
909
955
  if (sel && sel.rangeCount) {
910
956
  let el = sel.getRangeAt(0).startContainer;
911
- if (el.nodeType === 3) el = el.parentElement;
957
+ if (el && el.nodeType === 3) el = el.parentElement;
912
958
  while (el && el.nodeType === 1 && !el.style.fontSize) el = el.parentElement;
913
- const size = el && el.style && el.style.fontSize ? el.style.fontSize : "";
959
+ const size = el && el.style.fontSize ? el.style.fontSize : "";
914
960
  if (size) return size;
915
961
  }
916
962
  const editable = ctx && ctx.layoutInfo && ctx.layoutInfo.editable;
@@ -922,13 +968,6 @@
922
968
  }
923
969
  };
924
970
  var removeFormatBtn = btn("removeFormat", "remove-format", "Remove Format", () => execCommand("removeFormat"));
925
- btn("direction", "direction", "Toggle Text Direction (LTR / RTL)", (ctx) => {
926
- const editable = ctx.layoutInfo.editable;
927
- const next = (editable.getAttribute("dir") || "ltr") === "ltr" ? "rtl" : "ltr";
928
- editable.setAttribute("dir", next);
929
- editable.style.textAlign = next === "rtl" ? "right" : "left";
930
- ctx.invoke("editor.afterCommand");
931
- });
932
971
  /** @type {DropdownDef} */
933
972
  var fontFamilyBtn = {
934
973
  name: "fontFamily",
@@ -1035,8 +1074,11 @@
1035
1074
  "TH"
1036
1075
  ]);
1037
1076
  let el = sel.getRangeAt(0).startContainer;
1038
- if (el.nodeType === 3) el = el.parentElement;
1039
- while (el && !BLOCKS.has(el.tagName)) el = el.parentElement;
1077
+ if (el && el.nodeType === 3) el = el.parentElement;
1078
+ while (el && !BLOCKS.has(
1079
+ /** @type {Element} */
1080
+ el.tagName
1081
+ )) el = el.parentElement;
1040
1082
  if (!el) return "";
1041
1083
  return el.style.lineHeight || getComputedStyle(el).lineHeight || "";
1042
1084
  } catch {
@@ -1048,7 +1090,6 @@
1048
1090
  var fullscreenBtn = btn("fullscreen", "expand", "Fullscreen", (ctx) => ctx.invoke("fullscreen.toggle"), (ctx) => ctx.invoke("fullscreen.isActive"));
1049
1091
  var shortcutsBtn = btn("shortcuts", "keyboard", "Keyboard Shortcuts (Ctrl+Shift+/)", (ctx) => ctx.invoke("shortcutsDialog.show"));
1050
1092
  var findBtn = btn("find", "search", "Find (Ctrl+F)", (ctx) => ctx.invoke("findReplace.show", "find"));
1051
- btn("findReplace", "find-replace", "Find & Replace (Ctrl+H)", (ctx) => ctx.invoke("findReplace.show", "replace"));
1052
1093
  var inlineCodeBtn = btn("inlineCode", "inline-code", "Inline Code (Ctrl+`)", (ctx) => ctx.invoke("editor.inlineCode"), () => isInlineCode());
1053
1094
  var checklistBtn = btn("checklist", "checklist", "Checklist", (ctx) => ctx.invoke("editor.toggleChecklist"), () => isInChecklist());
1054
1095
  var printBtn = btn("print", "print", "Print", (ctx) => ctx.invoke("editor.print"));
@@ -1060,47 +1101,64 @@
1060
1101
  */
1061
1102
  /**
1062
1103
  * @typedef {object} AsnOptions
1063
- * @property {string} [placeholder] - Placeholder text when editor is empty
1064
- * @property {number} [height] - Editor height in px (min)
1065
- * @property {number} [minHeight] - Minimum height in px
1066
- * @property {number} [maxHeight] - Maximum height in px (0 = unlimited)
1067
- * @property {boolean} [focus] - Auto-focus on init
1068
- * @property {boolean} [resizable] - Show resize handle
1069
- * @property {Array} [toolbar] - Toolbar button group config
1070
- * @property {boolean} [pasteAsPlainText] - Force plain-text paste
1071
- * @property {boolean} [pasteCleanHTML] - Sanitise HTML on paste
1104
+ * @property {string} [placeholder] - Placeholder text when editor is empty
1105
+ * @property {number} [height] - Editor height in px (min)
1106
+ * @property {number} [minHeight] - Minimum height in px
1107
+ * @property {number} [maxHeight] - Maximum height in px (0 = unlimited)
1108
+ * @property {boolean} [focus] - Auto-focus on init
1109
+ * @property {boolean} [resizable] - Show resize handle
1110
+ * @property {Array} [toolbar] - Toolbar button group config
1111
+ * @property {boolean} [useBootstrap] - Use Bootstrap button classes on toolbar buttons
1112
+ * @property {string} [toolbarButtonClass] - CSS classes for Bootstrap toolbar buttons
1113
+ * @property {boolean} [useFontAwesome] - Use Font Awesome icons (default: true)
1114
+ * @property {string} [fontAwesomeClass] - Font Awesome prefix class, e.g. 'fas' or 'fa-solid'
1115
+ * @property {boolean} [pasteAsPlainText] - Force plain-text paste
1116
+ * @property {boolean} [pasteCleanHTML] - Sanitise HTML on paste
1072
1117
  * @property {boolean} [pasteStripAttributes] - Strip class/style/data-* from pasted HTML (default: false)
1073
- * @property {boolean} [allowImageUpload] - Allow file upload in image dialog
1074
- * @property {number} [maxImageSize] - Max upload size in MB
1075
- * @property {number} [tabSize] - Spaces per tab in non-list context
1076
- * @property {Function} [onChange] - Callback on content change
1077
- * @property {Function} [onFocus] - Callback on focus
1078
- * @property {Function} [onBlur] - Callback on blur
1079
- * @property {Function} [onImageUpload] - Custom upload handler: (files) => void
1080
- * @property {boolean} [stickyToolbar] - Stick the toolbar to the viewport top when scrolling
1081
- * @property {number} [stickyToolbarOffset] - Top offset in px for sticky toolbar (e.g. fixed nav height)
1082
- * @property {string} [theme] - 'light' (default) | 'dark'
1083
- * @property {boolean} [codeHighlight] - Auto-load Prism.js for syntax highlighting of code blocks
1084
- * @property {string} [codeHighlightCDN] - CDN base URL for Prism assets (defaults to cdnjs)
1085
- * @property {boolean} [markdownPaste] - Convert pasted Markdown text to HTML (default: true)
1086
- * @property {boolean} [readOnly] - Start editor in read-only / non-editable mode
1087
- * @property {boolean} [spellcheck] - Enable browser spellcheck in the editable area (default: true)
1088
- * @property {string} [direction] - Text direction: 'ltr' (default) | 'rtl'
1089
- * @property {string} [toolbarOverflow] - Toolbar overflow strategy: 'wrap' (default) | 'scroll'
1090
- * @property {boolean} [autoSave] - Auto-save content to localStorage on change
1091
- * @property {string} [autoSaveKey] - localStorage key used for auto-save (default: 'autumnnote-autosave')
1092
- * @property {number} [maxChars] - Maximum character count (0 = unlimited). Shows warning in statusbar.
1093
- * @property {number} [maxWords] - Maximum word count (0 = unlimited). Shows warning in statusbar.
1094
- * @property {boolean} [tableHeaderRow] - Insert a header row (<thead><th>) when creating tables
1095
- * @property {Function} [onPaste] - Callback fired on every paste: ({ text, html }) => void
1096
- * @property {Function} [onSelectionChange] - Callback fired on cursor/selection change: (context) => void
1097
- * @property {string[]} [colorSwatches] - Custom brand colour swatches prepended to the colour-picker palette
1118
+ * @property {boolean} [allowImageUpload] - Allow file upload in image dialog
1119
+ * @property {number} [maxImageSize] - Max upload size in MB
1120
+ * @property {number} [tabSize] - Spaces per tab in non-list context
1121
+ * @property {number} [historyLimit] - Maximum undo/redo history steps
1122
+ * @property {string} [defaultFontFamily] - Default font family applied to the editable area on init
1123
+ * @property {string} [defaultFontSize] - Default font size applied to the editable area on init (e.g. '14px')
1124
+ * @property {string[]} [fontFamilies] - Font families shown in the font-family toolbar dropdown
1125
+ * @property {Function} [onChange] - Callback on content change
1126
+ * @property {Function} [onFocus] - Callback on focus
1127
+ * @property {Function} [onBlur] - Callback on blur
1128
+ * @property {Function} [onInit] - Callback after the editor has initialised
1129
+ * @property {Function} [onImageUpload] - Custom upload handler: (files) => void
1130
+ * @property {Function} [onImageError] - Callback when an image upload error occurs
1131
+ * @property {boolean} [stickyToolbar] - Stick the toolbar to the viewport top when scrolling
1132
+ * @property {number} [stickyToolbarOffset] - Top offset in px for sticky toolbar (e.g. fixed nav height)
1133
+ * @property {string} [theme] - 'light' (default) | 'dark'
1134
+ * @property {boolean} [codeHighlight] - Auto-load Prism.js for syntax highlighting of code blocks
1135
+ * @property {string} [codeHighlightCDN] - CDN base URL for Prism assets (defaults to cdnjs)
1136
+ * @property {boolean} [markdownPaste] - Convert pasted Markdown text to HTML (default: true)
1137
+ * @property {boolean} [readOnly] - Start editor in read-only / non-editable mode
1138
+ * @property {boolean} [spellcheck] - Enable browser spellcheck in the editable area (default: true)
1139
+ * @property {string} [direction] - Text direction: 'ltr' (default) | 'rtl'
1140
+ * @property {string} [toolbarOverflow] - Toolbar overflow strategy: 'wrap' (default) | 'scroll'
1141
+ * @property {boolean} [autoSave] - Auto-save content to localStorage on change
1142
+ * @property {string} [autoSaveKey] - localStorage key used for auto-save (default: 'autumnnote-autosave')
1143
+ * @property {number} [maxChars] - Maximum character count (0 = unlimited). Shows warning in statusbar.
1144
+ * @property {number} [maxWords] - Maximum word count (0 = unlimited). Shows warning in statusbar.
1145
+ * @property {boolean} [tableHeaderRow] - Insert a header row (<thead><th>) when creating tables
1146
+ * @property {Function} [onPaste] - Callback fired on every paste: ({ text, html }) => void
1147
+ * @property {Function} [onSelectionChange] - Callback fired on cursor/selection change: (context) => void
1148
+ * @property {string[]} [colorSwatches] - Custom brand colour swatches prepended to the colour-picker palette
1098
1149
  * @property {Function} [onDestroy] - Callback fired when the editor is destroyed: (context) => void
1099
1150
  * @property {Function} [onCharLimitReached] - Callback fired when the character limit is hit: (context) => void
1100
1151
  * @property {Function} [onWordLimitReached] - Callback fired when the word limit is hit: (context) => void
1101
- * @property {string} [focusColor] - Custom focus ring colour, e.g. '#f97316'. Overrides the default blue.
1152
+ * @property {string} [focusColor] - Custom focus ring colour, e.g. '#f97316'. Overrides the default blue.
1153
+ * @property {boolean} [autoSaveRestore] - Show a restore banner when a previously auto-saved draft exists
1154
+ * @property {number} [autoSaveRestoreTimeout] - Maximum age in days for a draft to be offered for restore (0 = no expiry)
1155
+ * @property {Function} [onAutoSaveRestore] - Callback fired after the user chooses to restore a draft
1156
+ * @property {boolean} [markdownShortcuts] - Convert markdown syntax typed inline to HTML
1157
+ * @property {boolean} [bubbleToolbar] - Show a mini floating toolbar above text selections
1158
+ * @property {string[]} [bubbleToolbarItems] - Button names for the bubble toolbar
1159
+ * @property {object|null} [mention] - @mention configuration (onSearch, minChars, ...)
1160
+ * @property {string} [lang] - Display language or partial locale object override
1102
1161
  */
1103
- /** @type {AsnOptions} */
1104
1162
  var defaultOptions = {
1105
1163
  placeholder: "",
1106
1164
  height: 200,
@@ -1295,6 +1353,7 @@
1295
1353
  chooseHighlightColor: "Choose highlight color",
1296
1354
  customColor: "Custom color",
1297
1355
  insertTableLabel: "Insert Table",
1356
+ /** Map of paragraph-style value → label (only values needing translation) */
1298
1357
  paragraphItems: {
1299
1358
  p: "Normal",
1300
1359
  blockquote: "Quote",
@@ -1337,6 +1396,7 @@
1337
1396
  widthPlaceholder: "560",
1338
1397
  insertBtn: "Insert",
1339
1398
  cancelBtn: "Cancel",
1399
+ /** @param {string} type */
1340
1400
  detected: (type) => `Detected: ${type}`,
1341
1401
  unknownFormat: "Unknown format — will try direct video embed",
1342
1402
  invalidUrl: "Invalid URL — please enter a valid video link."
@@ -1499,9 +1559,13 @@
1499
1559
  },
1500
1560
  statusbar: {
1501
1561
  resizeHandle: "Resize editor",
1562
+ /** @param {number} n */
1502
1563
  words: (n) => `Words: ${n}`,
1564
+ /** @param {number} n @param {number} max */
1503
1565
  wordsLimit: (n, max) => `Words: ${n}/${max}`,
1566
+ /** @param {number} n */
1504
1567
  chars: (n) => `Chars: ${n}`,
1568
+ /** @param {number} n @param {number} max */
1505
1569
  charsLimit: (n, max) => `Chars: ${n}/${max}`
1506
1570
  },
1507
1571
  tooltips: {
@@ -1554,6 +1618,8 @@
1554
1618
  rowHeight: "Row Height",
1555
1619
  tableBorderWidth: "Table Border Width",
1556
1620
  deleteTable: "Delete Table",
1621
+ cellBackground: "Cell Background",
1622
+ noShading: "No Shading",
1557
1623
  columnWidthPx: "Column Width (px)",
1558
1624
  rowHeightPx: "Row Height (px)",
1559
1625
  tableBorderWidthPx: "Table Border Width (px)",
@@ -1574,7 +1640,9 @@
1574
1640
  }
1575
1641
  },
1576
1642
  errors: {
1643
+ /** @param {string} type */
1577
1644
  imageFormat: (type) => `Format "${type}" is not supported for display in web browsers. Please convert to JPEG, PNG, or WebP first.`,
1645
+ /** @param {number} maxSize */
1578
1646
  imageSize: (maxSize) => `Image file is too large. Maximum allowed size is ${maxSize} MB.`
1579
1647
  }
1580
1648
  };
@@ -1907,6 +1975,8 @@
1907
1975
  rowHeight: "Chiều cao hàng",
1908
1976
  tableBorderWidth: "Độ rộng viền bảng",
1909
1977
  deleteTable: "Xóa bảng",
1978
+ cellBackground: "Màu Nền Ô",
1979
+ noShading: "Xóa Màu Nền",
1910
1980
  columnWidthPx: "Chiều rộng cột (px)",
1911
1981
  rowHeightPx: "Chiều cao hàng (px)",
1912
1982
  tableBorderWidthPx: "Độ rộng viền bảng (px)",
@@ -4108,7 +4178,10 @@
4108
4178
  else if (options.minHeight) editable.style.minHeight = `${options.minHeight}px`;
4109
4179
  if (options.maxHeight) editable.style.maxHeight = `${options.maxHeight}px`;
4110
4180
  container.appendChild(editable);
4111
- if (options.theme === "dark") container.classList.add("an-theme-dark");
4181
+ if (options.theme === "dark") {
4182
+ container.classList.add("an-theme-dark");
4183
+ document.body.classList.add("an-theme-dark");
4184
+ }
4112
4185
  if (options.readOnly) {
4113
4186
  container.classList.add("an-disabled");
4114
4187
  editable.querySelectorAll("ul.an-checklist input[type=\"checkbox\"]").forEach((cb) => {
@@ -4146,7 +4219,7 @@
4146
4219
  constructor(editable, limit = 100) {
4147
4220
  this.editable = editable;
4148
4221
  this._limit = limit;
4149
- /** @type {Array<{html: string, range: {sc: string, so: number, ec: string, eo: number}|null}>} */
4222
+ /** @type {Array<{html: string, images?: Record<string,string>, sel: {start: number, end: number}|null}>} */
4150
4223
  this.stack = [];
4151
4224
  this.stackOffset = -1;
4152
4225
  this._savePoint();
@@ -4345,8 +4418,7 @@
4345
4418
  * Build an HTML table with the given number of columns and rows, optionally including a header row.
4346
4419
  * @param {number} cols - Number of columns in each row.
4347
4420
  * @param {number} rows - Total number of rows to create (including header when `headerRow` is true).
4348
- * @param {{ headerRow?: boolean }} [opts] - Options object.
4349
- * @param {boolean} [opts.headerRow=false] - When true and `rows > 0`, creates a header row (`<thead>`) plus body rows for the remainder.
4421
+ * @param {{ headerRow?: boolean }} [opts] - Options: `headerRow` creates a `<thead>` when true.
4350
4422
  * @returns {HTMLTableElement} The constructed `<table>` element with a `<tbody>` and optional `<thead>`; each cell contains a `<br>` placeholder.
4351
4423
  */
4352
4424
  function createTable(cols, rows, opts = {}) {
@@ -4380,7 +4452,6 @@
4380
4452
  * @param {number} cols - Number of columns for the new table.
4381
4453
  * @param {number} rows - Number of rows for the new table.
4382
4454
  * @param {{ headerRow?: boolean }} [opts] - Options for table creation.
4383
- * @param {boolean} [opts.headerRow=false] - If true, include a header row as the first row.
4384
4455
  */
4385
4456
  function insertTable(cols, rows, opts = {}) {
4386
4457
  if (cols <= 0 || rows <= 0) return;
@@ -4403,7 +4474,7 @@
4403
4474
  "PRE"
4404
4475
  ]);
4405
4476
  let anchor = range.startContainer;
4406
- if (anchor.nodeType === 3) anchor = anchor.parentElement;
4477
+ if (anchor && anchor.nodeType === 3) anchor = anchor.parentElement;
4407
4478
  while (anchor && !BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentElement) anchor = anchor.parentElement;
4408
4479
  if (anchor && BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentNode) {
4409
4480
  anchor.after(table);
@@ -4521,7 +4592,7 @@
4521
4592
  const textNode = r.startContainer;
4522
4593
  if (r.startOffset === 0 && isFAIcon(textNode.previousSibling)) {
4523
4594
  event.preventDefault();
4524
- textNode.previousSibling.remove();
4595
+ /** @type {ChildNode} */ textNode.previousSibling.remove();
4525
4596
  return true;
4526
4597
  }
4527
4598
  if (r.startOffset === 1 && textNode.textContent === "​" && isFAIcon(textNode.previousSibling)) {
@@ -4667,7 +4738,7 @@
4667
4738
  }
4668
4739
  return false;
4669
4740
  }
4670
- const videoWrapper = el && el.closest && el.closest(".an-video-wrapper");
4741
+ const videoWrapper = el && el.closest(".an-video-wrapper");
4671
4742
  if (videoWrapper) {
4672
4743
  event.preventDefault();
4673
4744
  const p = document.createElement("p");
@@ -4681,7 +4752,7 @@
4681
4752
  sel.addRange(nr);
4682
4753
  return true;
4683
4754
  }
4684
- const checkLi = el && el.closest && el.closest(".an-checklist li");
4755
+ const checkLi = el && el.closest(".an-checklist li");
4685
4756
  if (checkLi) {
4686
4757
  event.preventDefault();
4687
4758
  const ul = checkLi.closest(".an-checklist");
@@ -4781,8 +4852,9 @@
4781
4852
  function _domToMd(node, depth = 0) {
4782
4853
  if (node.nodeType === 3) return node.textContent.replace(/\s+/g, " ");
4783
4854
  if (node.nodeType !== 1) return "";
4784
- const tag = node.nodeName.toLowerCase();
4785
- const inner = () => Array.from(node.childNodes).map((n) => _domToMd(n, depth)).join("");
4855
+ const el = node;
4856
+ const tag = el.nodeName.toLowerCase();
4857
+ const inner = () => Array.from(el.childNodes).map((n) => _domToMd(n, depth)).join("");
4786
4858
  switch (tag) {
4787
4859
  case "p":
4788
4860
  case "div": return `\n\n${inner()}\n\n`;
@@ -4800,34 +4872,34 @@
4800
4872
  case "del":
4801
4873
  case "s":
4802
4874
  case "strike": return `~~${inner()}~~`;
4803
- case "sup": return `^${inner()}`;
4804
- case "sub": return `~${inner()}`;
4875
+ case "sup": return `^${inner()}^`;
4876
+ case "sub": return `~${inner()}~`;
4805
4877
  case "code":
4806
- if (node.closest("pre")) return inner();
4878
+ if (el.closest("pre")) return inner();
4807
4879
  return `\`${inner()}\``;
4808
4880
  case "pre": {
4809
- const codeEl = node.querySelector("code");
4881
+ const codeEl = el.querySelector("code");
4810
4882
  const langMatch = (codeEl && codeEl.className || "").match(/language-(\S+)/);
4811
- return `\n\n\`\`\`${langMatch ? langMatch[1] : ""}\n${(codeEl || node).textContent || ""}\n\`\`\`\n\n`;
4883
+ return `\n\n\`\`\`${langMatch ? langMatch[1] : ""}\n${(codeEl || el).textContent || ""}\n\`\`\`\n\n`;
4812
4884
  }
4813
4885
  case "blockquote": return `\n\n${inner().trim().split("\n").map((l) => `> ${l}`).join("\n")}\n\n`;
4814
4886
  case "a": {
4815
- const href = node.getAttribute("href") || "";
4887
+ const href = el.getAttribute("href") || "";
4816
4888
  return `[${inner()}](${href})`;
4817
4889
  }
4818
4890
  case "img": {
4819
- const src = node.getAttribute("src") || "";
4820
- return `![${node.getAttribute("alt") || ""}](${src})`;
4891
+ const src = el.getAttribute("src") || "";
4892
+ return `![${el.getAttribute("alt") || ""}](${src})`;
4821
4893
  }
4822
4894
  case "ul": {
4823
- const items = Array.from(node.querySelectorAll(":scope > li"));
4895
+ const items = Array.from(el.querySelectorAll(":scope > li"));
4824
4896
  if (!items.length) return inner();
4825
4897
  const indent = " ".repeat(depth);
4826
4898
  const lines = items.map((li) => `${indent}- ${_domToMd(li, depth + 1).trim()}`).join("\n");
4827
4899
  return depth === 0 ? `\n\n${lines}\n\n` : `\n${lines}`;
4828
4900
  }
4829
4901
  case "ol": {
4830
- const items = Array.from(node.querySelectorAll(":scope > li"));
4902
+ const items = Array.from(el.querySelectorAll(":scope > li"));
4831
4903
  if (!items.length) return inner();
4832
4904
  const indent = " ".repeat(depth);
4833
4905
  const lines = items.map((li, i) => `${indent}${i + 1}. ${_domToMd(li, depth + 1).trim()}`).join("\n");
@@ -4836,7 +4908,7 @@
4836
4908
  case "li": return inner();
4837
4909
  case "hr": return "\n\n---\n\n";
4838
4910
  case "table": {
4839
- const rows = Array.from(node.querySelectorAll("tr"));
4911
+ const rows = Array.from(el.querySelectorAll("tr"));
4840
4912
  if (!rows.length) return inner();
4841
4913
  const cellTexts = rows.map((tr) => Array.from(tr.querySelectorAll("th, td")).map((c) => c.textContent.trim().replace(/\|/g, "\\|")));
4842
4914
  const cols = Math.max(...cellTexts.map((r) => r.length));
@@ -4984,6 +5056,47 @@
4984
5056
  return String(v).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4985
5057
  }
4986
5058
  //#endregion
5059
+ //#region src/js/core/detectLang.js
5060
+ /**
5061
+ * detectLang.js — Heuristic programming-language detection for code snippets.
5062
+ *
5063
+ * Returns a Prism.js language identifier or null when no language can be
5064
+ * determined with reasonable confidence.
5065
+ *
5066
+ * Detection order (conflicts in parentheses):
5067
+ * TypeScript → Rust → PHP → Java → Kotlin → Swift → Go
5068
+ * → JavaScript → HTML → CSS → JSON → SQL → Python → Ruby
5069
+ * → Bash → C++ → C# → C → XML
5070
+ *
5071
+ * @param {string} code
5072
+ * @returns {string|null}
5073
+ */
5074
+ function detectLang(code) {
5075
+ if (!code || !code.trim()) return null;
5076
+ const s = code.trim();
5077
+ if (/(:\s*(string|number|boolean|void|never|any|unknown)\b|interface\s+\w+\s*\{|type\s+\w+\s*[=<(]|<\w+>\s*[;,)]|readonly\s+\w|enum\s+\w+\s*\{|\?\s*:\s*\w|as\s+\w+\s*[;,)\]])/.test(s)) return "typescript";
5078
+ if (/\bprintln!\s*\(|\bprint!\s*\(|\bfn\s+\w+\s*(<[^>]*>)?\s*\(|\blet\s+mut\s|\bpub\s+fn\s|\buse\s+std::|\bimpl\s+\w+|\bOption<|\bResult<\w+/.test(s)) return "rust";
5079
+ if (/(<\?php\b|<\?=|\becho\s+.*\$\w|\$this->|\$\w+\s*=\s*\w|\bforeach\s*\(\s*\$|Illuminate\\)/.test(s)) return "php";
5080
+ if (/\bpublic\s+(class|static|void|int|String)\s+\w|System\.out\.(print|println)\s*\(|@(Override|Autowired|Component|Service|Controller)\b|import\s+java\.(util|io|lang|net)\.|throws\s+\w+Exception/.test(s)) return "java";
5081
+ if (/\bfun\s+\w+\s*\(|\bdata\s+class\s+\w+|\bcompanion\s+object\b|\bval\s+\w+\s*:\s*\w|\bprintln\s*\(/.test(s)) return "kotlin";
5082
+ if (/\bguard\s+(let|var)\b|\bprotocol\s+\w+\s*\{|\bextension\s+\w+|\bfunc\s+\w+[^(]*\([^)]*\)\s*->\s*\w|\blet\s+\w+\s*:\s*[A-Z]\w*|\bSwiftUI\b/.test(s)) return "swift";
5083
+ if (/\bpackage\s+\w+\b|\bfmt\.(Print|Println|Sprintf|Errorf|Fprintf)\s*\(|:=\s*\w|\bgoroutine\b|\bchan\s+\w|\bgo\s+func\b/.test(s)) return "go";
5084
+ if (/\b(const\s+\w|let\s+\w+\s*=|var\s+\w+\s*=|function\s+\w|\=>\s*[{(]|import\s+.*\bfrom\b\s*['"]|require\s*\(|console\.(log|error|warn|info)|document\.\w|window\.\w|async\s+function|\bPromise\b|React\.|useState\s*\(|\.then\s*\()/.test(s)) return "javascript";
5085
+ if (/^<!DOCTYPE html/i.test(s) || /<(html|head|body|div|section|article|nav|p|a|img|ul|ol|li|table|form|input|button|script|style)\b[^>]*>/i.test(s)) return "html";
5086
+ if (/(^|\n)\s*(\/\/\s+\S|&[:.[\w]|\$\w+\s*:|@(mixin|include|extend|each|if|for|use|forward)\b|#\{)/.test(s) && /[\w#.*&[\]:(),>+~ -]+\s*\{/.test(s)) return "scss";
5087
+ if (/(^|\n)\s*[\w#.*:[\]&, +-]+\s*\{[^}]*[\w-]+\s*:[^{}:;]+[;}\n]/m.test(s) && !/<\w|function\s|def\s|:\s*(string|number)/.test(s)) return "css";
5088
+ if (/^\s*[{[]/.test(s) && /"\w[\w\s-]*"\s*:/.test(s) && !/\bfunction\b|\bdef\b/.test(s)) return "json";
5089
+ if (/(^|\n)\s*(SELECT\s|INSERT\s+INTO|UPDATE\s+\w|DELETE\s+FROM|CREATE\s+(TABLE|DATABASE|INDEX|VIEW)|DROP\s+(TABLE|DATABASE)|ALTER\s+TABLE|WITH\s+\w+\s+AS\s*\()/im.test(s)) return "sql";
5090
+ if (/\bdef\s+\w+\s*\([^)]*\)\s*:|(^|\n)\s*class\s+\w+.*:\s*$|(^|\n)\s*import\s+\w|(^|\n)\s*from\s+\w+\s+import\s+|\bprint\s*\(|if\s+__name__\s*==\s*['"]__main__['"]/m.test(s)) return "python";
5091
+ if (/\bputs\s+\S|\battr_(accessor|reader|writer)\s|\.each\s+do\s*\|\w+\s*\||\bdo\s*\|\w+\s*\|.*\bend\b|\bdef\s+\w+[^:]*\n[\s\S]*?\bend\b/.test(s)) return "ruby";
5092
+ if (/^#!.*\/(ba|z|da|fi|k)?sh\b/m.test(s) || /\b(echo\s+["']|grep\s+|awk\s+|sed\s+['"\\/-]|chmod\s+|sudo\s+|apt(-get)?\s+install|brew\s+install|npm\s+(install|run|start|build)|pip\s+(install|3\s)|docker\s+(run|build|compose)|kubectl\s+|git\s+(clone|add|commit|push|pull|checkout))\b/.test(s)) return "bash";
5093
+ if (/\bcout\s*<<|\bcin\s*>>|using\s+namespace\s+std\b|std::\w|\btemplate\s*<\w|\b#include\s*<(iostream|vector|map|set|algorithm|string|memory)>/.test(s)) return "cpp";
5094
+ if (/\busing\s+System\b|Console\.(Write|WriteLine)\s*\(|\bget;\s*set;|\basync\s+Task[<\s]|IEnumerable<|\bLINQ\b|\.Select\s*\(|\.Where\s*\(/.test(s)) return "csharp";
5095
+ if (/\b#include\s*<(stdio|stdlib|string|math|time|ctype)\.h>|\bprintf\s*\(|\bscanf\s*\(|int\s+main\s*\(\s*(void|int\s+argc)|\bmalloc\s*\(|\bfree\s*\(/.test(s) && !/namespace|cout|cin|std::/.test(s)) return "c";
5096
+ if (/^<\?xml\s/i.test(s) || /xmlns:|<\/[\w:]+>/.test(s)) return "xml";
5097
+ return null;
5098
+ }
5099
+ //#endregion
4987
5100
  //#region src/js/module/Editor.js
4988
5101
  /**
4989
5102
  * Editor.js - Core editing command module
@@ -5038,7 +5151,8 @@
5038
5151
  if (!r.collapsed) return;
5039
5152
  const sc = r.startContainer;
5040
5153
  if (sc.nodeType !== Node.ELEMENT_NODE) return;
5041
- const li = sc.matches && sc.matches(".an-checklist li") ? sc : null;
5154
+ const scEl = sc;
5155
+ const li = scEl.matches(".an-checklist li") ? scEl : null;
5042
5156
  if (!li) return;
5043
5157
  const cb = li.querySelector("input[type=\"checkbox\"]");
5044
5158
  if (!cb) return;
@@ -5078,7 +5192,7 @@
5078
5192
  return;
5079
5193
  }
5080
5194
  const target = e.target;
5081
- if (target && (target.nodeName === "IFRAME" || target.closest && target.closest(".an-video-wrapper"))) e.preventDefault();
5195
+ if (target && (target.nodeName === "IFRAME" || target.closest(".an-video-wrapper"))) e.preventDefault();
5082
5196
  }), on(editable, "drop", (e) => {
5083
5197
  if (isReadOnly()) e.preventDefault();
5084
5198
  }));
@@ -5092,9 +5206,12 @@
5092
5206
  }
5093
5207
  let node = sel.getRangeAt(0).startContainer;
5094
5208
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
5095
- if (node && node.closest) if (node.closest("sup")) _compositionSupSub = "superscript";
5096
- else if (node.closest("sub")) _compositionSupSub = "subscript";
5097
- else _compositionSupSub = null;
5209
+ if (node) {
5210
+ const el = node;
5211
+ if (el.closest("sup")) _compositionSupSub = "superscript";
5212
+ else if (el.closest("sub")) _compositionSupSub = "subscript";
5213
+ else _compositionSupSub = null;
5214
+ }
5098
5215
  };
5099
5216
  const onCompositionEnd = () => {
5100
5217
  const tag = _compositionSupSub;
@@ -5104,7 +5221,8 @@
5104
5221
  if (!sel || !sel.rangeCount) return;
5105
5222
  let node = sel.getRangeAt(0).startContainer;
5106
5223
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
5107
- if (!(node && node.closest && (tag === "superscript" ? node.closest("sup") : node.closest("sub")))) document.execCommand(tag);
5224
+ const el = node;
5225
+ if (!(el && (tag === "superscript" ? el.closest("sup") : el.closest("sub")))) document.execCommand(tag);
5108
5226
  };
5109
5227
  this._disposers.push(on(editable, "compositionstart", onCompositionStart), on(editable, "compositionend", onCompositionEnd));
5110
5228
  }
@@ -5193,6 +5311,7 @@
5193
5311
  }
5194
5312
  afterCommand() {
5195
5313
  this._cleanOrphanedFigures();
5314
+ this._ensureTrailingParagraph();
5196
5315
  this.context.invoke("toolbar.refresh");
5197
5316
  this.context.invoke("statusbar.update");
5198
5317
  this._scheduleSnapshot();
@@ -5219,6 +5338,31 @@
5219
5338
  if (!fig.querySelector("img")) fig.parentNode.removeChild(fig);
5220
5339
  });
5221
5340
  }
5341
+ /**
5342
+ * Ensures the editable always ends with a plain paragraph so the cursor can
5343
+ * be placed after block elements that do not naturally allow it
5344
+ * (pre, blockquote, table, figure, ul, ol, hr).
5345
+ * Without this, clicking below the last such element does nothing.
5346
+ */
5347
+ _ensureTrailingParagraph() {
5348
+ const editable = this.context.layoutInfo.editable;
5349
+ if (!editable) return;
5350
+ const last = editable.lastElementChild;
5351
+ if (!last) return;
5352
+ if (new Set([
5353
+ "PRE",
5354
+ "BLOCKQUOTE",
5355
+ "TABLE",
5356
+ "FIGURE",
5357
+ "UL",
5358
+ "OL",
5359
+ "HR"
5360
+ ]).has(last.nodeName)) {
5361
+ const p = document.createElement("p");
5362
+ p.innerHTML = "<br>";
5363
+ editable.appendChild(p);
5364
+ }
5365
+ }
5222
5366
  focus() {
5223
5367
  this.context.layoutInfo.editable.focus();
5224
5368
  }
@@ -5402,10 +5546,24 @@
5402
5546
  this.context.print();
5403
5547
  }
5404
5548
  /**
5405
- * @param {string} tagName - e.g. 'h1', 'p', 'blockquote'
5549
+ * @param {string} tagName - e.g. 'h1', 'p', 'blockquote', 'pre'
5406
5550
  */
5407
5551
  formatBlock(tagName) {
5408
5552
  formatBlock(tagName);
5553
+ if (tagName === "pre") {
5554
+ const sel = window.getSelection();
5555
+ if (sel && sel.rangeCount > 0) {
5556
+ const container = sel.getRangeAt(0).commonAncestorContainer;
5557
+ const pre = container.nodeType === 1 ? container.closest("pre") : container.parentElement?.closest("pre");
5558
+ if (pre && !pre.getAttribute("data-language")) {
5559
+ const lang = detectLang(pre.textContent || "");
5560
+ if (lang) {
5561
+ this.context.invoke("codeTooltip.applyLanguage", pre, lang);
5562
+ return;
5563
+ }
5564
+ }
5565
+ }
5566
+ }
5409
5567
  this.afterCommand();
5410
5568
  }
5411
5569
  /**
@@ -5462,8 +5620,8 @@
5462
5620
  if (openInNewTab) {
5463
5621
  const link = this._getClosestAnchor();
5464
5622
  if (link) {
5465
- link.setAttribute("target", "_blank");
5466
- link.setAttribute("rel", "noopener noreferrer");
5623
+ /** @type {Element} */ link.setAttribute("target", "_blank");
5624
+ /** @type {Element} */ link.setAttribute("rel", "noopener noreferrer");
5467
5625
  }
5468
5626
  }
5469
5627
  }
@@ -5745,13 +5903,13 @@
5745
5903
  else openPopup();
5746
5904
  });
5747
5905
  const d2 = on(grid, "mouseover", (e) => {
5748
- const cell = e.target.closest(".an-table-cell");
5906
+ const cell = e.target?.closest(".an-table-cell");
5749
5907
  if (!cell) return;
5750
5908
  setHighlight(+cell.getAttribute("data-row"), +cell.getAttribute("data-col"));
5751
5909
  });
5752
5910
  const d3 = on(grid, "mouseleave", () => setHighlight(0, 0));
5753
5911
  const d4 = on(grid, "click", (e) => {
5754
- const cell = e.target.closest(".an-table-cell");
5912
+ const cell = e.target?.closest(".an-table-cell");
5755
5913
  if (!cell) return;
5756
5914
  const rows = +cell.getAttribute("data-row");
5757
5915
  const cols = +cell.getAttribute("data-col");
@@ -5914,11 +6072,17 @@
5914
6072
  e.preventDefault();
5915
6073
  });
5916
6074
  const d3b = on(swatches, "click", (e) => {
5917
- const sw = e.target.closest(".an-color-swatch");
5918
- if (sw) applyColor(sw.dataset.color);
6075
+ const sw = e.target?.closest(".an-color-swatch");
6076
+ if (sw) applyColor(
6077
+ /** @type {HTMLElement} */
6078
+ sw.dataset.color
6079
+ );
5919
6080
  });
5920
6081
  const d4 = on(colorInput, "change", (e) => {
5921
- applyColor(e.target.value);
6082
+ applyColor(
6083
+ /** @type {HTMLInputElement} */
6084
+ e.target.value
6085
+ );
5922
6086
  });
5923
6087
  const d5 = on(document, "click", (e) => {
5924
6088
  if (isOpen && !wrap.contains(e.target) && !popup.contains(e.target)) closePopup();
@@ -6053,15 +6217,17 @@
6053
6217
  this.el.querySelectorAll("button[data-btn]").forEach((btn) => {
6054
6218
  const def = btnMap.get(btn.getAttribute("data-btn"));
6055
6219
  if (def && typeof def.isActive === "function") btn.classList.toggle("active", !!def.isActive(this.context));
6056
- if (def && typeof def.isDisabled === "function") btn.disabled = !!def.isDisabled(this.context);
6220
+ if (def && typeof def.isDisabled === "function")
6221
+ /** @type {HTMLButtonElement} */ btn.disabled = !!def.isDisabled(this.context);
6057
6222
  });
6058
6223
  this.el.querySelectorAll("select[data-btn]").forEach((select) => {
6059
6224
  const def = btnMap.get(select.getAttribute("data-btn"));
6060
6225
  if (!def || typeof def.getValue !== "function") return;
6061
6226
  let raw = (def.getValue(this.context) || "").replace(/["']/g, "").trim();
6062
6227
  if (!raw) raw = this.options.defaultFontFamily || this.options.fontFamilies && this.options.fontFamilies[0] || "";
6063
- const matched = Array.from(select.options).find((opt) => opt.value && opt.value.toLowerCase() === raw.toLowerCase());
6064
- select.value = matched ? matched.value : "";
6228
+ const sel = select;
6229
+ const matched = Array.from(sel.options).find((opt) => opt.value && opt.value.toLowerCase() === raw.toLowerCase());
6230
+ sel.value = matched ? matched.value : "";
6065
6231
  });
6066
6232
  }
6067
6233
  /**
@@ -6855,8 +7021,13 @@
6855
7021
  "aria-label": L.ariaLabel
6856
7022
  });
6857
7023
  const box = createElement("div", { class: "an-dialog-box" });
7024
+ const header = createElement("div", { class: "an-dialog-header" });
7025
+ const iconEl = createElement("span", { class: "an-dialog-icon" });
7026
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>`;
6858
7027
  const title = createElement("h3", { class: "an-dialog-title" });
6859
7028
  title.textContent = L.title;
7029
+ header.appendChild(iconEl);
7030
+ header.appendChild(title);
6860
7031
  const urlLabel = createElement("label", { class: "an-label" });
6861
7032
  urlLabel.textContent = L.url;
6862
7033
  const urlInput = createElement("input", {
@@ -6901,8 +7072,9 @@
6901
7072
  cancelBtn.textContent = L.cancelBtn;
6902
7073
  btnRow.appendChild(insertBtn);
6903
7074
  btnRow.appendChild(cancelBtn);
6904
- box.append(title, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
7075
+ box.append(header, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
6905
7076
  overlay.appendChild(box);
7077
+ makeDraggable(header, box);
6906
7078
  const d1 = on(insertBtn, "click", () => this._onInsert());
6907
7079
  const d2 = on(cancelBtn, "click", () => this._close());
6908
7080
  const d3 = on(overlay, "click", (e) => {
@@ -7032,8 +7204,13 @@
7032
7204
  "aria-label": L.ariaLabel
7033
7205
  });
7034
7206
  const box = createElement("div", { class: "an-dialog-box" });
7207
+ const header = createElement("div", { class: "an-dialog-header" });
7208
+ const iconEl = createElement("span", { class: "an-dialog-icon" });
7209
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" 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="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`;
7035
7210
  const title = createElement("h3", { class: "an-dialog-title" });
7036
7211
  title.textContent = L.title;
7212
+ header.appendChild(iconEl);
7213
+ header.appendChild(title);
7037
7214
  const urlLabel = createElement("label", { class: "an-label" });
7038
7215
  urlLabel.textContent = L.imageUrl;
7039
7216
  const urlInput = createElement("input", {
@@ -7052,7 +7229,7 @@
7052
7229
  autocomplete: "off"
7053
7230
  });
7054
7231
  this._altInput = altInput;
7055
- box.append(title, urlLabel, urlInput, altLabel, altInput);
7232
+ box.append(header, urlLabel, urlInput, altLabel, altInput);
7056
7233
  const alignLabel = createElement("label", { class: "an-label" });
7057
7234
  alignLabel.textContent = L.alignment;
7058
7235
  const alignRow = createElement("div", { class: "an-align-row" });
@@ -7121,6 +7298,7 @@
7121
7298
  btnRow.appendChild(cancelBtn);
7122
7299
  box.append(btnRow);
7123
7300
  overlay.appendChild(box);
7301
+ makeDraggable(header, box);
7124
7302
  const d1 = on(insertBtn, "click", () => this._onInsert());
7125
7303
  const d2 = on(cancelBtn, "click", () => this._close());
7126
7304
  const d3 = on(overlay, "click", (e) => {
@@ -7258,8 +7436,13 @@
7258
7436
  "aria-label": L.ariaLabel
7259
7437
  });
7260
7438
  const box = createElement("div", { class: "an-dialog-box" });
7439
+ const header = createElement("div", { class: "an-dialog-header" });
7440
+ const iconEl = createElement("span", { class: "an-dialog-icon" });
7441
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg>`;
7261
7442
  const title = createElement("h3", { class: "an-dialog-title" });
7262
7443
  title.textContent = L.title;
7444
+ header.appendChild(iconEl);
7445
+ header.appendChild(title);
7263
7446
  const urlLabel = createElement("label", { class: "an-label" });
7264
7447
  urlLabel.textContent = L.videoUrl;
7265
7448
  const urlInput = createElement("input", {
@@ -7295,8 +7478,9 @@
7295
7478
  cancelBtn.textContent = L.cancelBtn;
7296
7479
  btnRow.appendChild(insertBtn);
7297
7480
  btnRow.appendChild(cancelBtn);
7298
- box.append(title, urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
7481
+ box.append(header, urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
7299
7482
  overlay.appendChild(box);
7483
+ makeDraggable(header, box);
7300
7484
  const d0 = on(urlInput, "input", () => {
7301
7485
  const info = this._parseVideoUrl(urlInput.value.trim());
7302
7486
  hintEl.textContent = info ? this.context.locale.videoDialog.detected(info.type) : urlInput.value ? this.context.locale.videoDialog.unknownFormat : "";
@@ -7479,7 +7663,7 @@
7479
7663
  };
7480
7664
  this._disposers.push(on(editable, "click", (e) => this._onEditorClick(e)), on(editable, "contextmenu", (e) => {
7481
7665
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
7482
- const img = e.target.closest("img");
7666
+ const img = e.target?.closest("img");
7483
7667
  if (img) this._select(img);
7484
7668
  }), 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 }));
7485
7669
  return this;
@@ -7889,12 +8073,12 @@
7889
8073
  document.body.appendChild(this._el);
7890
8074
  const editable = this.context.layoutInfo.editable;
7891
8075
  this._disposers.push(on(editable, "mouseover", (e) => {
7892
- const anchor = e.target.closest("a[href]");
8076
+ const anchor = e.target?.closest("a[href]");
7893
8077
  if (anchor && editable.contains(anchor)) this._scheduleShow(anchor);
7894
8078
  }), on(editable, "mouseout", (e) => {
7895
8079
  const to = e.relatedTarget;
7896
8080
  if (!to || !editable.contains(to) && !this._el.contains(to)) this._scheduleHide();
7897
- }));
8081
+ }), on(window, "scroll", () => this._hide(), { passive: true }), on(window, "resize", () => this._hide(), { passive: true }));
7898
8082
  return this;
7899
8083
  }
7900
8084
  destroy() {
@@ -8079,14 +8263,15 @@
8079
8263
  const editable = this.context.layoutInfo.editable;
8080
8264
  this._disposers.push(on(editable, "mouseover", (e) => {
8081
8265
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
8082
- const img = e.target.closest("img");
8266
+ const img = e.target?.closest("img");
8083
8267
  if (img && editable.contains(img) && !img.closest("a[href]")) this._scheduleShow(img);
8084
8268
  }, { passive: true }), on(editable, "mouseout", (e) => {
8085
8269
  const to = e.relatedTarget;
8086
8270
  if (!to || !editable.contains(to) && !this._el.contains(to)) this._scheduleHide();
8087
8271
  }, { passive: true }), on(document, "click", (e) => {
8088
- if (this._activeImg && !this._activeImg.contains(e.target) && !this._el.contains(e.target)) this._hide();
8089
- }));
8272
+ const et = e.target;
8273
+ if (this._activeImg && !this._activeImg.contains(et) && !this._el.contains(et)) this._hide();
8274
+ }), on(window, "scroll", () => this._hide(), { passive: true }), on(window, "resize", () => this._hide(), { passive: true }));
8090
8275
  return this;
8091
8276
  }
8092
8277
  destroy() {
@@ -8170,7 +8355,7 @@
8170
8355
  clearTimeout(this._hideTimer);
8171
8356
  this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY$3);
8172
8357
  }
8173
- _show(img) {
8358
+ _show(_img) {
8174
8359
  this._el.style.display = "flex";
8175
8360
  requestAnimationFrame(() => {
8176
8361
  if (this._activeImg) this._positionNear(this._activeImg);
@@ -8362,14 +8547,16 @@
8362
8547
  const editable = this.context.layoutInfo.editable;
8363
8548
  this._disposers.push(on(editable, "mouseover", (e) => {
8364
8549
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
8365
- const wrapper = e.target.closest(".an-video-wrapper");
8550
+ const target = e.target;
8551
+ const wrapper = target && target.closest ? target.closest(".an-video-wrapper") : null;
8366
8552
  if (wrapper && editable.contains(wrapper)) this._scheduleShow(wrapper);
8367
8553
  }, { passive: true }), on(editable, "mouseout", (e) => {
8368
8554
  const to = e.relatedTarget;
8369
8555
  if (!to || !editable.contains(to) && !this._el.contains(to)) this._scheduleHide();
8370
8556
  }, { passive: true }), on(document, "click", (e) => {
8371
- if (this._activeWrapper && !this._activeWrapper.contains(e.target) && !this._el.contains(e.target)) this._hide();
8372
- }));
8557
+ const target = e.target;
8558
+ if (this._activeWrapper && !this._activeWrapper.contains(target) && !this._el.contains(target)) this._hide();
8559
+ }), on(window, "scroll", () => this._hide(), { passive: true }), on(window, "resize", () => this._hide(), { passive: true }));
8373
8560
  return this;
8374
8561
  }
8375
8562
  destroy() {
@@ -8449,7 +8636,7 @@
8449
8636
  if (this._hideTimer) return;
8450
8637
  this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY$2);
8451
8638
  }
8452
- _show(wrapper) {
8639
+ _show(_wrapper) {
8453
8640
  this._el.style.display = "flex";
8454
8641
  requestAnimationFrame(() => {
8455
8642
  if (this._activeWrapper) this._positionNear(this._activeWrapper);
@@ -8673,8 +8860,35 @@
8673
8860
  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>`,
8674
8861
  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>`,
8675
8862
  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>`,
8676
- 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>`
8863
+ 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>`,
8864
+ cellShade: `<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"><path d="M19 11L8.93 3.36a1 1 0 0 0-1.29.08L3.22 7.8a1 1 0 0 0-.07 1.29L11 20"/><path d="m5 14 5-5"/><path d="M22 22a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2c0-1.5 2.5-5 3.5-5s3.5 3.5 3.5 5z"/></svg>`
8677
8865
  };
8866
+ var SHADE_PRESETS = [
8867
+ "#000000",
8868
+ "#434343",
8869
+ "#666666",
8870
+ "#999999",
8871
+ "#b7b7b7",
8872
+ "#cccccc",
8873
+ "#efefef",
8874
+ "#ffffff",
8875
+ "#ff0000",
8876
+ "#ff9900",
8877
+ "#ffff00",
8878
+ "#00ff00",
8879
+ "#00ffff",
8880
+ "#4a86e8",
8881
+ "#9900ff",
8882
+ "#ff00ff",
8883
+ "#f4cccc",
8884
+ "#fce5cd",
8885
+ "#fff2cc",
8886
+ "#d9ead3",
8887
+ "#d0e0e3",
8888
+ "#c9daf8",
8889
+ "#d9d2e9",
8890
+ "#ead1dc"
8891
+ ];
8678
8892
  var TableTooltip = class {
8679
8893
  /** @param {import('../Context.js').Context} context */
8680
8894
  constructor(context) {
@@ -8689,6 +8903,9 @@
8689
8903
  this._sizeApply = null;
8690
8904
  this._sizeTitleEl = null;
8691
8905
  this._sizeInputEl = null;
8906
+ this._shadePopover = null;
8907
+ this._shadeTitleEl = null;
8908
+ this._shadeColorStrip = null;
8692
8909
  this._selectMode = false;
8693
8910
  this._selectedCells = [];
8694
8911
  this._selectStart = null;
@@ -8701,6 +8918,8 @@
8701
8918
  document.body.appendChild(this._el);
8702
8919
  this._sizePopover = this._buildSizePopover();
8703
8920
  document.body.appendChild(this._sizePopover);
8921
+ this._shadePopover = this._buildCellShadePopover();
8922
+ document.body.appendChild(this._shadePopover);
8704
8923
  const editable = this.context.layoutInfo.editable;
8705
8924
  this._editable = editable;
8706
8925
  const onSelMousedown = (e) => {
@@ -8727,10 +8946,13 @@
8727
8946
  this._disposers.push(on(editable, "mousedown", onSelMousedown), on(editable, "mousemove", onSelMousemove), on(document, "mouseup", onSelMouseup));
8728
8947
  this._disposers.push(on(editable, "mouseover", (e) => {
8729
8948
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
8730
- const table = e.target.closest("table");
8949
+ const table = e.target?.closest("table");
8731
8950
  if (table && editable.contains(table)) {
8732
- const cell = e.target.closest("td, th");
8733
- if (cell) this._activeCell = cell;
8951
+ const cell = e.target?.closest("td, th");
8952
+ if (cell) {
8953
+ this._activeCell = cell;
8954
+ this._syncShadeStrip();
8955
+ }
8734
8956
  this._scheduleShow(table);
8735
8957
  }
8736
8958
  }, { passive: true }), on(editable, "mouseout", (e) => {
@@ -8738,9 +8960,10 @@
8738
8960
  const to = e.relatedTarget;
8739
8961
  if (!to || !editable.contains(to) && !this._el.contains(to) && !(this._sizePopover && this._sizePopover.contains(to))) this._scheduleHide();
8740
8962
  }, { passive: true }), on(document, "click", (e) => {
8741
- if (this._selectMode && this._activeTable && this._activeTable.contains(e.target)) return;
8742
- if (this._activeTable && !this._activeTable.contains(e.target) && !this._el.contains(e.target) && !(this._sizePopover && this._sizePopover.contains(e.target))) this._hide();
8743
- }));
8963
+ const et = e.target;
8964
+ if (this._selectMode && this._activeTable && this._activeTable.contains(et)) return;
8965
+ if (this._activeTable && !this._activeTable.contains(et) && !this._el.contains(et) && !(this._sizePopover && this._sizePopover.contains(et))) this._hide();
8966
+ }), on(document, "selectionchange", () => this._syncShadeStrip()), on(window, "scroll", () => this._hide(), { passive: true }), on(window, "resize", () => this._hide(), { passive: true }));
8744
8967
  this._initResize();
8745
8968
  return this;
8746
8969
  }
@@ -8870,6 +9093,8 @@
8870
9093
  this._el = null;
8871
9094
  if (this._sizePopover && this._sizePopover.parentNode) this._sizePopover.parentNode.removeChild(this._sizePopover);
8872
9095
  this._sizePopover = null;
9096
+ if (this._shadePopover && this._shadePopover.parentNode) this._shadePopover.parentNode.removeChild(this._shadePopover);
9097
+ this._shadePopover = null;
8873
9098
  }
8874
9099
  _buildTooltip() {
8875
9100
  const L = this.context.locale.tooltips.table;
@@ -8897,6 +9122,24 @@
8897
9122
  el.appendChild(this._makeBtn(ICONS$2.mergeCells, L.mergeCells, () => this._mergeCells()));
8898
9123
  el.appendChild(this._makeBtn(ICONS$2.unmergeCells, L.unmergeCells, () => this._unmergeCells()));
8899
9124
  el.appendChild(this._sep());
9125
+ const shadeBtn = createElement("button", {
9126
+ type: "button",
9127
+ class: "an-link-tooltip-btn an-link-tooltip-btn--shade",
9128
+ title: L.cellBackground
9129
+ });
9130
+ const shadeSvgWrap = createElement("span", { class: "an-bubble-btn-svg" });
9131
+ shadeSvgWrap.innerHTML = ICONS$2.cellShade;
9132
+ const shadeStrip = createElement("span", { class: "an-link-tooltip-color-strip" });
9133
+ shadeBtn.appendChild(shadeSvgWrap);
9134
+ shadeBtn.appendChild(shadeStrip);
9135
+ this._shadeColorStrip = shadeStrip;
9136
+ this._disposers.push(on(shadeBtn, "click", (e) => {
9137
+ e.preventDefault();
9138
+ e.stopPropagation();
9139
+ this._openCellShadePopover();
9140
+ }));
9141
+ el.appendChild(shadeBtn);
9142
+ el.appendChild(this._sep());
8900
9143
  el.appendChild(this._makeBtn(ICONS$2.colWidth, L.columnWidth, () => this._openSizePopover("col")));
8901
9144
  el.appendChild(this._makeBtn(ICONS$2.rowHeight, L.rowHeight, () => this._openSizePopover("row")));
8902
9145
  el.appendChild(this._makeBtn(ICONS$2.tableBorder, L.tableBorderWidth, () => this._openSizePopover("border")));
@@ -8905,6 +9148,7 @@
8905
9148
  this._disposers.push(on(el, "mouseenter", () => this._clearTimers()), on(el, "mouseleave", () => {
8906
9149
  if (this._selectMode) return;
8907
9150
  if (this._sizePopover && this._sizePopover.style.display !== "none") return;
9151
+ if (this._shadePopover && this._shadePopover.style.display !== "none") return;
8908
9152
  this._scheduleHide();
8909
9153
  }));
8910
9154
  return el;
@@ -8951,10 +9195,16 @@
8951
9195
  _show() {
8952
9196
  if (!this._activeTable) return;
8953
9197
  this._el.style.display = "flex";
9198
+ this._syncShadeStrip();
8954
9199
  requestAnimationFrame(() => {
8955
9200
  if (this._activeTable) this._positionNear(this._activeTable);
8956
9201
  });
8957
9202
  }
9203
+ _syncShadeStrip() {
9204
+ if (!this._shadeColorStrip || !this._el || this._el.style.display === "none") return;
9205
+ const cell = this._getCell();
9206
+ this._shadeColorStrip.style.background = cell && cell.style.backgroundColor || "transparent";
9207
+ }
8958
9208
  _hide() {
8959
9209
  this._el.style.display = "none";
8960
9210
  this._activeTable = null;
@@ -8993,7 +9243,7 @@
8993
9243
  if (sel && sel.rangeCount) {
8994
9244
  let container = sel.getRangeAt(0).commonAncestorContainer;
8995
9245
  if (container.nodeType === 3) container = container.parentElement;
8996
- const cellFromSel = container && container.closest && container.closest("td, th");
9246
+ const cellFromSel = container && container.closest("td, th");
8997
9247
  if (cellFromSel && this._activeTable && this._activeTable.contains(cellFromSel)) return cellFromSel;
8998
9248
  }
8999
9249
  return this._activeCell || this._activeTable && this._activeTable.querySelector("td, th");
@@ -9320,14 +9570,16 @@
9320
9570
  });
9321
9571
  const d2 = on(cancelBtn, "click", () => this._hideSizePopover());
9322
9572
  const d3 = on(inputEl, "keydown", (e) => {
9323
- if (e.key === "Enter") {
9573
+ const ke = e;
9574
+ if (ke.key === "Enter") {
9324
9575
  e.preventDefault();
9325
9576
  applyBtn.click();
9326
9577
  }
9327
- if (e.key === "Escape") this._hideSizePopover();
9578
+ if (ke.key === "Escape") this._hideSizePopover();
9328
9579
  });
9329
9580
  const d4 = on(document, "click", (e) => {
9330
- if (this._sizePopover && this._sizePopover.style.display !== "none" && !this._sizePopover.contains(e.target) && !this._el.contains(e.target)) this._hideSizePopover();
9581
+ const et = e.target;
9582
+ if (this._sizePopover && this._sizePopover.style.display !== "none" && !this._sizePopover.contains(et) && !this._el.contains(et)) this._hideSizePopover();
9331
9583
  });
9332
9584
  const d5 = on(popover, "mouseenter", () => this._clearTimers());
9333
9585
  const d6 = on(popover, "mouseleave", () => this._scheduleHide());
@@ -9345,7 +9597,7 @@
9345
9597
  this._sizeTitleEl.textContent = this.context.locale.tooltips.table.tableBorderWidthPx;
9346
9598
  this._sizeInputEl.min = "0";
9347
9599
  this._sizeInputEl.max = "10";
9348
- this._sizeInputEl.value = currentPx;
9600
+ this._sizeInputEl.value = String(currentPx);
9349
9601
  this._sizeApply = (val) => {
9350
9602
  const cells = Array.from(table.querySelectorAll("td, th"));
9351
9603
  if (val === 0) cells.forEach((c) => {
@@ -9414,6 +9666,85 @@
9414
9666
  if (this._sizePopover) this._sizePopover.style.display = "none";
9415
9667
  this._sizeApply = null;
9416
9668
  }
9669
+ _buildCellShadePopover() {
9670
+ const pop = createElement("div", { class: "an-cell-shade-popover" });
9671
+ pop.style.display = "none";
9672
+ const title = createElement("div", { class: "an-size-popover-title" });
9673
+ pop.appendChild(title);
9674
+ this._shadeTitleEl = title;
9675
+ const palette = createElement("div", { class: "an-context-color-palette" });
9676
+ SHADE_PRESETS.forEach((color) => {
9677
+ const sw = createElement("div", {
9678
+ class: "an-context-color-swatch",
9679
+ title: color
9680
+ });
9681
+ sw.style.background = color;
9682
+ this._disposers.push(on(sw, "click", (e) => {
9683
+ e.stopPropagation();
9684
+ this._applyCellShade(color);
9685
+ }));
9686
+ palette.appendChild(sw);
9687
+ });
9688
+ pop.appendChild(palette);
9689
+ const noShadeRow = createElement("div", { class: "an-context-color-custom" });
9690
+ const noShadeBtn = createElement("button", {
9691
+ type: "button",
9692
+ class: "an-shade-no-color"
9693
+ });
9694
+ this._disposers.push(on(noShadeBtn, "click", () => this._applyCellShade("")));
9695
+ noShadeRow.appendChild(noShadeBtn);
9696
+ pop.appendChild(noShadeRow);
9697
+ this._shadeNoBtn = noShadeBtn;
9698
+ const customRow = createElement("div", { class: "an-context-color-custom" });
9699
+ const colorInput = createElement("input", {
9700
+ type: "color",
9701
+ class: "an-shade-color-input",
9702
+ value: "#ffffff"
9703
+ });
9704
+ const customLabel = createElement("span");
9705
+ customLabel.textContent = "Custom…";
9706
+ this._disposers.push(on(colorInput, "change", () => this._applyCellShade(colorInput.value)));
9707
+ customRow.appendChild(colorInput);
9708
+ customRow.appendChild(customLabel);
9709
+ pop.appendChild(customRow);
9710
+ this._disposers.push(on(pop, "mousedown", (e) => e.preventDefault()));
9711
+ this._disposers.push(on(pop, "mouseenter", () => this._clearTimers()), on(pop, "mouseleave", () => this._scheduleHide()));
9712
+ this._disposers.push(on(document, "click", (e) => {
9713
+ const et = e.target;
9714
+ if (this._shadePopover && this._shadePopover.style.display !== "none" && !this._shadePopover.contains(et) && !(this._el && this._el.contains(et))) this._hideCellShadePopover();
9715
+ }));
9716
+ return pop;
9717
+ }
9718
+ _openCellShadePopover() {
9719
+ if (!this._shadePopover) return;
9720
+ const L = this.context.locale.tooltips.table;
9721
+ if (this._shadeTitleEl) this._shadeTitleEl.textContent = L.cellBackground;
9722
+ if (this._shadeNoBtn) this._shadeNoBtn.textContent = L.noShading;
9723
+ this._shadePopover.style.display = "block";
9724
+ requestAnimationFrame(() => {
9725
+ if (!this._shadePopover || !this._el) return;
9726
+ const pw = this._shadePopover.offsetWidth || 170;
9727
+ const ph = this._shadePopover.offsetHeight || 120;
9728
+ const tipRect = this._el.getBoundingClientRect();
9729
+ let left = tipRect.left;
9730
+ let top = tipRect.bottom + 6;
9731
+ if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8;
9732
+ if (top + ph > window.innerHeight - 8) top = tipRect.top - ph - 6;
9733
+ this._shadePopover.style.left = `${Math.max(8, left)}px`;
9734
+ this._shadePopover.style.top = `${Math.max(8, top)}px`;
9735
+ });
9736
+ }
9737
+ _hideCellShadePopover() {
9738
+ if (this._shadePopover) this._shadePopover.style.display = "none";
9739
+ }
9740
+ _applyCellShade(color) {
9741
+ (this._selectMode ? this._selectedCells : [this._getCell()]).forEach((cell) => {
9742
+ if (cell) cell.style.backgroundColor = color;
9743
+ });
9744
+ if (this._shadeColorStrip) this._shadeColorStrip.style.background = color || "transparent";
9745
+ this._hideCellShadePopover();
9746
+ this.context.invoke("editor.afterCommand");
9747
+ }
9417
9748
  };
9418
9749
  //#endregion
9419
9750
  //#region src/js/module/CodeTooltip.js
@@ -9446,13 +9777,14 @@
9446
9777
  const editable = this.context.layoutInfo.editable;
9447
9778
  this._disposers.push(on(editable, "mouseover", (e) => {
9448
9779
  if (this.context.layoutInfo.container.classList.contains("an-disabled")) return;
9449
- const pre = e.target.closest("pre");
9780
+ const pre = e.target?.closest("pre");
9450
9781
  if (pre && editable.contains(pre)) this._scheduleShow(pre);
9451
9782
  }), on(editable, "mouseout", (e) => {
9452
9783
  const to = e.relatedTarget;
9453
9784
  if (!to || !editable.contains(to) && !this._el.contains(to)) this._scheduleHide();
9454
9785
  }), on(document, "click", (e) => {
9455
- if (this._activePre && !this._activePre.contains(e.target) && !this._el.contains(e.target)) this._hide();
9786
+ const et = e.target;
9787
+ if (this._activePre && !this._activePre.contains(et) && !this._el.contains(et)) this._hide();
9456
9788
  }));
9457
9789
  return this;
9458
9790
  }
@@ -9487,6 +9819,7 @@
9487
9819
  ["python", "Python"],
9488
9820
  ["html", "HTML"],
9489
9821
  ["css", "CSS"],
9822
+ ["scss", "SCSS"],
9490
9823
  ["json", "JSON"],
9491
9824
  ["xml", "XML"],
9492
9825
  ["bash", "Bash / Shell"],
@@ -9642,10 +9975,27 @@
9642
9975
  this.context.invoke("editor.afterCommand");
9643
9976
  this._positionNear(pre);
9644
9977
  }
9978
+ /**
9979
+ * Applies a language to a given <pre> element: sets classes, data-language,
9980
+ * and triggers Prism highlighting. Called by the auto-detect flow.
9981
+ * @param {HTMLElement} pre
9982
+ * @param {string} lang - Prism language identifier, e.g. 'javascript'
9983
+ */
9984
+ applyLanguage(pre, lang) {
9985
+ if (!pre || !lang) return;
9986
+ const savedPre = this._activePre;
9987
+ this._langSelect && this._langSelect.value;
9988
+ this._activePre = pre;
9989
+ if (this._langSelect) this._langSelect.value = lang;
9990
+ this._onLangChange();
9991
+ if (this._langSelect) this._langSelect.value = lang;
9992
+ this._activePre = savedPre || pre;
9993
+ }
9645
9994
  _onLangChange() {
9646
9995
  const pre = this._activePre;
9647
9996
  if (!pre) return;
9648
9997
  const lang = this._langSelect.value;
9998
+ const _w = window;
9649
9999
  let codeEl = pre.querySelector("code");
9650
10000
  if (!codeEl) {
9651
10001
  codeEl = document.createElement("code");
@@ -9659,12 +10009,12 @@
9659
10009
  else pre.removeAttribute("data-language");
9660
10010
  const applyPrism = () => {
9661
10011
  codeEl.querySelectorAll("br").forEach((br) => br.replaceWith("\n"));
9662
- window.Prism.highlightElement(codeEl);
10012
+ _w.Prism.highlightElement(codeEl);
9663
10013
  this.context.invoke("editor.afterCommand");
9664
10014
  };
9665
10015
  if (lang) {
9666
- if (typeof window.Prism !== "undefined") {
9667
- if (window.Prism.languages[lang]) {
10016
+ if (typeof _w.Prism !== "undefined") {
10017
+ if (_w.Prism.languages[lang]) {
9668
10018
  applyPrism();
9669
10019
  return;
9670
10020
  }
@@ -9672,7 +10022,7 @@
9672
10022
  return;
9673
10023
  } else if (this._prismScript) {
9674
10024
  this._prismScript.addEventListener("load", () => {
9675
- if (window.Prism.languages[lang]) applyPrism();
10025
+ if (_w.Prism.languages[lang]) applyPrism();
9676
10026
  else this._loadPrismComponent(lang, applyPrism);
9677
10027
  }, { once: true });
9678
10028
  return;
@@ -9685,7 +10035,8 @@
9685
10035
  * Called once at initialize time. Fire-and-forget; errors are silent.
9686
10036
  */
9687
10037
  _ensurePrism() {
9688
- if (!this.context.options.codeHighlight || window.Prism) return;
10038
+ const _w = window;
10039
+ if (!this.context.options.codeHighlight || _w.Prism) return;
9689
10040
  const cdn = this.context.options.codeHighlightCDN;
9690
10041
  const themeHref = `${cdn}/themes/prism-tomorrow.min.css`;
9691
10042
  const scriptSrc = `${cdn}/prism.min.js`;
@@ -9697,7 +10048,7 @@
9697
10048
  }
9698
10049
  const existingScript = document.querySelector(`script[src="${scriptSrc}"]`);
9699
10050
  if (existingScript) {
9700
- this._prismScript = window.Prism ? null : existingScript;
10051
+ this._prismScript = _w.Prism ? null : existingScript;
9701
10052
  return;
9702
10053
  }
9703
10054
  const script = document.createElement("script");
@@ -9717,10 +10068,11 @@
9717
10068
  * @param {Function} cb – called once the grammar is ready
9718
10069
  */
9719
10070
  _loadPrismComponent(lang, cb) {
10071
+ const _w = window;
9720
10072
  const src = `${this.context.options.codeHighlightCDN}/components/prism-${lang}.min.js`;
9721
10073
  if (document.querySelector(`script[src="${src}"]`)) {
9722
10074
  const poll = setInterval(() => {
9723
- if (window.Prism && window.Prism.languages[lang]) {
10075
+ if (_w.Prism && _w.Prism.languages[lang]) {
9724
10076
  clearInterval(poll);
9725
10077
  cb();
9726
10078
  }
@@ -12133,15 +12485,19 @@
12133
12485
  });
12134
12486
  const box = createElement("div", { class: "an-dialog-box an-emoji-box" });
12135
12487
  const titleRow = createElement("div", { class: "an-icon-title-row" });
12488
+ const titleGroup = createElement("div", { class: "an-dialog-title-group" });
12489
+ const iconEl = createElement("span", { class: "an-dialog-icon an-dialog-icon--sm" });
12490
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M8 13s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>`;
12136
12491
  const title = createElement("h3", { class: "an-dialog-title" });
12137
12492
  title.textContent = L.title;
12493
+ titleGroup.append(iconEl, title);
12138
12494
  const closeBtn = createElement("button", {
12139
12495
  type: "button",
12140
12496
  class: "an-icon-close",
12141
12497
  "aria-label": L.close
12142
12498
  });
12143
12499
  closeBtn.innerHTML = "&times;";
12144
- titleRow.append(title, closeBtn);
12500
+ titleRow.append(titleGroup, closeBtn);
12145
12501
  const searchInput = createElement("input", {
12146
12502
  type: "search",
12147
12503
  class: "an-input an-icon-search",
@@ -12190,6 +12546,7 @@
12190
12546
  btnRow.appendChild(cancelBtn);
12191
12547
  box.append(titleRow, searchInput, catBar, grid, btnRow);
12192
12548
  overlay.appendChild(box);
12549
+ makeDraggable(titleRow, box);
12193
12550
  const d1 = on(closeBtn, "click", () => this._close());
12194
12551
  const d2 = on(cancelBtn, "click", () => this._close());
12195
12552
  const d3 = on(overlay, "click", (e) => {
@@ -12197,7 +12554,7 @@
12197
12554
  });
12198
12555
  const d4 = on(searchInput, "input", () => this._filterEmojis(searchInput.value, this._activeCat));
12199
12556
  const d5 = on(catBar, "click", (e) => {
12200
- const tab = e.target.closest("[data-cat]");
12557
+ const tab = e.target?.closest("[data-cat]");
12201
12558
  if (tab) {
12202
12559
  this._activeCat = tab.dataset.cat;
12203
12560
  this._updateCatTabs();
@@ -12205,7 +12562,7 @@
12205
12562
  }
12206
12563
  });
12207
12564
  const d6 = on(grid, "click", (e) => {
12208
- const cell = e.target.closest(".an-emoji-cell");
12565
+ const cell = e.target?.closest(".an-emoji-cell");
12209
12566
  if (cell) this._onEmojiClick(cell.dataset.char);
12210
12567
  });
12211
12568
  this._disposers.push(d1, d2, d3, d4, d5, d6);
@@ -12213,17 +12570,22 @@
12213
12570
  }
12214
12571
  _updateCatTabs() {
12215
12572
  this._catBar.querySelectorAll(".an-icon-cat").forEach((tab) => {
12216
- tab.classList.toggle("active", tab.dataset.cat === this._activeCat);
12573
+ tab.classList.toggle(
12574
+ "active",
12575
+ /** @type {HTMLElement} */
12576
+ tab.dataset.cat === this._activeCat
12577
+ );
12217
12578
  });
12218
12579
  }
12219
12580
  _filterEmojis(query, cat) {
12220
12581
  const q = (query || "").trim().toLowerCase();
12221
12582
  let count = 0;
12222
12583
  this._grid.querySelectorAll(".an-emoji-cell").forEach((cell) => {
12223
- const matchCat = !cat || cat === "all" || cell.dataset.cat === cat;
12224
- const matchQuery = !q || cell.dataset.keywords.includes(q) || cell.dataset.char === q;
12584
+ const hCell = cell;
12585
+ const matchCat = !cat || cat === "all" || hCell.dataset.cat === cat;
12586
+ const matchQuery = !q || hCell.dataset.keywords.includes(q) || hCell.dataset.char === q;
12225
12587
  const visible = matchCat && matchQuery;
12226
- cell.style.display = visible ? "" : "none";
12588
+ hCell.style.display = visible ? "" : "none";
12227
12589
  if (visible) count++;
12228
12590
  });
12229
12591
  let empty = this._grid.querySelector(".an-icon-empty");
@@ -12232,7 +12594,7 @@
12232
12594
  empty.textContent = "No emojis found";
12233
12595
  this._grid.appendChild(empty);
12234
12596
  }
12235
- empty.style.display = count > 0 ? "none" : "";
12597
+ /** @type {HTMLElement} */ empty.style.display = count > 0 ? "none" : "";
12236
12598
  }
12237
12599
  _onEmojiClick(char) {
12238
12600
  const savedRange = this._savedRange;
@@ -12246,7 +12608,7 @@
12246
12608
  range.collapse(false);
12247
12609
  }
12248
12610
  const _sc = range.startContainer;
12249
- const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest?.("td, th");
12611
+ const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest("td, th");
12250
12612
  range.deleteContents();
12251
12613
  if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
12252
12614
  range.setStart(_tdAnchor, 0);
@@ -12606,15 +12968,19 @@
12606
12968
  });
12607
12969
  const box = createElement("div", { class: "an-dialog-box an-icon-box" });
12608
12970
  const titleRow = createElement("div", { class: "an-icon-title-row" });
12971
+ const titleGroup = createElement("div", { class: "an-dialog-title-group" });
12972
+ const iconEl = createElement("span", { class: "an-dialog-icon an-dialog-icon--sm" });
12973
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>`;
12609
12974
  const title = createElement("h3", { class: "an-dialog-title" });
12610
12975
  title.textContent = L.title;
12976
+ titleGroup.append(iconEl, title);
12611
12977
  const closeBtn = createElement("button", {
12612
12978
  type: "button",
12613
12979
  class: "an-icon-close",
12614
12980
  "aria-label": L.close
12615
12981
  });
12616
12982
  closeBtn.innerHTML = "&times;";
12617
- titleRow.append(title, closeBtn);
12983
+ titleRow.append(titleGroup, closeBtn);
12618
12984
  const searchInput = createElement("input", {
12619
12985
  type: "search",
12620
12986
  class: "an-input an-icon-search",
@@ -12729,6 +13095,7 @@
12729
13095
  this._insertBtn = insertBtn;
12730
13096
  box.append(titleRow, searchInput, catBar, grid, optRow, preview, btnRow);
12731
13097
  overlay.appendChild(box);
13098
+ makeDraggable(titleRow, box);
12732
13099
  const d1 = on(closeBtn, "click", () => this._close());
12733
13100
  const d2 = on(cancelBtn, "click", () => this._close());
12734
13101
  const d3 = on(insertBtn, "click", () => this._onInsert());
@@ -12737,7 +13104,7 @@
12737
13104
  });
12738
13105
  const d5 = on(searchInput, "input", () => this._filterIcons(searchInput.value, this._activeCat));
12739
13106
  const d6 = on(catBar, "click", (e) => {
12740
- const tab = e.target.closest("[data-cat]");
13107
+ const tab = e.target?.closest("[data-cat]");
12741
13108
  if (tab) {
12742
13109
  this._activeCat = tab.dataset.cat;
12743
13110
  this._updateCatTabs();
@@ -12745,7 +13112,7 @@
12745
13112
  }
12746
13113
  });
12747
13114
  const d7 = on(grid, "click", (e) => {
12748
- const cell = e.target.closest(".an-icon-cell");
13115
+ const cell = e.target?.closest(".an-icon-cell");
12749
13116
  if (cell) this._selectIcon(cell.dataset.name);
12750
13117
  });
12751
13118
  const d8 = on(styleSelect, "change", () => this._updatePreview(this._selectedIcon));
@@ -12757,19 +13124,24 @@
12757
13124
  }
12758
13125
  _updateCatTabs() {
12759
13126
  this._catBar.querySelectorAll(".an-icon-cat").forEach((tab) => {
12760
- tab.classList.toggle("active", tab.dataset.cat === this._activeCat);
13127
+ tab.classList.toggle(
13128
+ "active",
13129
+ /** @type {HTMLElement} */
13130
+ tab.dataset.cat === this._activeCat
13131
+ );
12761
13132
  });
12762
13133
  }
12763
13134
  _filterIcons(query, cat) {
12764
13135
  const q = (query || "").trim().toLowerCase();
12765
13136
  let visibleCount = 0;
12766
13137
  this._grid.querySelectorAll(".an-icon-cell").forEach((cell) => {
12767
- const name = cell.dataset.name;
12768
- const cellCat = cell.dataset.cat;
13138
+ const hCell = cell;
13139
+ const name = hCell.dataset.name;
13140
+ const cellCat = hCell.dataset.cat;
12769
13141
  const matchesCat = !cat || cat === "all" || cellCat === cat;
12770
13142
  const matchesQuery = !q || name.includes(q);
12771
13143
  const visible = matchesCat && matchesQuery;
12772
- cell.style.display = visible ? "" : "none";
13144
+ hCell.style.display = visible ? "" : "none";
12773
13145
  if (visible) visibleCount++;
12774
13146
  });
12775
13147
  let empty = this._grid.querySelector(".an-icon-empty");
@@ -12778,12 +13150,16 @@
12778
13150
  empty.textContent = "No icons found";
12779
13151
  this._grid.appendChild(empty);
12780
13152
  }
12781
- empty.style.display = visibleCount > 0 ? "none" : "";
13153
+ /** @type {HTMLElement} */ empty.style.display = visibleCount > 0 ? "none" : "";
12782
13154
  }
12783
13155
  _selectIcon(name) {
12784
13156
  this._selectedIcon = name;
12785
13157
  this._grid.querySelectorAll(".an-icon-cell").forEach((cell) => {
12786
- cell.classList.toggle("active", cell.dataset.name === name);
13158
+ cell.classList.toggle(
13159
+ "active",
13160
+ /** @type {HTMLElement} */
13161
+ cell.dataset.name === name
13162
+ );
12787
13163
  });
12788
13164
  this._insertBtn.removeAttribute("disabled");
12789
13165
  this._updatePreview(name);
@@ -12822,7 +13198,7 @@
12822
13198
  range.collapse(false);
12823
13199
  }
12824
13200
  const _sc = range.startContainer;
12825
- const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest?.("td, th");
13201
+ const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)?.closest("td, th");
12826
13202
  range.deleteContents();
12827
13203
  if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
12828
13204
  range.setStart(_tdAnchor, 0);
@@ -13093,13 +13469,13 @@
13093
13469
  this._menuDisposers.forEach((d) => {
13094
13470
  try {
13095
13471
  d();
13096
- } catch (e) {}
13472
+ } catch (_e) {}
13097
13473
  });
13098
13474
  this._menuDisposers = [];
13099
13475
  this._disposers.forEach((d) => {
13100
13476
  try {
13101
13477
  d();
13102
- } catch (e) {}
13478
+ } catch (_e) {}
13103
13479
  });
13104
13480
  this._disposers = [];
13105
13481
  if (this.el && this.el.parentNode) this.el.parentNode.removeChild(this.el);
@@ -13290,13 +13666,13 @@
13290
13666
  });
13291
13667
  this._menuDisposers.push(offHeader);
13292
13668
  const offMove = on(gridEl, "mousemove", (e) => {
13293
- const cell = e.target.closest("[data-row]");
13669
+ const cell = e.target?.closest("[data-row]");
13294
13670
  if (!cell) return;
13295
13671
  setHighlight(+cell.dataset.row, +cell.dataset.col);
13296
13672
  });
13297
13673
  const offLeave = on(gridEl, "mouseleave", () => setHighlight(0, 0));
13298
13674
  const offClick = on(gridEl, "click", (e) => {
13299
- const cell = e.target.closest("[data-row]");
13675
+ const cell = e.target?.closest("[data-row]");
13300
13676
  if (!cell) return;
13301
13677
  const rows = +cell.dataset.row;
13302
13678
  const cols = +cell.dataset.col;
@@ -13321,7 +13697,7 @@
13321
13697
  class: "an-context-item",
13322
13698
  "data-name": it.name || ""
13323
13699
  });
13324
- if (typeof it.disabled === "function" ? it.disabled(this.context) : !!it.disabled) btn.disabled = true;
13700
+ if (typeof it.disabled === "function" ? it.disabled(this.context) : !!it.disabled) /** @type {HTMLButtonElement} */ btn.disabled = true;
13325
13701
  if (it.icon) {
13326
13702
  const iconSpan = createElement("span", {
13327
13703
  class: "an-context-icon",
@@ -13353,7 +13729,7 @@
13353
13729
  const winSel = window.getSelection();
13354
13730
  this._savedRange = winSel && winSel.rangeCount > 0 ? winSel.getRangeAt(0).cloneRange() : null;
13355
13731
  this._renderItems(this._items);
13356
- let openX = event.clientX;
13732
+ const openX = event.clientX;
13357
13733
  let openY = event.clientY;
13358
13734
  if (this._savedRange && !this._savedRange.collapsed) try {
13359
13735
  const selRect = this._savedRange.getBoundingClientRect();
@@ -13519,7 +13895,7 @@
13519
13895
  while (el = iter.nextNode()) {
13520
13896
  if (!editable.contains(el) || el === editable) continue;
13521
13897
  try {
13522
- if (range.intersectsNode(el)) el.removeAttribute("style");
13898
+ if (range.intersectsNode(el)) /** @type {Element} */ el.removeAttribute("style");
13523
13899
  } catch {}
13524
13900
  }
13525
13901
  this.context.invoke("editor.afterCommand");
@@ -13781,8 +14157,8 @@
13781
14157
  const replaceActions = this._dialog.querySelector(".an-fr-replace-actions");
13782
14158
  const title = this._dialog.querySelector(".an-dialog-title");
13783
14159
  const isReplace = this._mode === "replace";
13784
- if (replaceRow) replaceRow.style.display = isReplace ? "" : "none";
13785
- if (replaceActions) replaceActions.style.display = isReplace ? "" : "none";
14160
+ if (replaceRow) /** @type {HTMLElement} */ replaceRow.style.display = isReplace ? "" : "none";
14161
+ if (replaceActions) /** @type {HTMLElement} */ replaceActions.style.display = isReplace ? "" : "none";
13786
14162
  if (title) title.textContent = isReplace ? this.context.locale.findReplace.findReplaceTitle : this.context.locale.findReplace.findTitle;
13787
14163
  }
13788
14164
  _buildDialog() {
@@ -13793,106 +14169,123 @@
13793
14169
  "aria-modal": "true",
13794
14170
  "aria-label": L.findReplaceTitle
13795
14171
  });
13796
- const box = createElement("div", { class: "an-dialog-box" });
13797
- const titleRow = createElement("div", { class: "an-icon-title-row" });
14172
+ const box = createElement("div", { class: "an-dialog-box an-fr-box" });
14173
+ const header = createElement("div", { class: "an-fr-header" });
14174
+ const titleGroup = createElement("div", { class: "an-dialog-title-group" });
14175
+ const iconEl = createElement("span", { class: "an-dialog-icon an-dialog-icon--sm" });
14176
+ iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`;
13798
14177
  const title = createElement("h3", { class: "an-dialog-title" });
13799
14178
  title.textContent = L.findTitle;
14179
+ titleGroup.append(iconEl, title);
13800
14180
  const closeBtn = createElement("button", {
13801
14181
  type: "button",
13802
14182
  class: "an-icon-close",
13803
- "aria-label": "Close"
14183
+ title: L.close,
14184
+ "aria-label": L.close
13804
14185
  });
13805
- closeBtn.textContent = L.close;
14186
+ closeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
13806
14187
  this._closeBtn = closeBtn;
13807
- titleRow.append(title, closeBtn);
13808
- box.appendChild(titleRow);
13809
- const findRow = createElement("div", { class: "an-fr-find-row" });
14188
+ header.append(titleGroup, closeBtn);
14189
+ box.appendChild(header);
14190
+ const searchBar = createElement("div", { class: "an-fr-search-bar" });
13810
14191
  const findInput = createElement("input", {
13811
14192
  type: "text",
13812
- class: "an-input",
14193
+ class: "an-input an-fr-input",
13813
14194
  placeholder: L.findPlaceholder,
13814
14195
  "aria-label": L.searchAriaLabel
13815
14196
  });
13816
14197
  this._findInput = findInput;
13817
- findRow.appendChild(findInput);
13818
- box.appendChild(findRow);
13819
- const optRow = createElement("div", { class: "an-fr-options-row" });
13820
- const caseLabel = createElement("label", { class: "an-label an-label-inline" });
13821
14198
  const caseCheckbox = createElement("input", {
13822
14199
  type: "checkbox",
13823
- "aria-label": "Case sensitive"
14200
+ style: "display:none",
14201
+ "aria-hidden": "true"
13824
14202
  });
13825
14203
  this._caseCheckbox = caseCheckbox;
13826
- caseLabel.append(caseCheckbox, document.createTextNode(L.caseSensitive));
13827
- const counter = createElement("span", { class: "an-fr-counter" });
13828
- this._counterEl = counter;
13829
- optRow.append(caseLabel, counter);
13830
- box.appendChild(optRow);
13831
- const findActions = createElement("div", { class: "an-dialog-actions an-fr-find-actions" });
14204
+ const caseBtn = createElement("button", {
14205
+ type: "button",
14206
+ class: "an-fr-icon-btn",
14207
+ title: "Case sensitive",
14208
+ "aria-label": "Case sensitive"
14209
+ });
14210
+ caseBtn.textContent = "Aa";
13832
14211
  const prevBtn = createElement("button", {
13833
14212
  type: "button",
13834
- class: "an-btn"
14213
+ class: "an-fr-icon-btn",
14214
+ title: "Previous (Shift+Enter)",
14215
+ "aria-label": "Previous"
13835
14216
  });
13836
- prevBtn.textContent = L.prevBtn;
14217
+ prevBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>`;
13837
14218
  const nextBtn = createElement("button", {
13838
14219
  type: "button",
13839
- class: "an-btn an-btn-primary"
14220
+ class: "an-fr-icon-btn",
14221
+ title: "Next (Enter)",
14222
+ "aria-label": "Next"
13840
14223
  });
13841
- nextBtn.textContent = L.nextBtn;
13842
- findActions.append(prevBtn, nextBtn);
13843
- box.appendChild(findActions);
14224
+ nextBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`;
14225
+ const counter = createElement("span", { class: "an-fr-counter" });
14226
+ this._counterEl = counter;
14227
+ searchBar.append(findInput, caseCheckbox, caseBtn, prevBtn, nextBtn, counter);
14228
+ box.appendChild(searchBar);
13844
14229
  const replaceRow = createElement("div", { class: "an-fr-replace-row" });
13845
14230
  replaceRow.style.display = "none";
13846
14231
  const replaceInput = createElement("input", {
13847
14232
  type: "text",
13848
- class: "an-input",
14233
+ class: "an-input an-fr-input",
13849
14234
  placeholder: L.replacePlaceholder,
13850
14235
  "aria-label": L.replaceAriaLabel
13851
14236
  });
13852
14237
  this._replaceInput = replaceInput;
13853
- replaceRow.appendChild(replaceInput);
13854
- box.appendChild(replaceRow);
13855
- const replaceActions = createElement("div", { class: "an-dialog-actions an-fr-replace-actions" });
13856
- replaceActions.style.display = "none";
13857
14238
  const replaceBtn = createElement("button", {
13858
14239
  type: "button",
13859
- class: "an-btn"
14240
+ class: "an-btn an-fr-replace-btn"
13860
14241
  });
13861
14242
  replaceBtn.textContent = L.replaceBtn;
13862
14243
  const replaceAllBtn = createElement("button", {
13863
14244
  type: "button",
13864
- class: "an-btn an-btn-primary"
14245
+ class: "an-btn an-btn-primary an-fr-replace-btn"
13865
14246
  });
13866
14247
  replaceAllBtn.textContent = L.replaceAllBtn;
13867
- replaceActions.append(replaceBtn, replaceAllBtn);
14248
+ replaceRow.append(replaceInput, replaceBtn, replaceAllBtn);
14249
+ box.appendChild(replaceRow);
14250
+ const replaceActions = createElement("div", { class: "an-fr-replace-actions" });
14251
+ replaceActions.style.display = "none";
13868
14252
  box.appendChild(replaceActions);
13869
14253
  overlay.appendChild(box);
14254
+ makeDraggable(header, box);
13870
14255
  const d1 = on(closeBtn, "click", () => this._close());
13871
14256
  const d2 = on(overlay, "click", (e) => {
13872
14257
  if (e.target === overlay) this._close();
13873
14258
  });
13874
14259
  const d3 = on(findInput, "input", () => this._onSearch());
13875
- const d4 = on(caseCheckbox, "change", () => {
14260
+ const d4 = on(caseBtn, "click", () => {
14261
+ this._caseSensitive = !this._caseSensitive;
14262
+ caseCheckbox.checked = this._caseSensitive;
14263
+ caseBtn.classList.toggle("an-fr-icon-btn--active", this._caseSensitive);
14264
+ this._onSearch();
14265
+ });
14266
+ const d5 = on(caseCheckbox, "change", () => {
13876
14267
  this._caseSensitive = caseCheckbox.checked;
14268
+ caseBtn.classList.toggle("an-fr-icon-btn--active", this._caseSensitive);
13877
14269
  this._onSearch();
13878
14270
  });
13879
- const d5 = on(nextBtn, "click", () => this._next());
13880
- const d6 = on(prevBtn, "click", () => this._prev());
13881
- const d7 = on(replaceBtn, "click", () => this._replace());
13882
- const d8 = on(replaceAllBtn, "click", () => this._replaceAll());
13883
- const d9 = on(findInput, "keydown", (e) => {
13884
- if (e.key === "Enter") {
14271
+ const d6 = on(nextBtn, "click", () => this._next());
14272
+ const d7 = on(prevBtn, "click", () => this._prev());
14273
+ const d8 = on(replaceBtn, "click", () => this._replace());
14274
+ const d9 = on(replaceAllBtn, "click", () => this._replaceAll());
14275
+ const d10 = on(findInput, "keydown", (e) => {
14276
+ const ke = e;
14277
+ if (ke.key === "Enter") {
13885
14278
  e.preventDefault();
13886
- e.shiftKey ? this._prev() : this._next();
14279
+ ke.shiftKey ? this._prev() : this._next();
13887
14280
  }
13888
14281
  });
13889
- const d10 = on(replaceInput, "keydown", (e) => {
14282
+ const d11 = on(replaceInput, "keydown", (e) => {
13890
14283
  if (e.key === "Enter") {
13891
14284
  e.preventDefault();
13892
14285
  this._replace();
13893
14286
  }
13894
14287
  });
13895
- this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10);
14288
+ this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
13896
14289
  return overlay;
13897
14290
  }
13898
14291
  _onSearch() {
@@ -14253,9 +14646,10 @@
14253
14646
  }), on(h, "touchstart", (e) => {
14254
14647
  e.preventDefault();
14255
14648
  e.stopPropagation();
14649
+ const te = e;
14256
14650
  this._startHandleDrag({
14257
- clientX: e.touches[0].clientX,
14258
- clientY: e.touches[0].clientY
14651
+ clientX: te.touches[0].clientX,
14652
+ clientY: te.touches[0].clientY
14259
14653
  }, id);
14260
14654
  }, { passive: false }));
14261
14655
  this._handles[id] = h;
@@ -14272,9 +14666,10 @@
14272
14666
  if (e.target !== cropBox && e.target !== grid) return;
14273
14667
  e.preventDefault();
14274
14668
  e.stopPropagation();
14669
+ const te2 = e;
14275
14670
  this._startBoxMove({
14276
- clientX: e.touches[0].clientX,
14277
- clientY: e.touches[0].clientY
14671
+ clientX: te2.touches[0].clientX,
14672
+ clientY: te2.touches[0].clientY
14278
14673
  });
14279
14674
  }, { passive: false }));
14280
14675
  const infoEl = document.createElement("div");
@@ -14436,9 +14831,10 @@
14436
14831
  _attachDocDrag(onMove) {
14437
14832
  const onTouchMove = (e) => {
14438
14833
  e.preventDefault();
14834
+ const te3 = e;
14439
14835
  onMove({
14440
- clientX: e.touches[0].clientX,
14441
- clientY: e.touches[0].clientY
14836
+ clientX: te3.touches[0].clientX,
14837
+ clientY: te3.touches[0].clientY
14442
14838
  });
14443
14839
  };
14444
14840
  const cleanup = () => {
@@ -15015,7 +15411,9 @@
15015
15411
  const d6 = this.context.on("contextMenu:hide", () => {
15016
15412
  this._contextMenuOpen = false;
15017
15413
  });
15018
- this._disposers.push(d1, d2, d3, d4, d5, d6);
15414
+ const d7 = on(window, "scroll", () => this._hide(), { passive: true });
15415
+ const d8 = on(window, "resize", () => this._hide(), { passive: true });
15416
+ this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8);
15019
15417
  return this;
15020
15418
  }
15021
15419
  destroy() {
@@ -15129,20 +15527,22 @@
15129
15527
  picker.appendChild(customRow);
15130
15528
  document.body.appendChild(picker);
15131
15529
  this._picker = picker;
15132
- this._picker._paletteEl = palette;
15133
- this._picker._noColorBtn = noColorBtn;
15134
- this._picker._colorInput = colorInput;
15530
+ const pickerAny = picker;
15531
+ pickerAny._paletteEl = palette;
15532
+ pickerAny._noColorBtn = noColorBtn;
15533
+ pickerAny._colorInput = colorInput;
15135
15534
  }
15136
15535
  _openColorPicker(type, anchorBtn) {
15137
15536
  const sel = window.getSelection();
15138
15537
  if (sel && sel.rangeCount > 0) this._savedRange = sel.getRangeAt(0).cloneRange();
15139
15538
  this._pickerType = type;
15140
- const palette = this._picker._paletteEl;
15141
- const noColorBtn = this._picker._noColorBtn;
15539
+ const pickerAny = this._picker;
15540
+ const palette = pickerAny._paletteEl;
15541
+ const noColorBtn = pickerAny._noColorBtn;
15142
15542
  if (type === "hiliteColor") {
15143
15543
  if (!palette.contains(noColorBtn)) palette.appendChild(noColorBtn);
15144
15544
  } else if (palette.contains(noColorBtn)) palette.removeChild(noColorBtn);
15145
- this._picker._colorInput.value = type === "foreColor" ? "#000000" : "#ffff00";
15545
+ /** @type {any} */ this._picker._colorInput.value = type === "foreColor" ? "#000000" : "#ffff00";
15146
15546
  this._picker.style.display = "block";
15147
15547
  const pw = this._picker.offsetWidth;
15148
15548
  const ph = this._picker.offsetHeight;
@@ -15176,7 +15576,7 @@
15176
15576
  const name = type === "hiliteColor" ? "hiliteColor" : "foreColor";
15177
15577
  const btn = this._el && this._el.querySelector(`[data-name="${name}"]`);
15178
15578
  const strip = btn && btn.querySelector(".an-bubble-color-strip");
15179
- if (strip) strip.style.background = color === "transparent" ? "transparent" : color;
15579
+ if (strip) /** @type {HTMLElement} */ strip.style.background = color === "transparent" ? "transparent" : color;
15180
15580
  this._closeColorPicker();
15181
15581
  this._syncActive();
15182
15582
  }
@@ -15192,6 +15592,14 @@
15192
15592
  let top = rect.top - bh - gap;
15193
15593
  left = Math.max(8, Math.min(left, window.innerWidth - bw - 8));
15194
15594
  if (top < 8) top = rect.bottom + gap;
15595
+ const tableTooltipEl = document.querySelector(".an-table-tooltip");
15596
+ if (tableTooltipEl && tableTooltipEl.style.display !== "none") {
15597
+ const ttRect = tableTooltipEl.getBoundingClientRect();
15598
+ if (top < ttRect.bottom + gap && top + bh > ttRect.top - gap) {
15599
+ top = rect.bottom + gap;
15600
+ if (top + bh > window.innerHeight - 8) top = ttRect.bottom + gap;
15601
+ }
15602
+ }
15195
15603
  el.style.top = `${top}px`;
15196
15604
  el.style.left = `${left}px`;
15197
15605
  el.style.visibility = "";
@@ -15223,12 +15631,12 @@
15223
15631
  const cs = window.getComputedStyle(node);
15224
15632
  const foreBtn = this._el.querySelector("[data-name=\"foreColor\"]");
15225
15633
  const foreStrip = foreBtn && foreBtn.querySelector(".an-bubble-color-strip");
15226
- if (foreStrip) foreStrip.style.background = cs.color || "#000000";
15634
+ if (foreStrip) /** @type {HTMLElement} */ foreStrip.style.background = cs.color || "#000000";
15227
15635
  const hiliteBtn = this._el.querySelector("[data-name=\"hiliteColor\"]");
15228
15636
  const hiliteStrip = hiliteBtn && hiliteBtn.querySelector(".an-bubble-color-strip");
15229
15637
  if (hiliteStrip) {
15230
15638
  const bg = cs.backgroundColor;
15231
- hiliteStrip.style.background = !bg || bg === "rgba(0, 0, 0, 0)" || bg === "transparent" ? "transparent" : bg;
15639
+ /** @type {HTMLElement} */ hiliteStrip.style.background = !bg || bg === "rgba(0, 0, 0, 0)" || bg === "transparent" ? "transparent" : bg;
15232
15640
  }
15233
15641
  }
15234
15642
  _onSelectionChange() {
@@ -15356,11 +15764,11 @@
15356
15764
  el.setAttribute("role", "listbox");
15357
15765
  el.addEventListener("mousedown", (e) => e.preventDefault());
15358
15766
  el.addEventListener("click", (e) => {
15359
- const item = e.target.closest(".an-mention-item");
15767
+ const item = e.target?.closest(".an-mention-item");
15360
15768
  if (item) this._select(+item.dataset.index);
15361
15769
  });
15362
15770
  el.addEventListener("mousemove", (e) => {
15363
- const item = e.target.closest(".an-mention-item");
15771
+ const item = e.target?.closest(".an-mention-item");
15364
15772
  if (item) this._highlightItem(+item.dataset.index);
15365
15773
  });
15366
15774
  document.body.appendChild(el);
@@ -15375,7 +15783,7 @@
15375
15783
  const li = document.createElement("div");
15376
15784
  li.className = "an-mention-item";
15377
15785
  li.setAttribute("role", "option");
15378
- li.dataset.index = i;
15786
+ li.dataset.index = String(i);
15379
15787
  if (item.avatar) {
15380
15788
  const img = document.createElement("img");
15381
15789
  img.src = item.avatar;
@@ -15625,7 +16033,7 @@
15625
16033
  /**
15626
16034
  * Registers and initialises a custom module on this instance.
15627
16035
  * @param {string} name
15628
- * @param {Function} ModuleClass
16036
+ * @param {new (ctx: this) => any} ModuleClass
15629
16037
  * @returns {this}
15630
16038
  */
15631
16039
  registerModule(name, ModuleClass) {
@@ -15932,10 +16340,12 @@
15932
16340
  this._disposers.forEach((d) => d());
15933
16341
  this._disposers = [];
15934
16342
  const container = this.layoutInfo.container;
16343
+ const wasDark = container && container.classList.contains("an-theme-dark");
15935
16344
  if (container && container.parentNode) {
15936
16345
  this.targetEl.style.display = "";
15937
16346
  container.parentNode.removeChild(container);
15938
16347
  }
16348
+ if (wasDark && !document.querySelector(".an-container.an-theme-dark")) document.body.classList.remove("an-theme-dark");
15939
16349
  if (typeof this.options.onDestroy === "function") this.options.onDestroy(this);
15940
16350
  this._alive = false;
15941
16351
  this._listeners.clear();
@@ -15944,7 +16354,8 @@
15944
16354
  * Syncs editor HTML back into the original textarea/input for form submission.
15945
16355
  */
15946
16356
  _syncToTarget() {
15947
- if (this.targetEl.tagName === "TEXTAREA" || this.targetEl.tagName === "INPUT") this.targetEl.value = this.getHTML();
16357
+ if (this.targetEl.tagName === "TEXTAREA" || this.targetEl.tagName === "INPUT")
16358
+ /** @type {HTMLInputElement} */ this.targetEl.value = this.getHTML();
15948
16359
  }
15949
16360
  };
15950
16361
  //#endregion
@@ -15961,6 +16372,13 @@
15961
16372
  /** @type {WeakMap<Element, Context>} */
15962
16373
  var instances = /* @__PURE__ */ new WeakMap();
15963
16374
  var AutumnNote = {
16375
+ /**
16376
+ * Creates (or returns existing) editor instance on one or more elements.
16377
+ *
16378
+ * @param {string|Element|NodeList|Element[]} selector
16379
+ * @param {import('./settings.js').AsnOptions} [options]
16380
+ * @returns {Context|Context[]} single Context or array of Contexts
16381
+ */
15964
16382
  create(selector, options = {}) {
15965
16383
  const ctxs = resolveElements(selector).map((el) => {
15966
16384
  if (instances.has(el)) return instances.get(el);
@@ -15971,6 +16389,10 @@
15971
16389
  });
15972
16390
  return ctxs.length === 1 ? ctxs[0] : ctxs;
15973
16391
  },
16392
+ /**
16393
+ * Destroys the editor(s) on the given selector.
16394
+ * @param {string|Element|NodeList|Element[]} selector
16395
+ */
15974
16396
  destroy(selector) {
15975
16397
  resolveElements(selector).forEach((el) => {
15976
16398
  const ctx = instances.get(el);
@@ -15980,23 +16402,45 @@
15980
16402
  }
15981
16403
  });
15982
16404
  },
16405
+ /**
16406
+ * Returns the Context instance for a given element (or null).
16407
+ * @param {string|Element} selector
16408
+ * @returns {Context|null}
16409
+ */
15983
16410
  getInstance(selector) {
15984
16411
  const el = typeof selector === "string" ? document.querySelector(selector) : selector;
15985
16412
  return el ? instances.get(el) || null : null;
15986
16413
  },
16414
+ /** Returns a shallow copy of the default options (read-only snapshot). */
15987
16415
  get defaults() {
15988
16416
  return { ...defaultOptions };
15989
16417
  },
16418
+ /** Merges properties into the global defaults, applied to all future instances. */
15990
16419
  setDefaults(overrides) {
15991
16420
  Object.assign(defaultOptions, overrides);
15992
16421
  },
16422
+ /** Restores global defaults to their original factory values. */
15993
16423
  resetDefaults() {
15994
16424
  Object.keys(defaultOptions).forEach((k) => delete defaultOptions[k]);
15995
16425
  Object.assign(defaultOptions, _originalDefaults);
15996
16426
  },
16427
+ /**
16428
+ * Registers a custom module to be included in every new editor instance.
16429
+ * @param {string} name - unique module key used for ctx.invoke() calls
16430
+ * @param {Function} ModuleClass - class with initialize() and optional destroy()
16431
+ */
15997
16432
  registerModule(name, ModuleClass) {
15998
16433
  _customModules.set(name, ModuleClass);
15999
16434
  },
16435
+ /**
16436
+ * Installs a plugin globally — applied to every future editor instance.
16437
+ * Plugin `buttons` are registered to the global button registry immediately
16438
+ * so they are available when Toolbar initialises inside create().
16439
+ * Plugin `install()` is called after all built-in modules have initialised.
16440
+ * @param {object} plugin - { name, version?, buttons?, install?, uninstall? }
16441
+ * @param {object} [options] - Forwarded to plugin.install(context, options)
16442
+ * @returns {typeof AutumnNote}
16443
+ */
16000
16444
  use(plugin, options = {}) {
16001
16445
  if (!plugin || typeof plugin.name !== "string") throw new TypeError("[AutumnNote] AutumnNote.use: plugin must have a string `name` property.");
16002
16446
  if (_globalPlugins.has(plugin.name)) {
@@ -16010,14 +16454,26 @@
16010
16454
  });
16011
16455
  return this;
16012
16456
  },
16457
+ /**
16458
+ * Returns true if a plugin with the given name has been registered globally.
16459
+ * @param {string} name
16460
+ * @returns {boolean}
16461
+ */
16013
16462
  hasPlugin(name) {
16014
16463
  return _globalPlugins.has(name);
16015
16464
  },
16465
+ /**
16466
+ * Registers a single button definition in the global button registry.
16467
+ * After create(), call ctx.invoke('toolbar.rebuild') to render new buttons.
16468
+ * @param {object} btnDef - ButtonDef-compatible object with a `name` string
16469
+ * @returns {typeof AutumnNote}
16470
+ */
16016
16471
  registerButton(btnDef) {
16017
16472
  registerButton(btnDef);
16018
16473
  return this;
16019
16474
  },
16020
- version: "1.4.2"
16475
+ /** Library version */
16476
+ version: "1.5.0"
16021
16477
  };
16022
16478
  /**
16023
16479
  * @param {string|Element|NodeList|Element[]} selector