autumnnote 1.7.0 → 1.8.1

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.
@@ -807,13 +807,74 @@ function _checklistItemToP(checkLi) {
807
807
  } catch {}
808
808
  }
809
809
  /**
810
- * Inserts an unordered list or converts selection.
810
+ * Inserts an unordered (bulleted) list, or converts the current list to `<ul>`.
811
+ *
812
+ * When the cursor is already inside a list, direct DOM manipulation is used to
813
+ * transition between list types — `execCommand` alone cannot handle checklist →
814
+ * UL/OL conversions because it has no awareness of the `an-checklist` class or
815
+ * the checkbox `<input>` elements.
816
+ *
817
+ * Transition paths:
818
+ * - **Checklist → UL**: strips `an-checklist` class and all checkbox inputs;
819
+ * converts `<ol>` container to `<ul>` via `changeTagName()` if needed.
820
+ * - **OL → UL**: swaps the container tag via `changeTagName()`.
821
+ * - **UL → paragraphs**: falls back to `execCommand('insertUnorderedList')`
822
+ * which toggles the list off (browser-native behaviour).
823
+ * - **No list → UL**: falls back to `execCommand('insertUnorderedList')`.
824
+ */
825
+ /**
826
+ * Helper to get the closest ul/ol element containing the current selection.
827
+ * @returns {Element|null}
828
+ */
829
+ function getSelectedList() {
830
+ const sel = globalThis.getSelection();
831
+ if (!sel?.rangeCount) return null;
832
+ let container = sel.getRangeAt(0).commonAncestorContainer;
833
+ if (container.nodeType === 3) container = container.parentElement;
834
+ return container?.closest("ul, ol") || null;
835
+ }
836
+ /**
837
+ * Strips the checklist class and checkbox inputs from a list element.
838
+ * @param {Element} listEl
811
839
  */
812
- var insertUnorderedList = () => execCommand("insertUnorderedList");
840
+ function stripChecklist(listEl) {
841
+ listEl.classList.remove("an-checklist");
842
+ listEl.querySelectorAll("input[type=\"checkbox\"]").forEach((cb) => cb.remove());
843
+ }
844
+ function insertUnorderedList() {
845
+ const listEl = getSelectedList();
846
+ if (listEl) if (listEl.classList.contains("an-checklist")) {
847
+ stripChecklist(listEl);
848
+ if (listEl.tagName === "OL") changeTagName(listEl, "ul");
849
+ } else if (listEl.tagName === "OL") changeTagName(listEl, "ul");
850
+ else execCommand("insertUnorderedList");
851
+ else execCommand("insertUnorderedList");
852
+ }
813
853
  /**
814
- * Inserts an ordered list or converts selection.
854
+ * Inserts an ordered (numbered) list, or converts the current list to `<ol>`.
855
+ *
856
+ * When the cursor is already inside a list, direct DOM manipulation is used to
857
+ * transition between list types — `execCommand` alone cannot handle checklist →
858
+ * UL/OL conversions because it has no awareness of the `an-checklist` class or
859
+ * the checkbox `<input>` elements.
860
+ *
861
+ * Transition paths:
862
+ * - **Checklist → OL**: strips `an-checklist` class and all checkbox inputs;
863
+ * converts container to `<ol>` via `changeTagName()`.
864
+ * - **UL → OL**: swaps the container tag via `changeTagName()`.
865
+ * - **OL → paragraphs**: falls back to `execCommand('insertOrderedList')`
866
+ * which toggles the list off (browser-native behaviour).
867
+ * - **No list → OL**: falls back to `execCommand('insertOrderedList')`.
815
868
  */
816
- var insertOrderedList = () => execCommand("insertOrderedList");
869
+ function insertOrderedList() {
870
+ const listEl = getSelectedList();
871
+ if (listEl) if (listEl.classList.contains("an-checklist")) {
872
+ stripChecklist(listEl);
873
+ changeTagName(listEl, "ol");
874
+ } else if (listEl.tagName === "UL") changeTagName(listEl, "ol");
875
+ else execCommand("insertOrderedList");
876
+ else execCommand("insertOrderedList");
877
+ }
817
878
  /**
818
879
  * Set the line-height on every block-level element that intersects the current selection.
819
880
  *
@@ -939,18 +1000,34 @@ function isInlineCode() {
939
1000
  return !!(code && !code.closest("pre"));
940
1001
  }
941
1002
  /**
1003
+ * Changes the tag name of an element in the DOM while preserving attributes and children.
1004
+ * @param {Element} el
1005
+ * @param {string} newTagName
1006
+ * @returns {HTMLElement}
1007
+ */
1008
+ function changeTagName(el, newTagName) {
1009
+ const newEl = document.createElement(newTagName);
1010
+ for (const attr of el.attributes) newEl.setAttribute(attr.name, attr.value);
1011
+ while (el.firstChild) newEl.appendChild(el.firstChild);
1012
+ el.parentNode.replaceChild(newEl, el);
1013
+ return newEl;
1014
+ }
1015
+ /**
1016
+ * Ensures all list items under the list element have a checkbox.
1017
+ * @param {Element} listEl
1018
+ */
1019
+ function ensureCheckboxes(listEl) {
1020
+ listEl.querySelectorAll("li").forEach((li) => {
1021
+ if (!li.querySelector("input[type=\"checkbox\"]")) {
1022
+ const cb = document.createElement("input");
1023
+ cb.type = "checkbox";
1024
+ cb.contentEditable = "false";
1025
+ li.insertBefore(cb, li.firstChild);
1026
+ }
1027
+ });
1028
+ }
1029
+ /**
942
1030
  * Toggle a checklist at the current selection or caret.
943
- *
944
- * When the selection is inside an existing checklist `<ul class="an-checklist">`,
945
- * converts the selected `<li>` items back into `<p>` paragraphs and places the caret
946
- * at the start of the first converted paragraph. Otherwise creates a checklist:
947
- * - If the selection is collapsed, converts the nearest block-level ancestor (or inserts
948
- * a single checklist item at the editable root) into a checklist with one item containing
949
- * that block's text and places the caret inside the new item.
950
- * - If the selection is a range, converts each intersecting block element into one checklist
951
- * item (preserving textual content) and places the caret at the end of the last item.
952
- *
953
- * Empty or whitespace-only selections do not create a checklist.
954
1031
  */
955
1032
  function toggleChecklist() {
956
1033
  const sel = globalThis.getSelection();
@@ -958,27 +1035,26 @@ function toggleChecklist() {
958
1035
  const range = sel.getRangeAt(0);
959
1036
  let container = range.commonAncestorContainer;
960
1037
  if (container.nodeType === 3) container = container.parentElement;
961
- const ul = container?.closest(".an-checklist");
962
- if (ul) {
963
- const selectedLis = Array.from(ul.querySelectorAll("li")).filter((li) => sel.containsNode(li, true));
964
- if (selectedLis.length > 0) {
965
- /** @type {HTMLElement|null} */ let firstP = null;
966
- selectedLis.forEach((li) => {
1038
+ const listEl = container?.closest("ul, ol");
1039
+ if (listEl) if (listEl.classList.contains("an-checklist")) {
1040
+ if (listEl.parentNode) {
1041
+ const lis = Array.from(listEl.children);
1042
+ let firstP = null;
1043
+ lis.forEach((li) => {
967
1044
  const p = document.createElement("p");
968
1045
  for (const child of li.childNodes) {
969
1046
  if (child.nodeType === 1 && child.tagName === "INPUT") continue;
970
1047
  p.appendChild(child.cloneNode(true));
971
1048
  }
972
- p.innerHTML = p.innerHTML.replaceAll("​", "");
1049
+ p.innerHTML = p.innerHTML.replaceAll("​", "").replaceAll("​", "");
973
1050
  if (!p.hasChildNodes() || !p.textContent.trim()) {
974
1051
  p.innerHTML = "";
975
1052
  p.appendChild(document.createTextNode("\xA0"));
976
1053
  }
977
- ul.parentNode.insertBefore(p, ul);
1054
+ listEl.before(p);
978
1055
  if (!firstP) firstP = p;
979
- li.remove();
980
1056
  });
981
- if (ul.children.length === 0) ul.remove();
1057
+ listEl.remove();
982
1058
  if (firstP) {
983
1059
  const nr = document.createRange();
984
1060
  nr.setStart(firstP.firstChild || firstP, 0);
@@ -986,11 +1062,65 @@ function toggleChecklist() {
986
1062
  sel.removeAllRanges();
987
1063
  sel.addRange(nr);
988
1064
  }
989
- return;
1065
+ }
1066
+ } else {
1067
+ const targetUl = changeTagName(listEl, "ul");
1068
+ targetUl.classList.add("an-checklist");
1069
+ ensureCheckboxes(targetUl);
1070
+ const firstLi = targetUl.querySelector("li");
1071
+ if (firstLi) {
1072
+ const nr = document.createRange();
1073
+ nr.selectNodeContents(firstLi);
1074
+ nr.collapse(false);
1075
+ sel.removeAllRanges();
1076
+ sel.addRange(nr);
990
1077
  }
991
1078
  }
992
- if (range.collapsed) {
993
- const BLOCK_TAGS = new Set([
1079
+ else {
1080
+ const editableRoot = container?.closest("[contenteditable=\"true\"]");
1081
+ if (range.collapsed) {
1082
+ const BLOCK_TAGS = new Set([
1083
+ "P",
1084
+ "DIV",
1085
+ "H1",
1086
+ "H2",
1087
+ "H3",
1088
+ "H4",
1089
+ "H5",
1090
+ "H6",
1091
+ "BLOCKQUOTE",
1092
+ "LI"
1093
+ ]);
1094
+ let block = container;
1095
+ while (block?.parentNode && block !== editableRoot && !BLOCK_TAGS.has(block.tagName)) block = block.parentNode;
1096
+ if (block === editableRoot) block = null;
1097
+ const itemText = block && BLOCK_TAGS.has(block.tagName) ? Array.from(block.childNodes).map((n) => n.textContent).join("").replaceAll("\xA0", " ") : "";
1098
+ const newUl = document.createElement("ul");
1099
+ newUl.className = "an-checklist";
1100
+ const li = document.createElement("li");
1101
+ const checkbox = document.createElement("input");
1102
+ checkbox.type = "checkbox";
1103
+ checkbox.contentEditable = "false";
1104
+ li.appendChild(checkbox);
1105
+ li.appendChild(document.createTextNode(itemText || "​"));
1106
+ newUl.appendChild(li);
1107
+ if (block && BLOCK_TAGS.has(block.tagName)) block.parentNode.replaceChild(newUl, block);
1108
+ else {
1109
+ const nativeRange = sel.getRangeAt(0);
1110
+ nativeRange.deleteContents();
1111
+ nativeRange.insertNode(newUl);
1112
+ }
1113
+ const textNode = li.lastChild;
1114
+ const nr = document.createRange();
1115
+ const offset = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent.length : 0;
1116
+ nr.setStart(textNode, offset);
1117
+ nr.collapse(true);
1118
+ sel.removeAllRanges();
1119
+ sel.addRange(nr);
1120
+ return;
1121
+ }
1122
+ if (!sel.toString().replace(/[\u00a0\u200B]/g, " ").trim()) return;
1123
+ const BLOCK_TAGS_MULTI = new Set([
994
1124
  "P",
995
1125
  "DIV",
996
1126
  "H1",
@@ -1000,89 +1130,51 @@ function toggleChecklist() {
1000
1130
  "H5",
1001
1131
  "H6",
1002
1132
  "BLOCKQUOTE",
1133
+ "PRE",
1003
1134
  "LI"
1004
1135
  ]);
1005
- let block = container;
1006
- while (block?.parentNode && !BLOCK_TAGS.has(block.tagName)) block = block.parentNode;
1007
- const itemText = block && BLOCK_TAGS.has(block.tagName) ? Array.from(block.childNodes).map((n) => n.textContent).join("").replaceAll("\xA0", " ") : "";
1008
- const ul = document.createElement("ul");
1009
- ul.className = "an-checklist";
1010
- const li = document.createElement("li");
1011
- const checkbox = document.createElement("input");
1012
- checkbox.type = "checkbox";
1013
- checkbox.contentEditable = "false";
1014
- li.appendChild(checkbox);
1015
- li.appendChild(document.createTextNode(itemText || "​"));
1016
- ul.appendChild(li);
1017
- if (block && BLOCK_TAGS.has(block.tagName)) block.parentNode.replaceChild(ul, block);
1018
- else {
1019
- const nativeRange = sel.getRangeAt(0);
1020
- nativeRange.deleteContents();
1021
- nativeRange.insertNode(ul);
1136
+ const blocks = [];
1137
+ const seenBlocks = /* @__PURE__ */ new Set();
1138
+ const commonAncestor = range.commonAncestorContainer;
1139
+ const iter = document.createNodeIterator(commonAncestor.nodeType === Node.TEXT_NODE ? commonAncestor.parentNode : commonAncestor, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null);
1140
+ let node;
1141
+ while (node = iter.nextNode()) {
1142
+ if (!range.intersectsNode(node)) continue;
1143
+ let blockEl = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
1144
+ while (blockEl && blockEl !== editableRoot && !BLOCK_TAGS_MULTI.has(blockEl.tagName)) blockEl = blockEl.parentElement;
1145
+ if (blockEl === editableRoot) blockEl = null;
1146
+ if (blockEl && !seenBlocks.has(blockEl)) {
1147
+ seenBlocks.add(blockEl);
1148
+ blocks.push(blockEl);
1149
+ }
1022
1150
  }
1023
- const textNode = li.lastChild;
1024
- const nr = document.createRange();
1025
- const offset = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent.length : 0;
1026
- nr.setStart(textNode, offset);
1027
- nr.collapse(true);
1028
- sel.removeAllRanges();
1029
- sel.addRange(nr);
1030
- return;
1031
- }
1032
- if (!sel.toString().replace(/[\u00a0\u200B]/g, " ").trim()) return;
1033
- const BLOCK_TAGS_MULTI = new Set([
1034
- "P",
1035
- "DIV",
1036
- "H1",
1037
- "H2",
1038
- "H3",
1039
- "H4",
1040
- "H5",
1041
- "H6",
1042
- "BLOCKQUOTE",
1043
- "PRE",
1044
- "LI"
1045
- ]);
1046
- const blocks = [];
1047
- const seenBlocks = /* @__PURE__ */ new Set();
1048
- const commonAncestor = range.commonAncestorContainer;
1049
- const iter = document.createNodeIterator(commonAncestor.nodeType === Node.TEXT_NODE ? commonAncestor.parentNode : commonAncestor, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null);
1050
- let node;
1051
- while (node = iter.nextNode()) {
1052
- if (!range.intersectsNode(node)) continue;
1053
- let block = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
1054
- while (block && !BLOCK_TAGS_MULTI.has(block.tagName)) block = block.parentElement;
1055
- if (block && !seenBlocks.has(block)) {
1056
- seenBlocks.add(block);
1057
- blocks.push(block);
1151
+ if (blocks.length === 0) return;
1152
+ const newUl = document.createElement("ul");
1153
+ newUl.className = "an-checklist";
1154
+ /** @type {Text|null} */ let lastTextNode = null;
1155
+ blocks.forEach((block) => {
1156
+ const li = document.createElement("li");
1157
+ const cb = document.createElement("input");
1158
+ cb.type = "checkbox";
1159
+ cb.contentEditable = "false";
1160
+ li.appendChild(cb);
1161
+ const blockText = Array.from(block.childNodes).map((n) => n.textContent).join("").replace(/[\u00a0\u200B]/g, " ").trim();
1162
+ const tn = document.createTextNode(blockText || "");
1163
+ li.appendChild(tn);
1164
+ newUl.appendChild(li);
1165
+ lastTextNode = tn;
1166
+ });
1167
+ const firstBlock = blocks[0];
1168
+ firstBlock.parentNode.insertBefore(newUl, firstBlock);
1169
+ blocks.forEach((block) => block.remove());
1170
+ if (lastTextNode) {
1171
+ const nr = document.createRange();
1172
+ nr.setStart(lastTextNode, lastTextNode.textContent.length);
1173
+ nr.collapse(true);
1174
+ sel.removeAllRanges();
1175
+ sel.addRange(nr);
1058
1176
  }
1059
1177
  }
1060
- if (blocks.length === 0) return;
1061
- const newUl = document.createElement("ul");
1062
- newUl.className = "an-checklist";
1063
- /** @type {Text|null} */ let lastTextNode = null;
1064
- blocks.forEach((block) => {
1065
- const li = document.createElement("li");
1066
- const cb = document.createElement("input");
1067
- cb.type = "checkbox";
1068
- cb.setAttribute("contenteditable", "false");
1069
- li.appendChild(cb);
1070
- const blockText = Array.from(block.childNodes).map((n) => n.textContent).join("").replace(/[\u00a0\u200B]/g, " ").trim();
1071
- const tn = document.createTextNode(blockText || "​");
1072
- li.appendChild(tn);
1073
- newUl.appendChild(li);
1074
- lastTextNode = tn;
1075
- });
1076
- const firstBlock = blocks[0];
1077
- firstBlock.parentNode.insertBefore(newUl, firstBlock);
1078
- blocks.forEach((block) => block.remove());
1079
- if (lastTextNode) {
1080
- const nr = document.createRange();
1081
- nr.setStart(lastTextNode, lastTextNode.textContent.length);
1082
- nr.collapse(true);
1083
- sel.removeAllRanges();
1084
- sel.addRange(nr);
1085
- }
1086
1178
  }
1087
1179
  /**
1088
1180
  * Returns true when the cursor is inside a checklist item.
@@ -1458,6 +1550,48 @@ var defaultToolbar = [
1458
1550
  shortcutsBtn
1459
1551
  ]
1460
1552
  ];
1553
+ var buttons = {
1554
+ boldBtn,
1555
+ italicBtn,
1556
+ underlineBtn,
1557
+ strikeBtn,
1558
+ superscriptBtn,
1559
+ subscriptBtn,
1560
+ alignLeftBtn,
1561
+ alignCenterBtn,
1562
+ alignRightBtn,
1563
+ alignJustifyBtn,
1564
+ ulBtn,
1565
+ olBtn,
1566
+ indentBtn,
1567
+ outdentBtn,
1568
+ undoBtn,
1569
+ redoBtn,
1570
+ hrBtn,
1571
+ linkBtn,
1572
+ imageBtn,
1573
+ videoBtn,
1574
+ emojiBtn,
1575
+ iconBtn,
1576
+ tableBtn,
1577
+ fontSizeBtn,
1578
+ removeFormatBtn,
1579
+ directionBtn,
1580
+ fontFamilyBtn,
1581
+ paragraphStyleBtn,
1582
+ lineHeightBtn,
1583
+ codeviewBtn,
1584
+ fullscreenBtn,
1585
+ shortcutsBtn,
1586
+ findBtn,
1587
+ findReplaceBtn,
1588
+ inlineCodeBtn,
1589
+ checklistBtn,
1590
+ printBtn,
1591
+ foreColorBtn,
1592
+ backColorBtn,
1593
+ defaultToolbar
1594
+ };
1461
1595
  //#endregion
1462
1596
  //#region src/js/settings.js
1463
1597
  /**
@@ -1752,6 +1886,7 @@ var en = {
1752
1886
  findPlaceholder: "Find…",
1753
1887
  searchAriaLabel: "Search text",
1754
1888
  caseSensitive: "\xA0Case sensitive",
1889
+ wholeWord: "Whole Word",
1755
1890
  prevBtn: "← Prev",
1756
1891
  nextBtn: "Next →",
1757
1892
  replacePlaceholder: "Replace with…",
@@ -1762,6 +1897,12 @@ var en = {
1762
1897
  useRegex: "Use Regular Expression",
1763
1898
  close: "×"
1764
1899
  },
1900
+ autoSaveRestore: {
1901
+ found: "Draft found. Restore?",
1902
+ foundAt: "Draft from {date}. Restore?",
1903
+ restore: "Restore",
1904
+ discard: "Discard"
1905
+ },
1765
1906
  shortcutsDialog: {
1766
1907
  title: "Keyboard Shortcuts",
1767
1908
  ariaLabel: "Keyboard Shortcuts",
@@ -2127,6 +2268,7 @@ var locales = {
2127
2268
  findPlaceholder: "Tìm…",
2128
2269
  searchAriaLabel: "Văn bản tìm kiếm",
2129
2270
  caseSensitive: "\xA0Phân biệt hoa thường",
2271
+ wholeWord: "Toàn bộ từ",
2130
2272
  prevBtn: "← Trước",
2131
2273
  nextBtn: "Tiếp →",
2132
2274
  replacePlaceholder: "Thay thế bằng…",
@@ -2294,9 +2436,16 @@ var locales = {
2294
2436
  columnWidth: "Chiều rộng cột",
2295
2437
  rowHeight: "Chiều cao hàng",
2296
2438
  tableBorderWidth: "Độ rộng viền bảng",
2439
+ tableBorderColor: "Màu viền bảng",
2297
2440
  deleteTable: "Xóa bảng",
2441
+ cellAlignLeft: "Căn trái",
2442
+ cellAlignCenter: "Căn giữa",
2443
+ cellAlignRight: "Căn phải",
2444
+ cellAlignJustify: "Căn đều",
2445
+ toggleHeaderRow: "Bật/tắt hàng tiêu đề",
2298
2446
  cellBackground: "Màu Nền Ô",
2299
2447
  noShading: "Xóa Màu Nền",
2448
+ noBorderColor: "Xóa màu viền",
2300
2449
  columnWidthPx: "Chiều rộng cột (px)",
2301
2450
  rowHeightPx: "Chiều cao hàng (px)",
2302
2451
  tableBorderWidthPx: "Độ rộng viền bảng (px)",
@@ -2324,6 +2473,12 @@ var locales = {
2324
2473
  errors: {
2325
2474
  imageFormat: (type) => `Định dạng "${type}" không được hỗ trợ hiển thị trên trình duyệt. Vui lòng chuyển đổi sang JPEG, PNG hoặc WebP.`,
2326
2475
  imageSize: (maxSize) => `Tệp hình ảnh quá lớn. Kích thước tối đa cho phép là ${maxSize} MB.`
2476
+ },
2477
+ autoSaveRestore: {
2478
+ found: "Tìm thấy bản nháp. Khôi phục?",
2479
+ foundAt: "Bản nháp từ {date}. Khôi phục?",
2480
+ restore: "Khôi phục",
2481
+ discard: "Bỏ qua"
2327
2482
  }
2328
2483
  },
2329
2484
  ja: {
@@ -2467,6 +2622,7 @@ var locales = {
2467
2622
  findPlaceholder: "検索…",
2468
2623
  searchAriaLabel: "検索テキスト",
2469
2624
  caseSensitive: "\xA0大文字/小文字を区別",
2625
+ wholeWord: "単語単位",
2470
2626
  prevBtn: "← 前へ",
2471
2627
  nextBtn: "次へ →",
2472
2628
  replacePlaceholder: "置換後…",
@@ -2634,7 +2790,16 @@ var locales = {
2634
2790
  columnWidth: "列幅",
2635
2791
  rowHeight: "行の高さ",
2636
2792
  tableBorderWidth: "テーブルの枠幅",
2793
+ tableBorderColor: "テーブルの枠線色",
2637
2794
  deleteTable: "テーブルを削除",
2795
+ cellAlignLeft: "左揃え",
2796
+ cellAlignCenter: "中央揃え",
2797
+ cellAlignRight: "右揃え",
2798
+ cellAlignJustify: "両端揃え",
2799
+ toggleHeaderRow: "ヘッダー行の切り替え",
2800
+ cellBackground: "セル背景色",
2801
+ noShading: "背景色なし",
2802
+ noBorderColor: "枠線色なし",
2638
2803
  columnWidthPx: "列幅 (px)",
2639
2804
  rowHeightPx: "行の高さ (px)",
2640
2805
  tableBorderWidthPx: "テーブルの枠幅 (px)",
@@ -2662,6 +2827,12 @@ var locales = {
2662
2827
  errors: {
2663
2828
  imageFormat: (type) => `形式 "${type}" はブラウザでの表示をサポートしていません。JPEG、PNG、または WebP に変換してください。`,
2664
2829
  imageSize: (maxSize) => `画像ファイルが大きすぎます。最大許容サイズは ${maxSize} MB です。`
2830
+ },
2831
+ autoSaveRestore: {
2832
+ found: "下書きが見つかりました。復元しますか?",
2833
+ foundAt: "{date} の下書きが見つかりました。復元しますか?",
2834
+ restore: "復元",
2835
+ discard: "破棄"
2665
2836
  }
2666
2837
  },
2667
2838
  zh: {
@@ -2805,6 +2976,7 @@ var locales = {
2805
2976
  findPlaceholder: "查找…",
2806
2977
  searchAriaLabel: "搜索文字",
2807
2978
  caseSensitive: "\xA0区分大小写",
2979
+ wholeWord: "全字匹配",
2808
2980
  prevBtn: "← 上一个",
2809
2981
  nextBtn: "下一个 →",
2810
2982
  replacePlaceholder: "替换为…",
@@ -2972,7 +3144,16 @@ var locales = {
2972
3144
  columnWidth: "列宽",
2973
3145
  rowHeight: "行高",
2974
3146
  tableBorderWidth: "表格边框宽度",
3147
+ tableBorderColor: "表格边框颜色",
2975
3148
  deleteTable: "删除表格",
3149
+ cellAlignLeft: "左对齐",
3150
+ cellAlignCenter: "居中对齐",
3151
+ cellAlignRight: "右对齐",
3152
+ cellAlignJustify: "两端对齐",
3153
+ toggleHeaderRow: "切换标题行",
3154
+ cellBackground: "单元格背景",
3155
+ noShading: "无底纹",
3156
+ noBorderColor: "无边框颜色",
2976
3157
  columnWidthPx: "列宽 (px)",
2977
3158
  rowHeightPx: "行高 (px)",
2978
3159
  tableBorderWidthPx: "表格边框宽度 (px)",
@@ -3000,6 +3181,12 @@ var locales = {
3000
3181
  errors: {
3001
3182
  imageFormat: (type) => `格式 "${type}" 不支持在浏览器中显示。请转换为 JPEG、PNG 或 WebP。`,
3002
3183
  imageSize: (maxSize) => `图片文件过大。最大允许大小为 ${maxSize} MB。`
3184
+ },
3185
+ autoSaveRestore: {
3186
+ found: "找到草稿。是否恢复?",
3187
+ foundAt: "发现 {date} 的草稿。是否恢复?",
3188
+ restore: "恢复",
3189
+ discard: "放弃"
3003
3190
  }
3004
3191
  },
3005
3192
  fr: {
@@ -3143,6 +3330,7 @@ var locales = {
3143
3330
  findPlaceholder: "Rechercher…",
3144
3331
  searchAriaLabel: "Texte à rechercher",
3145
3332
  caseSensitive: "\xA0Respecter la casse",
3333
+ wholeWord: "Mot entier",
3146
3334
  prevBtn: "← Préc.",
3147
3335
  nextBtn: "Suiv. →",
3148
3336
  replacePlaceholder: "Remplacer par…",
@@ -3310,7 +3498,16 @@ var locales = {
3310
3498
  columnWidth: "Largeur de colonne",
3311
3499
  rowHeight: "Hauteur de ligne",
3312
3500
  tableBorderWidth: "Épaisseur des bordures",
3501
+ tableBorderColor: "Couleur de bordure",
3313
3502
  deleteTable: "Supprimer le tableau",
3503
+ cellAlignLeft: "Aligner à gauche",
3504
+ cellAlignCenter: "Centrer",
3505
+ cellAlignRight: "Aligner à droite",
3506
+ cellAlignJustify: "Justifier",
3507
+ toggleHeaderRow: "Ligne d'en-tête",
3508
+ cellBackground: "Couleur de fond",
3509
+ noShading: "Aucun fond",
3510
+ noBorderColor: "Aucune bordure",
3314
3511
  columnWidthPx: "Largeur de colonne (px)",
3315
3512
  rowHeightPx: "Hauteur de ligne (px)",
3316
3513
  tableBorderWidthPx: "Épaisseur des bordures (px)",
@@ -3338,6 +3535,12 @@ var locales = {
3338
3535
  errors: {
3339
3536
  imageFormat: (type) => `Le format "${type}" n'est pas pris en charge par le navigateur. Veuillez le convertir en JPEG, PNG ou WebP.`,
3340
3537
  imageSize: (maxSize) => `Le fichier image est trop volumineux. La taille maximale autorisée est de ${maxSize}\u00a0Mo.`
3538
+ },
3539
+ autoSaveRestore: {
3540
+ found: "Brouillon trouvé. Restaurer ?",
3541
+ foundAt: "Brouillon du {date}. Restaurer ?",
3542
+ restore: "Restaurer",
3543
+ discard: "Ignorer"
3341
3544
  }
3342
3545
  },
3343
3546
  de: {
@@ -3481,6 +3684,7 @@ var locales = {
3481
3684
  findPlaceholder: "Suchen…",
3482
3685
  searchAriaLabel: "Suchtext",
3483
3686
  caseSensitive: "\xA0Groß-/Kleinschreibung",
3687
+ wholeWord: "Ganzes Wort",
3484
3688
  prevBtn: "← Zurück",
3485
3689
  nextBtn: "Weiter →",
3486
3690
  replacePlaceholder: "Ersetzen durch…",
@@ -3648,7 +3852,16 @@ var locales = {
3648
3852
  columnWidth: "Spaltenbreite",
3649
3853
  rowHeight: "Zeilenhöhe",
3650
3854
  tableBorderWidth: "Tabellenrahmenbreite",
3855
+ tableBorderColor: "Tabellenrahmenfarbe",
3651
3856
  deleteTable: "Tabelle löschen",
3857
+ cellAlignLeft: "Linksbündig",
3858
+ cellAlignCenter: "Zentrieren",
3859
+ cellAlignRight: "Rechtsbündig",
3860
+ cellAlignJustify: "Blocksatz",
3861
+ toggleHeaderRow: "Kopfzeile umschalten",
3862
+ cellBackground: "Zellhintergrund",
3863
+ noShading: "Kein Hintergrund",
3864
+ noBorderColor: "Keine Rahmenfarbe",
3652
3865
  columnWidthPx: "Spaltenbreite (px)",
3653
3866
  rowHeightPx: "Zeilenhöhe (px)",
3654
3867
  tableBorderWidthPx: "Tabellenrahmenbreite (px)",
@@ -3676,6 +3889,12 @@ var locales = {
3676
3889
  errors: {
3677
3890
  imageFormat: (type) => `Das Format „${type}" wird in Webbrowsern nicht unterstützt. Bitte konvertieren Sie es zuerst in JPEG, PNG oder WebP.`,
3678
3891
  imageSize: (maxSize) => `Die Bilddatei ist zu groß. Die maximal zulässige Größe beträgt ${maxSize} MB.`
3892
+ },
3893
+ autoSaveRestore: {
3894
+ found: "Entwurf gefunden. Wiederherstellen?",
3895
+ foundAt: "Entwurf vom {date}. Wiederherstellen?",
3896
+ restore: "Wiederherstellen",
3897
+ discard: "Verwerfen"
3679
3898
  }
3680
3899
  },
3681
3900
  es: {
@@ -3819,6 +4038,7 @@ var locales = {
3819
4038
  findPlaceholder: "Buscar…",
3820
4039
  searchAriaLabel: "Texto de búsqueda",
3821
4040
  caseSensitive: "\xA0Distinguir mayúsculas",
4041
+ wholeWord: "Palabra completa",
3822
4042
  prevBtn: "← Anterior",
3823
4043
  nextBtn: "Siguiente →",
3824
4044
  replacePlaceholder: "Reemplazar con…",
@@ -3986,7 +4206,16 @@ var locales = {
3986
4206
  columnWidth: "Ancho de columna",
3987
4207
  rowHeight: "Alto de fila",
3988
4208
  tableBorderWidth: "Grosor del borde de la tabla",
4209
+ tableBorderColor: "Color del borde de la tabla",
3989
4210
  deleteTable: "Eliminar tabla",
4211
+ cellAlignLeft: "Alinear a la izquierda",
4212
+ cellAlignCenter: "Centrar",
4213
+ cellAlignRight: "Alinear a la derecha",
4214
+ cellAlignJustify: "Justificar",
4215
+ toggleHeaderRow: "Activar fila de encabezado",
4216
+ cellBackground: "Fondo de celda",
4217
+ noShading: "Sin fondo",
4218
+ noBorderColor: "Sin color de borde",
3990
4219
  columnWidthPx: "Ancho de columna (px)",
3991
4220
  rowHeightPx: "Alto de fila (px)",
3992
4221
  tableBorderWidthPx: "Grosor del borde (px)",
@@ -4014,6 +4243,12 @@ var locales = {
4014
4243
  errors: {
4015
4244
  imageFormat: (type) => `El formato "${type}" no es compatible con los navegadores web. Por favor, conviértalo primero a JPEG, PNG o WebP.`,
4016
4245
  imageSize: (maxSize) => `El archivo de imagen es demasiado grande. El tamaño máximo permitido es ${maxSize} MB.`
4246
+ },
4247
+ autoSaveRestore: {
4248
+ found: "Borrador encontrado. ¿Restaurar?",
4249
+ foundAt: "Borrador del {date}. ¿Restaurar?",
4250
+ restore: "Restaurar",
4251
+ discard: "Descartar"
4017
4252
  }
4018
4253
  },
4019
4254
  ko: {
@@ -4157,6 +4392,7 @@ var locales = {
4157
4392
  findPlaceholder: "찾기…",
4158
4393
  searchAriaLabel: "검색 텍스트",
4159
4394
  caseSensitive: "\xA0대소문자 구분",
4395
+ wholeWord: "전체 단어",
4160
4396
  prevBtn: "← 이전",
4161
4397
  nextBtn: "다음 →",
4162
4398
  replacePlaceholder: "바꿀 내용…",
@@ -4324,7 +4560,16 @@ var locales = {
4324
4560
  columnWidth: "열 너비",
4325
4561
  rowHeight: "행 높이",
4326
4562
  tableBorderWidth: "표 테두리 너비",
4563
+ tableBorderColor: "표 테두리 색상",
4327
4564
  deleteTable: "표 삭제",
4565
+ cellAlignLeft: "왼쪽 정렬",
4566
+ cellAlignCenter: "가운데 정렬",
4567
+ cellAlignRight: "오른쪽 정렬",
4568
+ cellAlignJustify: "양쪽 정렬",
4569
+ toggleHeaderRow: "머리글 행 전환",
4570
+ cellBackground: "셀 배경색",
4571
+ noShading: "배경 없음",
4572
+ noBorderColor: "테두리 색 없음",
4328
4573
  columnWidthPx: "열 너비 (px)",
4329
4574
  rowHeightPx: "행 높이 (px)",
4330
4575
  tableBorderWidthPx: "표 테두리 너비 (px)",
@@ -4352,6 +4597,12 @@ var locales = {
4352
4597
  errors: {
4353
4598
  imageFormat: (type) => `"${type}" 형식은 웹 브라우저에서 지원되지 않습니다. JPEG, PNG 또는 WebP로 변환해 주세요.`,
4354
4599
  imageSize: (maxSize) => `이미지 파일이 너무 큽니다. 최대 허용 크기는 ${maxSize} MB입니다.`
4600
+ },
4601
+ autoSaveRestore: {
4602
+ found: "초안을 찾았습니다. 복원하시겠습니까?",
4603
+ foundAt: "{date}의 초안을 찾았습니다. 복원하시겠습니까?",
4604
+ restore: "복원",
4605
+ discard: "삭제"
4355
4606
  }
4356
4607
  }
4357
4608
  };
@@ -4836,7 +5087,11 @@ function insertTable(cols, rows, opts = {}) {
4836
5087
  const sel = globalThis.getSelection();
4837
5088
  if (!sel || sel.rangeCount === 0) return;
4838
5089
  const range = sel.getRangeAt(0);
4839
- range.deleteContents();
5090
+ try {
5091
+ range.deleteContents();
5092
+ } catch (_) {
5093
+ return;
5094
+ }
4840
5095
  const BLOCK = new Set([
4841
5096
  "P",
4842
5097
  "DIV",
@@ -4861,7 +5116,11 @@ function insertTable(cols, rows, opts = {}) {
4861
5116
  table.after(p);
4862
5117
  }
4863
5118
  if (!anchor.textContent.trim() && !anchor.querySelector("img, video, table")) anchor.remove();
4864
- } else range.insertNode(table);
5119
+ } else try {
5120
+ range.insertNode(table);
5121
+ } catch (_) {
5122
+ return;
5123
+ }
4865
5124
  const firstCell = table.querySelector("td, th");
4866
5125
  if (firstCell) {
4867
5126
  const nr = document.createRange();
@@ -4944,6 +5203,23 @@ var _FA_PATTERN = /\bfa-/;
4944
5203
  var isFAIcon = (n) => !!(n?.nodeName === "I" && _FA_PATTERN.test(n.className || ""));
4945
5204
  var isZwsAnchor = (n) => !!(n?.nodeType === Node.TEXT_NODE && (n.textContent === "​" || n.textContent === ""));
4946
5205
  /**
5206
+ * Extracts the content from `startContainer:startOffset` to the end of `li`.
5207
+ * Returns an empty fragment if the range is invalid (e.g. detached node).
5208
+ * @param {Range} nativeRange
5209
+ * @param {Element} li
5210
+ * @returns {DocumentFragment}
5211
+ */
5212
+ function extractAfterContent(nativeRange, li) {
5213
+ try {
5214
+ const r = document.createRange();
5215
+ r.setStart(nativeRange.startContainer, nativeRange.startOffset);
5216
+ r.setEnd(li, li.childNodes.length);
5217
+ return r.extractContents();
5218
+ } catch (_) {
5219
+ return document.createDocumentFragment();
5220
+ }
5221
+ }
5222
+ /**
4947
5223
  * Handles special keydown behaviour inside the editor.
4948
5224
  * @param {KeyboardEvent} event
4949
5225
  * @param {HTMLElement} editable
@@ -5151,12 +5427,10 @@ function handleKeydown(event, editable, options = {}) {
5151
5427
  }
5152
5428
  if (!nativeRange.collapsed) {
5153
5429
  nativeRange.deleteContents();
5430
+ if (sel.rangeCount === 0 || !checkLi.isConnected) return true;
5154
5431
  nativeRange = sel.getRangeAt(0);
5155
5432
  }
5156
- const afterRange = document.createRange();
5157
- afterRange.setStart(nativeRange.startContainer, nativeRange.startOffset);
5158
- afterRange.setEnd(checkLi, checkLi.childNodes.length);
5159
- const afterFrag = afterRange.extractContents();
5433
+ const afterFrag = extractAfterContent(nativeRange, checkLi);
5160
5434
  const newLi = document.createElement("li");
5161
5435
  const cb = document.createElement("input");
5162
5436
  cb.type = "checkbox";
@@ -5272,7 +5546,15 @@ function _domToMd(node, depth = 0) {
5272
5546
  const items = Array.from(el.querySelectorAll(":scope > li"));
5273
5547
  if (!items.length) return inner();
5274
5548
  const indent = " ".repeat(depth);
5275
- const lines = items.map((li) => `${indent}- ${_domToMd(li, depth + 1).trim()}`).join("\n");
5549
+ const isChecklist = el.classList.contains("an-checklist");
5550
+ const lines = items.map((li) => {
5551
+ let prefix = "- ";
5552
+ if (isChecklist) {
5553
+ const cb = li.querySelector("input[type=\"checkbox\"]");
5554
+ prefix = (cb ? cb.checked : false) ? "- [x] " : "- [ ] ";
5555
+ }
5556
+ return `${indent}${prefix}${_domToMd(li, depth + 1).trim()}`;
5557
+ }).join("\n");
5276
5558
  return depth === 0 ? `\n\n${lines}\n\n` : `\n${lines}`;
5277
5559
  }
5278
5560
  case "ol": {
@@ -5312,7 +5594,7 @@ function _domToMd(node, depth = 0) {
5312
5594
  * @returns {boolean} `true` if any Markdown-like pattern is present, `false` otherwise.
5313
5595
  */
5314
5596
  function isMarkdown(text) {
5315
- return /^#{1,6} \S|^\s*[-*+] \S|^\s*\d+\. \S|^> \S|^```|^\*{2}.+?\*{2}/m.test(text);
5597
+ return /^#{1,6} [^\s]|^[ \t]*[-*+] [^\s]|^[ \t]*\d+\. [^\s]|^> [^\s]|^```|^\*{2}[^*\n]+\*{2}/m.test(text);
5316
5598
  }
5317
5599
  /**
5318
5600
  * Converts a Markdown string to an HTML string.
@@ -5362,11 +5644,20 @@ function markdownToHTML(text) {
5362
5644
  }
5363
5645
  if (/^[-*+] /.test(line)) {
5364
5646
  const items = [];
5647
+ const isChecklist = /^[-*+]\s+\[[ xX]\]\s+/.test(line);
5648
+ const listTag = isChecklist ? "ul class=\"an-checklist\"" : "ul";
5365
5649
  while (i < lines.length && /^[-*+] /.test(lines[i])) {
5366
- items.push(`<li>${_inline(lines[i].slice(2))}</li>`);
5650
+ if (/^[-*+]\s+\[[ xX]\]\s+/.test(lines[i]) !== isChecklist) break;
5651
+ const content = lines[i].slice(2);
5652
+ if (isChecklist) {
5653
+ const cbMatch = /^\[([ xX])\][ \t]+/.exec(content);
5654
+ const cbHtml = `<input type="checkbox" contenteditable="false"${cbMatch?.[1]?.toLowerCase() === "x" ? " checked" : ""}>`;
5655
+ const textContent = cbMatch ? content.slice(cbMatch[0].length) : content;
5656
+ items.push(`<li>${cbHtml}${_inline(textContent)}</li>`);
5657
+ } else items.push(`<li>${_inline(content)}</li>`);
5367
5658
  i++;
5368
5659
  }
5369
- out.push(`<ul>${items.join("")}</ul>`);
5660
+ out.push(`<${listTag}>${items.join("")}</${listTag.split(" ")[0]}>`);
5370
5661
  continue;
5371
5662
  }
5372
5663
  if (/^\d+\. /.test(line)) {
@@ -5423,7 +5714,7 @@ function _inline(text) {
5423
5714
  text = text.replace(/_{2}([^_\n]+?)_{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
5424
5715
  text = text.replace(/\*([^*\n]+?)\*/g, (_, c) => `<em>${_esc(c)}</em>`);
5425
5716
  text = text.replace(/_([^_\n]+?)_/g, (_, c) => `<em>${_esc(c)}</em>`);
5426
- text = text.replace(/~~([^\n]+?)~~/g, (_, c) => `<del>${_esc(c)}</del>`);
5717
+ text = text.replace(/~~([^~\n]+?)~~/g, (_, c) => `<del>${_esc(c)}</del>`);
5427
5718
  text = text.replace(/`([^`]+)`/g, (_, c) => `<code>${_esc(c)}</code>`);
5428
5719
  return text;
5429
5720
  }
@@ -6977,6 +7268,15 @@ var Clipboard = class {
6977
7268
  if (!clipboardData) return;
6978
7269
  const forcePlain = this._forcePlain;
6979
7270
  this._forcePlain = false;
7271
+ const maxBytes = (this.options.maxPasteSize ?? 5) * 1024 * 1024;
7272
+ if (maxBytes > 0) {
7273
+ const text = clipboardData.getData("text/plain") || "";
7274
+ const html = clipboardData.getData("text/html") || "";
7275
+ if (Math.max(text.length, html.length) > maxBytes) {
7276
+ event.preventDefault();
7277
+ return;
7278
+ }
7279
+ }
6980
7280
  if (clipboardData.items) {
6981
7281
  const imageItems = Array.from(clipboardData.items).filter((item) => item.kind === "file" && item.type.startsWith("image/"));
6982
7282
  if (imageItems.length > 0) {
@@ -7113,7 +7413,7 @@ var Clipboard = class {
7113
7413
  const mime = /:(.*?);/.exec(header)?.[1] ?? "image/png";
7114
7414
  const binary = atob(b64);
7115
7415
  const arr = new Uint8Array(binary.length);
7116
- for (let i = 0; i < binary.length; i++) arr[i] = binary.codePointAt(i);
7416
+ for (let i = 0; i < binary.length; i++) arr[i] = binary.charCodeAt(i);
7117
7417
  return new Blob([arr], { type: mime });
7118
7418
  }
7119
7419
  /**
@@ -7719,6 +8019,7 @@ var ImageDialog = class extends BaseDialog {
7719
8019
  return overlay;
7720
8020
  }
7721
8021
  _onFileChange() {
8022
+ if (this.context._alive === false) return;
7722
8023
  const file = this._fileInput?.files?.[0];
7723
8024
  if (!file?.type?.startsWith("image/")) return;
7724
8025
  if (!new Set([
@@ -8099,6 +8400,7 @@ var ImageResizer = class {
8099
8400
  const aspectRatio = startW / (startH || 1);
8100
8401
  const isCorner = pos.length === 2;
8101
8402
  const editable = this.context.layoutInfo.editable;
8403
+ const minSz = this.context.options?.minImageSize ?? 20;
8102
8404
  let _raf = null;
8103
8405
  const onMove = (me) => {
8104
8406
  if (_raf !== null) return;
@@ -8111,15 +8413,15 @@ var ImageResizer = class {
8111
8413
  const maxW = editable.clientWidth || Infinity;
8112
8414
  let newW = startW;
8113
8415
  let newH = startH;
8114
- if (pos.includes("e")) newW = Math.max(20, startW + dx);
8115
- if (pos.includes("w")) newW = Math.max(20, startW - dx);
8116
- if (pos.includes("s")) newH = Math.max(20, startH + dy);
8117
- if (pos.includes("n")) newH = Math.max(20, startH - dy);
8416
+ if (pos.includes("e")) newW = Math.max(minSz, startW + dx);
8417
+ if (pos.includes("w")) newW = Math.max(minSz, startW - dx);
8418
+ if (pos.includes("s")) newH = Math.max(minSz, startH + dy);
8419
+ if (pos.includes("n")) newH = Math.max(minSz, startH - dy);
8118
8420
  newW = Math.min(newW, maxW);
8119
- if (isCorner) if (Math.abs(dx) >= Math.abs(dy)) newH = Math.max(20, Math.round(newW / aspectRatio));
8421
+ if (isCorner) if (Math.abs(dx) >= Math.abs(dy)) newH = Math.max(minSz, Math.round(newW / aspectRatio));
8120
8422
  else {
8121
- newW = Math.min(Math.max(20, Math.round(newH * aspectRatio)), maxW);
8122
- newH = Math.max(20, Math.round(newW / aspectRatio));
8423
+ newW = Math.min(Math.max(minSz, Math.round(newH * aspectRatio)), maxW);
8424
+ newH = Math.max(minSz, Math.round(newW / aspectRatio));
8123
8425
  }
8124
8426
  img.style.width = `${newW}px`;
8125
8427
  img.style.height = `${newH}px`;
@@ -10283,8 +10585,28 @@ var TableTooltip = class {
10283
10585
  return direction === "asc" ? aText.localeCompare(bText) : bText.localeCompare(aText);
10284
10586
  });
10285
10587
  rows.forEach((row) => tbody.appendChild(row));
10588
+ this._markSortIndicator(table, colIdx, direction);
10286
10589
  this.context.invoke("editor.afterCommand");
10287
10590
  }
10591
+ /**
10592
+ * Marks the header cell of the sorted column with `an-sort-asc`/`an-sort-desc`
10593
+ * so the active sort column and direction are visible, and clears any previous
10594
+ * indicator. No-op for tables without a `<thead>` (no header row to mark).
10595
+ * @param {HTMLTableElement} table
10596
+ * @param {number} colIdx
10597
+ * @param {'asc'|'desc'} direction
10598
+ */
10599
+ _markSortIndicator(table, colIdx, direction) {
10600
+ const thead = table.querySelector("thead");
10601
+ if (!thead) return;
10602
+ thead.querySelectorAll(".an-sort-asc, .an-sort-desc").forEach((el) => {
10603
+ el.classList.remove("an-sort-asc", "an-sort-desc");
10604
+ });
10605
+ const headerRow = thead.querySelector("tr");
10606
+ if (!headerRow) return;
10607
+ const headerCell = getCellAtVisualCol(headerRow, colIdx);
10608
+ if (headerCell) headerCell.classList.add(direction === "asc" ? "an-sort-asc" : "an-sort-desc");
10609
+ }
10288
10610
  _exportTableCSV() {
10289
10611
  const table = this._activeTable;
10290
10612
  if (!table) return;
@@ -14562,6 +14884,7 @@ var FindReplace = class extends BaseDialog {
14562
14884
  this._currentIndex = -1;
14563
14885
  this._caseSensitive = false;
14564
14886
  this._useRegex = false;
14887
+ this._wholeWord = false;
14565
14888
  /** @type {'find'|'replace'} */
14566
14889
  this._mode = "find";
14567
14890
  /** Cached compiled regex — reused when query and case-sensitivity are unchanged */
@@ -14569,6 +14892,7 @@ var FindReplace = class extends BaseDialog {
14569
14892
  this._lastQuery = null;
14570
14893
  this._lastCaseSensitive = null;
14571
14894
  this._lastUseRegex = null;
14895
+ this._lastWholeWord = null;
14572
14896
  this._focusTimer = null;
14573
14897
  }
14574
14898
  destroy() {
@@ -14679,6 +15003,13 @@ var FindReplace = class extends BaseDialog {
14679
15003
  "aria-label": L.useRegex
14680
15004
  });
14681
15005
  regexBtn.textContent = ".*";
15006
+ const wholeWordBtn = createElement("button", {
15007
+ type: "button",
15008
+ class: "an-fr-icon-btn",
15009
+ title: L.wholeWord,
15010
+ "aria-label": L.wholeWord
15011
+ });
15012
+ wholeWordBtn.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" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="10" rx="2"/><line x1="7" y1="21" x2="7" y2="17"/><line x1="17" y1="21" x2="17" y2="17"/></svg>`;
14682
15013
  const prevBtn = createElement("button", {
14683
15014
  type: "button",
14684
15015
  class: "an-fr-icon-btn",
@@ -14695,7 +15026,7 @@ var FindReplace = class extends BaseDialog {
14695
15026
  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>`;
14696
15027
  const counter = createElement("span", { class: "an-fr-counter" });
14697
15028
  this._counterEl = counter;
14698
- searchBar.append(findInput, caseCheckbox, caseBtn, regexBtn, prevBtn, nextBtn, counter);
15029
+ searchBar.append(findInput, caseCheckbox, caseBtn, regexBtn, wholeWordBtn, prevBtn, nextBtn, counter);
14699
15030
  box.appendChild(searchBar);
14700
15031
  const replaceRow = createElement("div", { class: "an-fr-replace-row" });
14701
15032
  replaceRow.style.display = "none";
@@ -14763,7 +15094,14 @@ var FindReplace = class extends BaseDialog {
14763
15094
  this._lastQuery = null;
14764
15095
  this._onSearch();
14765
15096
  });
14766
- this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, dRegex);
15097
+ const dWholeWord = on(wholeWordBtn, "click", () => {
15098
+ this._wholeWord = !this._wholeWord;
15099
+ wholeWordBtn.classList.toggle("an-fr-icon-btn--active", this._wholeWord);
15100
+ this._queryRegex = null;
15101
+ this._lastQuery = null;
15102
+ this._onSearch();
15103
+ });
15104
+ this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, dRegex, dWholeWord);
14767
15105
  return overlay;
14768
15106
  }
14769
15107
  _onSearch() {
@@ -14821,10 +15159,11 @@ var FindReplace = class extends BaseDialog {
14821
15159
  */
14822
15160
  _findRawMatches(query, root) {
14823
15161
  const results = [];
14824
- if (this._lastQuery !== query || this._lastCaseSensitive !== this._caseSensitive || this._lastUseRegex !== this._useRegex) {
15162
+ if (this._lastQuery !== query || this._lastCaseSensitive !== this._caseSensitive || this._lastUseRegex !== this._useRegex || this._lastWholeWord !== this._wholeWord) {
14825
15163
  const flags = this._caseSensitive ? "g" : "gi";
14826
15164
  try {
14827
- const pattern = this._useRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
15165
+ let pattern = this._useRegex ? query : query.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
15166
+ if (this._wholeWord) pattern = `\\b${pattern}\\b`;
14828
15167
  this._queryRegex = new RegExp(pattern, flags);
14829
15168
  } catch (_) {
14830
15169
  this._queryRegex = null;
@@ -14832,6 +15171,7 @@ var FindReplace = class extends BaseDialog {
14832
15171
  this._lastQuery = query;
14833
15172
  this._lastCaseSensitive = this._caseSensitive;
14834
15173
  this._lastUseRegex = this._useRegex;
15174
+ this._lastWholeWord = this._wholeWord;
14835
15175
  }
14836
15176
  if (!this._queryRegex) return results;
14837
15177
  const re = this._queryRegex;
@@ -16185,6 +16525,7 @@ var BubbleToolbar = class {
16185
16525
  * debounce: 200,
16186
16526
  * onSearch: (query, callback) => void,
16187
16527
  * onInsert: (item) => string | null,
16528
+ * onError: (err: Error) => void,
16188
16529
  * mentionClass: 'an-mention',
16189
16530
  * allowSpaces: false,
16190
16531
  * }
@@ -16377,8 +16718,18 @@ var Mention = class {
16377
16718
  this._renderItems(items);
16378
16719
  this._showDropdown();
16379
16720
  };
16380
- const result = this._cfg.onSearch(this._query, cb);
16381
- if (result && typeof result.then === "function") result.then(cb).catch(() => this._hideDropdown());
16721
+ let result;
16722
+ try {
16723
+ result = this._cfg.onSearch(this._query, cb);
16724
+ } catch (err) {
16725
+ this._hideDropdown();
16726
+ if (typeof this._cfg.onError === "function") this._cfg.onError(err);
16727
+ return;
16728
+ }
16729
+ if (result && typeof result.then === "function") result.then(cb).catch((err) => {
16730
+ this._hideDropdown();
16731
+ if (typeof this._cfg.onError === "function") this._cfg.onError(err);
16732
+ });
16382
16733
  }, this._cfg.debounce);
16383
16734
  }
16384
16735
  _onKeydown(e) {
@@ -16646,10 +16997,13 @@ var Context = class {
16646
16997
  }
16647
16998
  /**
16648
16999
  * Returns the current HTML content of the editor.
17000
+ * Zero-width spaces (U+200B) inserted by inline editing helpers are stripped
17001
+ * from the output so they don't leak into the consumer's HTML.
16649
17002
  * @returns {string}
16650
17003
  */
16651
17004
  getHTML() {
16652
- return this.invoke("editor.getHTML");
17005
+ const html = this.invoke("editor.getHTML");
17006
+ return typeof html === "string" ? html.replace(/​/g, "") : html;
16653
17007
  }
16654
17008
  /**
16655
17009
  * Sets the HTML content of the editor.
@@ -16820,6 +17174,25 @@ var Context = class {
16820
17174
  }));
16821
17175
  }
16822
17176
  /**
17177
+ * Moves focus into the editable area.
17178
+ */
17179
+ focus() {
17180
+ this.layoutInfo.editable.focus();
17181
+ }
17182
+ /**
17183
+ * Removes focus from the editable area.
17184
+ */
17185
+ blur() {
17186
+ this.layoutInfo.editable.blur();
17187
+ }
17188
+ /**
17189
+ * Returns true when the editor is currently in fullscreen mode.
17190
+ * @returns {boolean}
17191
+ */
17192
+ isFullscreen() {
17193
+ return this.invoke("fullscreen.isActive") === true;
17194
+ }
17195
+ /**
16823
17196
  * Sets whether the editor is disabled (readonly).
16824
17197
  * @param {boolean} disabled
16825
17198
  */
@@ -17115,8 +17488,10 @@ var AutumnNote = {
17115
17488
  registerButton(btnDef);
17116
17489
  return this;
17117
17490
  },
17491
+ /** All pre-built button definitions — accessible in every module format including UMD/CJS. */
17492
+ buttons,
17118
17493
  /** Library version */
17119
- version: "1.7.0"
17494
+ version: "1.8.0"
17120
17495
  };
17121
17496
  /**
17122
17497
  * @param {string|Element|NodeList|Element[]} selector
@@ -17129,6 +17504,6 @@ function resolveElements(selector) {
17129
17504
  return [];
17130
17505
  }
17131
17506
  //#endregion
17132
- export { Context, ELEMENT_NODE, TEXT_NODE, WrappedRange, _buttonRegistry, alignCenterBtn, alignJustifyBtn, alignLeftBtn, alignRightBtn, all, ancestors, any, backColorBtn, boldBtn, checklistBtn, children, chunk, clamp, closest, closestPara, codeviewBtn, collapsedRange, compose, createElement, currentRange, debounce, AutumnNote as default, defaultOptions, defaultToolbar, directionBtn, emojiBtn, env, findBtn, findReplaceBtn, first, flatten, fontFamilyBtn, fontSizeBtn, foreColorBtn, fromNativeRange, fullscreenBtn, getButton, groupBy, hrBtn, iconBtn, identity, imageBtn, indentBtn, initial, inlineCodeBtn, insertAfter, isAnchor, isEditable, isElement, isEmpty, isFunction, isImage, isInline, isInsideEditable, isKey, isLi, isList, isModifier, isNil, isPara, isPlainObject, isSelectionInside, isString, isTable, isText, isVoid, italicBtn, key, last, lineHeightBtn, linkBtn, locales, makeDraggable, mergeDeep, nextElement, nodeValue, olBtn, on, outdentBtn, outerHtml, paragraphStyleBtn, placeCaret, prevElement, printBtn, rangeFromElement, rect2bnd, redoBtn, registerButton, remove, removeFormatBtn, resolveLocale, sanitiseHTML, sanitiseUrl, shortcutsBtn, splitText, strikeBtn, subscriptBtn, superscriptBtn, tableBtn, tail, throttle, trapFocus, ulBtn, underlineBtn, undoBtn, unique, unwrap, videoBtn, withSavedRange, wrap };
17507
+ export { Context, ELEMENT_NODE, TEXT_NODE, WrappedRange, _buttonRegistry, alignCenterBtn, alignJustifyBtn, alignLeftBtn, alignRightBtn, all, ancestors, any, backColorBtn, boldBtn, buttons, checklistBtn, children, chunk, clamp, closest, closestPara, codeviewBtn, collapsedRange, compose, createElement, currentRange, debounce, AutumnNote as default, defaultOptions, defaultToolbar, directionBtn, emojiBtn, env, findBtn, findReplaceBtn, first, flatten, fontFamilyBtn, fontSizeBtn, foreColorBtn, fromNativeRange, fullscreenBtn, getButton, groupBy, hrBtn, iconBtn, identity, imageBtn, indentBtn, initial, inlineCodeBtn, insertAfter, isAnchor, isEditable, isElement, isEmpty, isFunction, isImage, isInline, isInsideEditable, isKey, isLi, isList, isModifier, isNil, isPara, isPlainObject, isSelectionInside, isString, isTable, isText, isVoid, italicBtn, key, last, lineHeightBtn, linkBtn, locales, makeDraggable, mergeDeep, nextElement, nodeValue, olBtn, on, outdentBtn, outerHtml, paragraphStyleBtn, placeCaret, prevElement, printBtn, rangeFromElement, rect2bnd, redoBtn, registerButton, remove, removeFormatBtn, resolveLocale, sanitiseHTML, sanitiseUrl, shortcutsBtn, splitText, strikeBtn, subscriptBtn, superscriptBtn, tableBtn, tail, throttle, trapFocus, ulBtn, underlineBtn, undoBtn, unique, unwrap, videoBtn, withSavedRange, wrap };
17133
17508
 
17134
17509
  //# sourceMappingURL=autumnnote.es.js.map