autumnnote 1.8.0 → 1.8.2

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.
@@ -520,13 +520,74 @@
520
520
  } catch {}
521
521
  }
522
522
  /**
523
- * Inserts an unordered list or converts selection.
523
+ * Inserts an unordered (bulleted) list, or converts the current list to `<ul>`.
524
+ *
525
+ * When the cursor is already inside a list, direct DOM manipulation is used to
526
+ * transition between list types — `execCommand` alone cannot handle checklist →
527
+ * UL/OL conversions because it has no awareness of the `an-checklist` class or
528
+ * the checkbox `<input>` elements.
529
+ *
530
+ * Transition paths:
531
+ * - **Checklist → UL**: strips `an-checklist` class and all checkbox inputs;
532
+ * converts `<ol>` container to `<ul>` via `changeTagName()` if needed.
533
+ * - **OL → UL**: swaps the container tag via `changeTagName()`.
534
+ * - **UL → paragraphs**: falls back to `execCommand('insertUnorderedList')`
535
+ * which toggles the list off (browser-native behaviour).
536
+ * - **No list → UL**: falls back to `execCommand('insertUnorderedList')`.
537
+ */
538
+ /**
539
+ * Helper to get the closest ul/ol element containing the current selection.
540
+ * @returns {Element|null}
541
+ */
542
+ function getSelectedList() {
543
+ const sel = globalThis.getSelection();
544
+ if (!sel?.rangeCount) return null;
545
+ let container = sel.getRangeAt(0).commonAncestorContainer;
546
+ if (container.nodeType === 3) container = container.parentElement;
547
+ return container?.closest("ul, ol") || null;
548
+ }
549
+ /**
550
+ * Strips the checklist class and checkbox inputs from a list element.
551
+ * @param {Element} listEl
524
552
  */
525
- var insertUnorderedList = () => execCommand("insertUnorderedList");
553
+ function stripChecklist(listEl) {
554
+ listEl.classList.remove("an-checklist");
555
+ listEl.querySelectorAll("input[type=\"checkbox\"]").forEach((cb) => cb.remove());
556
+ }
557
+ function insertUnorderedList() {
558
+ const listEl = getSelectedList();
559
+ if (listEl) if (listEl.classList.contains("an-checklist")) {
560
+ stripChecklist(listEl);
561
+ if (listEl.tagName === "OL") changeTagName(listEl, "ul");
562
+ } else if (listEl.tagName === "OL") changeTagName(listEl, "ul");
563
+ else execCommand("insertUnorderedList");
564
+ else execCommand("insertUnorderedList");
565
+ }
526
566
  /**
527
- * Inserts an ordered list or converts selection.
567
+ * Inserts an ordered (numbered) list, or converts the current list to `<ol>`.
568
+ *
569
+ * When the cursor is already inside a list, direct DOM manipulation is used to
570
+ * transition between list types — `execCommand` alone cannot handle checklist →
571
+ * UL/OL conversions because it has no awareness of the `an-checklist` class or
572
+ * the checkbox `<input>` elements.
573
+ *
574
+ * Transition paths:
575
+ * - **Checklist → OL**: strips `an-checklist` class and all checkbox inputs;
576
+ * converts container to `<ol>` via `changeTagName()`.
577
+ * - **UL → OL**: swaps the container tag via `changeTagName()`.
578
+ * - **OL → paragraphs**: falls back to `execCommand('insertOrderedList')`
579
+ * which toggles the list off (browser-native behaviour).
580
+ * - **No list → OL**: falls back to `execCommand('insertOrderedList')`.
528
581
  */
529
- var insertOrderedList = () => execCommand("insertOrderedList");
582
+ function insertOrderedList() {
583
+ const listEl = getSelectedList();
584
+ if (listEl) if (listEl.classList.contains("an-checklist")) {
585
+ stripChecklist(listEl);
586
+ changeTagName(listEl, "ol");
587
+ } else if (listEl.tagName === "UL") changeTagName(listEl, "ol");
588
+ else execCommand("insertOrderedList");
589
+ else execCommand("insertOrderedList");
590
+ }
530
591
  /**
531
592
  * Set the line-height on every block-level element that intersects the current selection.
532
593
  *
@@ -652,18 +713,34 @@
652
713
  return !!(code && !code.closest("pre"));
653
714
  }
654
715
  /**
716
+ * Changes the tag name of an element in the DOM while preserving attributes and children.
717
+ * @param {Element} el
718
+ * @param {string} newTagName
719
+ * @returns {HTMLElement}
720
+ */
721
+ function changeTagName(el, newTagName) {
722
+ const newEl = document.createElement(newTagName);
723
+ for (const attr of el.attributes) newEl.setAttribute(attr.name, attr.value);
724
+ while (el.firstChild) newEl.appendChild(el.firstChild);
725
+ el.parentNode.replaceChild(newEl, el);
726
+ return newEl;
727
+ }
728
+ /**
729
+ * Ensures all list items under the list element have a checkbox.
730
+ * @param {Element} listEl
731
+ */
732
+ function ensureCheckboxes(listEl) {
733
+ listEl.querySelectorAll("li").forEach((li) => {
734
+ if (!li.querySelector("input[type=\"checkbox\"]")) {
735
+ const cb = document.createElement("input");
736
+ cb.type = "checkbox";
737
+ cb.contentEditable = "false";
738
+ li.insertBefore(cb, li.firstChild);
739
+ }
740
+ });
741
+ }
742
+ /**
655
743
  * Toggle a checklist at the current selection or caret.
656
- *
657
- * When the selection is inside an existing checklist `<ul class="an-checklist">`,
658
- * converts the selected `<li>` items back into `<p>` paragraphs and places the caret
659
- * at the start of the first converted paragraph. Otherwise creates a checklist:
660
- * - If the selection is collapsed, converts the nearest block-level ancestor (or inserts
661
- * a single checklist item at the editable root) into a checklist with one item containing
662
- * that block's text and places the caret inside the new item.
663
- * - If the selection is a range, converts each intersecting block element into one checklist
664
- * item (preserving textual content) and places the caret at the end of the last item.
665
- *
666
- * Empty or whitespace-only selections do not create a checklist.
667
744
  */
668
745
  function toggleChecklist() {
669
746
  const sel = globalThis.getSelection();
@@ -671,27 +748,26 @@
671
748
  const range = sel.getRangeAt(0);
672
749
  let container = range.commonAncestorContainer;
673
750
  if (container.nodeType === 3) container = container.parentElement;
674
- const ul = container?.closest(".an-checklist");
675
- if (ul) {
676
- const selectedLis = Array.from(ul.querySelectorAll("li")).filter((li) => sel.containsNode(li, true));
677
- if (selectedLis.length > 0) {
678
- /** @type {HTMLElement|null} */ let firstP = null;
679
- selectedLis.forEach((li) => {
751
+ const listEl = container?.closest("ul, ol");
752
+ if (listEl) if (listEl.classList.contains("an-checklist")) {
753
+ if (listEl.parentNode) {
754
+ const lis = Array.from(listEl.children);
755
+ let firstP = null;
756
+ lis.forEach((li) => {
680
757
  const p = document.createElement("p");
681
758
  for (const child of li.childNodes) {
682
759
  if (child.nodeType === 1 && child.tagName === "INPUT") continue;
683
760
  p.appendChild(child.cloneNode(true));
684
761
  }
685
- p.innerHTML = p.innerHTML.replaceAll("​", "");
762
+ p.innerHTML = p.innerHTML.replaceAll("​", "").replaceAll("​", "");
686
763
  if (!p.hasChildNodes() || !p.textContent.trim()) {
687
764
  p.innerHTML = "";
688
765
  p.appendChild(document.createTextNode("\xA0"));
689
766
  }
690
- ul.parentNode.insertBefore(p, ul);
767
+ listEl.before(p);
691
768
  if (!firstP) firstP = p;
692
- li.remove();
693
769
  });
694
- if (ul.children.length === 0) ul.remove();
770
+ listEl.remove();
695
771
  if (firstP) {
696
772
  const nr = document.createRange();
697
773
  nr.setStart(firstP.firstChild || firstP, 0);
@@ -699,11 +775,65 @@
699
775
  sel.removeAllRanges();
700
776
  sel.addRange(nr);
701
777
  }
702
- return;
778
+ }
779
+ } else {
780
+ const targetUl = changeTagName(listEl, "ul");
781
+ targetUl.classList.add("an-checklist");
782
+ ensureCheckboxes(targetUl);
783
+ const firstLi = targetUl.querySelector("li");
784
+ if (firstLi) {
785
+ const nr = document.createRange();
786
+ nr.selectNodeContents(firstLi);
787
+ nr.collapse(false);
788
+ sel.removeAllRanges();
789
+ sel.addRange(nr);
703
790
  }
704
791
  }
705
- if (range.collapsed) {
706
- const BLOCK_TAGS = new Set([
792
+ else {
793
+ const editableRoot = container?.closest("[contenteditable=\"true\"]");
794
+ if (range.collapsed) {
795
+ const BLOCK_TAGS = new Set([
796
+ "P",
797
+ "DIV",
798
+ "H1",
799
+ "H2",
800
+ "H3",
801
+ "H4",
802
+ "H5",
803
+ "H6",
804
+ "BLOCKQUOTE",
805
+ "LI"
806
+ ]);
807
+ let block = container;
808
+ while (block?.parentNode && block !== editableRoot && !BLOCK_TAGS.has(block.tagName)) block = block.parentNode;
809
+ if (block === editableRoot) block = null;
810
+ const itemText = block && BLOCK_TAGS.has(block.tagName) ? Array.from(block.childNodes).map((n) => n.textContent).join("").replaceAll("\xA0", " ") : "";
811
+ const newUl = document.createElement("ul");
812
+ newUl.className = "an-checklist";
813
+ const li = document.createElement("li");
814
+ const checkbox = document.createElement("input");
815
+ checkbox.type = "checkbox";
816
+ checkbox.contentEditable = "false";
817
+ li.appendChild(checkbox);
818
+ li.appendChild(document.createTextNode(itemText || "​"));
819
+ newUl.appendChild(li);
820
+ if (block && BLOCK_TAGS.has(block.tagName)) block.parentNode.replaceChild(newUl, block);
821
+ else {
822
+ const nativeRange = sel.getRangeAt(0);
823
+ nativeRange.deleteContents();
824
+ nativeRange.insertNode(newUl);
825
+ }
826
+ const textNode = li.lastChild;
827
+ const nr = document.createRange();
828
+ const offset = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent.length : 0;
829
+ nr.setStart(textNode, offset);
830
+ nr.collapse(true);
831
+ sel.removeAllRanges();
832
+ sel.addRange(nr);
833
+ return;
834
+ }
835
+ if (!sel.toString().replace(/[\u00a0\u200B]/g, " ").trim()) return;
836
+ const BLOCK_TAGS_MULTI = new Set([
707
837
  "P",
708
838
  "DIV",
709
839
  "H1",
@@ -713,89 +843,51 @@
713
843
  "H5",
714
844
  "H6",
715
845
  "BLOCKQUOTE",
846
+ "PRE",
716
847
  "LI"
717
848
  ]);
718
- let block = container;
719
- while (block?.parentNode && !BLOCK_TAGS.has(block.tagName)) block = block.parentNode;
720
- const itemText = block && BLOCK_TAGS.has(block.tagName) ? Array.from(block.childNodes).map((n) => n.textContent).join("").replaceAll("\xA0", " ") : "";
721
- const ul = document.createElement("ul");
722
- ul.className = "an-checklist";
723
- const li = document.createElement("li");
724
- const checkbox = document.createElement("input");
725
- checkbox.type = "checkbox";
726
- checkbox.contentEditable = "false";
727
- li.appendChild(checkbox);
728
- li.appendChild(document.createTextNode(itemText || "​"));
729
- ul.appendChild(li);
730
- if (block && BLOCK_TAGS.has(block.tagName)) block.parentNode.replaceChild(ul, block);
731
- else {
732
- const nativeRange = sel.getRangeAt(0);
733
- nativeRange.deleteContents();
734
- nativeRange.insertNode(ul);
849
+ const blocks = [];
850
+ const seenBlocks = /* @__PURE__ */ new Set();
851
+ const commonAncestor = range.commonAncestorContainer;
852
+ const iter = document.createNodeIterator(commonAncestor.nodeType === Node.TEXT_NODE ? commonAncestor.parentNode : commonAncestor, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null);
853
+ let node;
854
+ while (node = iter.nextNode()) {
855
+ if (!range.intersectsNode(node)) continue;
856
+ let blockEl = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
857
+ while (blockEl && blockEl !== editableRoot && !BLOCK_TAGS_MULTI.has(blockEl.tagName)) blockEl = blockEl.parentElement;
858
+ if (blockEl === editableRoot) blockEl = null;
859
+ if (blockEl && !seenBlocks.has(blockEl)) {
860
+ seenBlocks.add(blockEl);
861
+ blocks.push(blockEl);
862
+ }
735
863
  }
736
- const textNode = li.lastChild;
737
- const nr = document.createRange();
738
- const offset = textNode.nodeType === Node.TEXT_NODE ? textNode.textContent.length : 0;
739
- nr.setStart(textNode, offset);
740
- nr.collapse(true);
741
- sel.removeAllRanges();
742
- sel.addRange(nr);
743
- return;
744
- }
745
- if (!sel.toString().replace(/[\u00a0\u200B]/g, " ").trim()) return;
746
- const BLOCK_TAGS_MULTI = new Set([
747
- "P",
748
- "DIV",
749
- "H1",
750
- "H2",
751
- "H3",
752
- "H4",
753
- "H5",
754
- "H6",
755
- "BLOCKQUOTE",
756
- "PRE",
757
- "LI"
758
- ]);
759
- const blocks = [];
760
- const seenBlocks = /* @__PURE__ */ new Set();
761
- const commonAncestor = range.commonAncestorContainer;
762
- const iter = document.createNodeIterator(commonAncestor.nodeType === Node.TEXT_NODE ? commonAncestor.parentNode : commonAncestor, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null);
763
- let node;
764
- while (node = iter.nextNode()) {
765
- if (!range.intersectsNode(node)) continue;
766
- let block = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
767
- while (block && !BLOCK_TAGS_MULTI.has(block.tagName)) block = block.parentElement;
768
- if (block && !seenBlocks.has(block)) {
769
- seenBlocks.add(block);
770
- blocks.push(block);
864
+ if (blocks.length === 0) return;
865
+ const newUl = document.createElement("ul");
866
+ newUl.className = "an-checklist";
867
+ /** @type {Text|null} */ let lastTextNode = null;
868
+ blocks.forEach((block) => {
869
+ const li = document.createElement("li");
870
+ const cb = document.createElement("input");
871
+ cb.type = "checkbox";
872
+ cb.contentEditable = "false";
873
+ li.appendChild(cb);
874
+ const blockText = Array.from(block.childNodes).map((n) => n.textContent).join("").replace(/[\u00a0\u200B]/g, " ").trim();
875
+ const tn = document.createTextNode(blockText || "");
876
+ li.appendChild(tn);
877
+ newUl.appendChild(li);
878
+ lastTextNode = tn;
879
+ });
880
+ const firstBlock = blocks[0];
881
+ firstBlock.parentNode.insertBefore(newUl, firstBlock);
882
+ blocks.forEach((block) => block.remove());
883
+ if (lastTextNode) {
884
+ const nr = document.createRange();
885
+ nr.setStart(lastTextNode, lastTextNode.textContent.length);
886
+ nr.collapse(true);
887
+ sel.removeAllRanges();
888
+ sel.addRange(nr);
771
889
  }
772
890
  }
773
- if (blocks.length === 0) return;
774
- const newUl = document.createElement("ul");
775
- newUl.className = "an-checklist";
776
- /** @type {Text|null} */ let lastTextNode = null;
777
- blocks.forEach((block) => {
778
- const li = document.createElement("li");
779
- const cb = document.createElement("input");
780
- cb.type = "checkbox";
781
- cb.setAttribute("contenteditable", "false");
782
- li.appendChild(cb);
783
- const blockText = Array.from(block.childNodes).map((n) => n.textContent).join("").replace(/[\u00a0\u200B]/g, " ").trim();
784
- const tn = document.createTextNode(blockText || "​");
785
- li.appendChild(tn);
786
- newUl.appendChild(li);
787
- lastTextNode = tn;
788
- });
789
- const firstBlock = blocks[0];
790
- firstBlock.parentNode.insertBefore(newUl, firstBlock);
791
- blocks.forEach((block) => block.remove());
792
- if (lastTextNode) {
793
- const nr = document.createRange();
794
- nr.setStart(lastTextNode, lastTextNode.textContent.length);
795
- nr.collapse(true);
796
- sel.removeAllRanges();
797
- sel.addRange(nr);
798
- }
799
891
  }
800
892
  /**
801
893
  * Returns true when the cursor is inside a checklist item.
@@ -1889,6 +1981,7 @@
1889
1981
  findPlaceholder: "Tìm…",
1890
1982
  searchAriaLabel: "Văn bản tìm kiếm",
1891
1983
  caseSensitive: "\xA0Phân biệt hoa thường",
1984
+ wholeWord: "Toàn bộ từ",
1892
1985
  prevBtn: "← Trước",
1893
1986
  nextBtn: "Tiếp →",
1894
1987
  replacePlaceholder: "Thay thế bằng…",
@@ -2056,9 +2149,16 @@
2056
2149
  columnWidth: "Chiều rộng cột",
2057
2150
  rowHeight: "Chiều cao hàng",
2058
2151
  tableBorderWidth: "Độ rộng viền bảng",
2152
+ tableBorderColor: "Màu viền bảng",
2059
2153
  deleteTable: "Xóa bảng",
2154
+ cellAlignLeft: "Căn trái",
2155
+ cellAlignCenter: "Căn giữa",
2156
+ cellAlignRight: "Căn phải",
2157
+ cellAlignJustify: "Căn đều",
2158
+ toggleHeaderRow: "Bật/tắt hàng tiêu đề",
2060
2159
  cellBackground: "Màu Nền Ô",
2061
2160
  noShading: "Xóa Màu Nền",
2161
+ noBorderColor: "Xóa màu viền",
2062
2162
  columnWidthPx: "Chiều rộng cột (px)",
2063
2163
  rowHeightPx: "Chiều cao hàng (px)",
2064
2164
  tableBorderWidthPx: "Độ rộng viền bảng (px)",
@@ -2235,6 +2335,7 @@
2235
2335
  findPlaceholder: "検索…",
2236
2336
  searchAriaLabel: "検索テキスト",
2237
2337
  caseSensitive: "\xA0大文字/小文字を区別",
2338
+ wholeWord: "単語単位",
2238
2339
  prevBtn: "← 前へ",
2239
2340
  nextBtn: "次へ →",
2240
2341
  replacePlaceholder: "置換後…",
@@ -2402,7 +2503,16 @@
2402
2503
  columnWidth: "列幅",
2403
2504
  rowHeight: "行の高さ",
2404
2505
  tableBorderWidth: "テーブルの枠幅",
2506
+ tableBorderColor: "テーブルの枠線色",
2405
2507
  deleteTable: "テーブルを削除",
2508
+ cellAlignLeft: "左揃え",
2509
+ cellAlignCenter: "中央揃え",
2510
+ cellAlignRight: "右揃え",
2511
+ cellAlignJustify: "両端揃え",
2512
+ toggleHeaderRow: "ヘッダー行の切り替え",
2513
+ cellBackground: "セル背景色",
2514
+ noShading: "背景色なし",
2515
+ noBorderColor: "枠線色なし",
2406
2516
  columnWidthPx: "列幅 (px)",
2407
2517
  rowHeightPx: "行の高さ (px)",
2408
2518
  tableBorderWidthPx: "テーブルの枠幅 (px)",
@@ -2579,6 +2689,7 @@
2579
2689
  findPlaceholder: "查找…",
2580
2690
  searchAriaLabel: "搜索文字",
2581
2691
  caseSensitive: "\xA0区分大小写",
2692
+ wholeWord: "全字匹配",
2582
2693
  prevBtn: "← 上一个",
2583
2694
  nextBtn: "下一个 →",
2584
2695
  replacePlaceholder: "替换为…",
@@ -2746,7 +2857,16 @@
2746
2857
  columnWidth: "列宽",
2747
2858
  rowHeight: "行高",
2748
2859
  tableBorderWidth: "表格边框宽度",
2860
+ tableBorderColor: "表格边框颜色",
2749
2861
  deleteTable: "删除表格",
2862
+ cellAlignLeft: "左对齐",
2863
+ cellAlignCenter: "居中对齐",
2864
+ cellAlignRight: "右对齐",
2865
+ cellAlignJustify: "两端对齐",
2866
+ toggleHeaderRow: "切换标题行",
2867
+ cellBackground: "单元格背景",
2868
+ noShading: "无底纹",
2869
+ noBorderColor: "无边框颜色",
2750
2870
  columnWidthPx: "列宽 (px)",
2751
2871
  rowHeightPx: "行高 (px)",
2752
2872
  tableBorderWidthPx: "表格边框宽度 (px)",
@@ -2923,6 +3043,7 @@
2923
3043
  findPlaceholder: "Rechercher…",
2924
3044
  searchAriaLabel: "Texte à rechercher",
2925
3045
  caseSensitive: "\xA0Respecter la casse",
3046
+ wholeWord: "Mot entier",
2926
3047
  prevBtn: "← Préc.",
2927
3048
  nextBtn: "Suiv. →",
2928
3049
  replacePlaceholder: "Remplacer par…",
@@ -3090,7 +3211,16 @@
3090
3211
  columnWidth: "Largeur de colonne",
3091
3212
  rowHeight: "Hauteur de ligne",
3092
3213
  tableBorderWidth: "Épaisseur des bordures",
3214
+ tableBorderColor: "Couleur de bordure",
3093
3215
  deleteTable: "Supprimer le tableau",
3216
+ cellAlignLeft: "Aligner à gauche",
3217
+ cellAlignCenter: "Centrer",
3218
+ cellAlignRight: "Aligner à droite",
3219
+ cellAlignJustify: "Justifier",
3220
+ toggleHeaderRow: "Ligne d'en-tête",
3221
+ cellBackground: "Couleur de fond",
3222
+ noShading: "Aucun fond",
3223
+ noBorderColor: "Aucune bordure",
3094
3224
  columnWidthPx: "Largeur de colonne (px)",
3095
3225
  rowHeightPx: "Hauteur de ligne (px)",
3096
3226
  tableBorderWidthPx: "Épaisseur des bordures (px)",
@@ -3267,6 +3397,7 @@
3267
3397
  findPlaceholder: "Suchen…",
3268
3398
  searchAriaLabel: "Suchtext",
3269
3399
  caseSensitive: "\xA0Groß-/Kleinschreibung",
3400
+ wholeWord: "Ganzes Wort",
3270
3401
  prevBtn: "← Zurück",
3271
3402
  nextBtn: "Weiter →",
3272
3403
  replacePlaceholder: "Ersetzen durch…",
@@ -3434,7 +3565,16 @@
3434
3565
  columnWidth: "Spaltenbreite",
3435
3566
  rowHeight: "Zeilenhöhe",
3436
3567
  tableBorderWidth: "Tabellenrahmenbreite",
3568
+ tableBorderColor: "Tabellenrahmenfarbe",
3437
3569
  deleteTable: "Tabelle löschen",
3570
+ cellAlignLeft: "Linksbündig",
3571
+ cellAlignCenter: "Zentrieren",
3572
+ cellAlignRight: "Rechtsbündig",
3573
+ cellAlignJustify: "Blocksatz",
3574
+ toggleHeaderRow: "Kopfzeile umschalten",
3575
+ cellBackground: "Zellhintergrund",
3576
+ noShading: "Kein Hintergrund",
3577
+ noBorderColor: "Keine Rahmenfarbe",
3438
3578
  columnWidthPx: "Spaltenbreite (px)",
3439
3579
  rowHeightPx: "Zeilenhöhe (px)",
3440
3580
  tableBorderWidthPx: "Tabellenrahmenbreite (px)",
@@ -3611,6 +3751,7 @@
3611
3751
  findPlaceholder: "Buscar…",
3612
3752
  searchAriaLabel: "Texto de búsqueda",
3613
3753
  caseSensitive: "\xA0Distinguir mayúsculas",
3754
+ wholeWord: "Palabra completa",
3614
3755
  prevBtn: "← Anterior",
3615
3756
  nextBtn: "Siguiente →",
3616
3757
  replacePlaceholder: "Reemplazar con…",
@@ -3778,7 +3919,16 @@
3778
3919
  columnWidth: "Ancho de columna",
3779
3920
  rowHeight: "Alto de fila",
3780
3921
  tableBorderWidth: "Grosor del borde de la tabla",
3922
+ tableBorderColor: "Color del borde de la tabla",
3781
3923
  deleteTable: "Eliminar tabla",
3924
+ cellAlignLeft: "Alinear a la izquierda",
3925
+ cellAlignCenter: "Centrar",
3926
+ cellAlignRight: "Alinear a la derecha",
3927
+ cellAlignJustify: "Justificar",
3928
+ toggleHeaderRow: "Activar fila de encabezado",
3929
+ cellBackground: "Fondo de celda",
3930
+ noShading: "Sin fondo",
3931
+ noBorderColor: "Sin color de borde",
3782
3932
  columnWidthPx: "Ancho de columna (px)",
3783
3933
  rowHeightPx: "Alto de fila (px)",
3784
3934
  tableBorderWidthPx: "Grosor del borde (px)",
@@ -3955,6 +4105,7 @@
3955
4105
  findPlaceholder: "찾기…",
3956
4106
  searchAriaLabel: "검색 텍스트",
3957
4107
  caseSensitive: "\xA0대소문자 구분",
4108
+ wholeWord: "전체 단어",
3958
4109
  prevBtn: "← 이전",
3959
4110
  nextBtn: "다음 →",
3960
4111
  replacePlaceholder: "바꿀 내용…",
@@ -4122,7 +4273,16 @@
4122
4273
  columnWidth: "열 너비",
4123
4274
  rowHeight: "행 높이",
4124
4275
  tableBorderWidth: "표 테두리 너비",
4276
+ tableBorderColor: "표 테두리 색상",
4125
4277
  deleteTable: "표 삭제",
4278
+ cellAlignLeft: "왼쪽 정렬",
4279
+ cellAlignCenter: "가운데 정렬",
4280
+ cellAlignRight: "오른쪽 정렬",
4281
+ cellAlignJustify: "양쪽 정렬",
4282
+ toggleHeaderRow: "머리글 행 전환",
4283
+ cellBackground: "셀 배경색",
4284
+ noShading: "배경 없음",
4285
+ noBorderColor: "테두리 색 없음",
4126
4286
  columnWidthPx: "열 너비 (px)",
4127
4287
  rowHeightPx: "행 높이 (px)",
4128
4288
  tableBorderWidthPx: "표 테두리 너비 (px)",
@@ -4640,7 +4800,11 @@
4640
4800
  const sel = globalThis.getSelection();
4641
4801
  if (!sel || sel.rangeCount === 0) return;
4642
4802
  const range = sel.getRangeAt(0);
4643
- range.deleteContents();
4803
+ try {
4804
+ range.deleteContents();
4805
+ } catch (_) {
4806
+ return;
4807
+ }
4644
4808
  const BLOCK = new Set([
4645
4809
  "P",
4646
4810
  "DIV",
@@ -4665,7 +4829,11 @@
4665
4829
  table.after(p);
4666
4830
  }
4667
4831
  if (!anchor.textContent.trim() && !anchor.querySelector("img, video, table")) anchor.remove();
4668
- } else range.insertNode(table);
4832
+ } else try {
4833
+ range.insertNode(table);
4834
+ } catch (_) {
4835
+ return;
4836
+ }
4669
4837
  const firstCell = table.querySelector("td, th");
4670
4838
  if (firstCell) {
4671
4839
  const nr = document.createRange();
@@ -4748,6 +4916,23 @@
4748
4916
  var isFAIcon = (n) => !!(n?.nodeName === "I" && _FA_PATTERN.test(n.className || ""));
4749
4917
  var isZwsAnchor = (n) => !!(n?.nodeType === Node.TEXT_NODE && (n.textContent === "​" || n.textContent === ""));
4750
4918
  /**
4919
+ * Extracts the content from `startContainer:startOffset` to the end of `li`.
4920
+ * Returns an empty fragment if the range is invalid (e.g. detached node).
4921
+ * @param {Range} nativeRange
4922
+ * @param {Element} li
4923
+ * @returns {DocumentFragment}
4924
+ */
4925
+ function extractAfterContent(nativeRange, li) {
4926
+ try {
4927
+ const r = document.createRange();
4928
+ r.setStart(nativeRange.startContainer, nativeRange.startOffset);
4929
+ r.setEnd(li, li.childNodes.length);
4930
+ return r.extractContents();
4931
+ } catch (_) {
4932
+ return document.createDocumentFragment();
4933
+ }
4934
+ }
4935
+ /**
4751
4936
  * Handles special keydown behaviour inside the editor.
4752
4937
  * @param {KeyboardEvent} event
4753
4938
  * @param {HTMLElement} editable
@@ -4955,12 +5140,10 @@
4955
5140
  }
4956
5141
  if (!nativeRange.collapsed) {
4957
5142
  nativeRange.deleteContents();
5143
+ if (sel.rangeCount === 0 || !checkLi.isConnected) return true;
4958
5144
  nativeRange = sel.getRangeAt(0);
4959
5145
  }
4960
- const afterRange = document.createRange();
4961
- afterRange.setStart(nativeRange.startContainer, nativeRange.startOffset);
4962
- afterRange.setEnd(checkLi, checkLi.childNodes.length);
4963
- const afterFrag = afterRange.extractContents();
5146
+ const afterFrag = extractAfterContent(nativeRange, checkLi);
4964
5147
  const newLi = document.createElement("li");
4965
5148
  const cb = document.createElement("input");
4966
5149
  cb.type = "checkbox";
@@ -5076,7 +5259,15 @@
5076
5259
  const items = Array.from(el.querySelectorAll(":scope > li"));
5077
5260
  if (!items.length) return inner();
5078
5261
  const indent = " ".repeat(depth);
5079
- const lines = items.map((li) => `${indent}- ${_domToMd(li, depth + 1).trim()}`).join("\n");
5262
+ const isChecklist = el.classList.contains("an-checklist");
5263
+ const lines = items.map((li) => {
5264
+ let prefix = "- ";
5265
+ if (isChecklist) {
5266
+ const cb = li.querySelector("input[type=\"checkbox\"]");
5267
+ prefix = (cb ? cb.checked : false) ? "- [x] " : "- [ ] ";
5268
+ }
5269
+ return `${indent}${prefix}${_domToMd(li, depth + 1).trim()}`;
5270
+ }).join("\n");
5080
5271
  return depth === 0 ? `\n\n${lines}\n\n` : `\n${lines}`;
5081
5272
  }
5082
5273
  case "ol": {
@@ -5116,7 +5307,7 @@
5116
5307
  * @returns {boolean} `true` if any Markdown-like pattern is present, `false` otherwise.
5117
5308
  */
5118
5309
  function isMarkdown(text) {
5119
- return /^#{1,6} \S|^\s*[-*+] \S|^\s*\d+\. \S|^> \S|^```|^\*{2}.+?\*{2}/m.test(text);
5310
+ return /^#{1,6} [^\s]|^[ \t]*[-*+] [^\s]|^[ \t]*\d+\. [^\s]|^> [^\s]|^```|^\*{2}[^*\n]+\*{2}/m.test(text);
5120
5311
  }
5121
5312
  /**
5122
5313
  * Converts a Markdown string to an HTML string.
@@ -5166,11 +5357,20 @@
5166
5357
  }
5167
5358
  if (/^[-*+] /.test(line)) {
5168
5359
  const items = [];
5360
+ const isChecklist = /^[-*+]\s+\[[ xX]\]\s+/.test(line);
5361
+ const listTag = isChecklist ? "ul class=\"an-checklist\"" : "ul";
5169
5362
  while (i < lines.length && /^[-*+] /.test(lines[i])) {
5170
- items.push(`<li>${_inline(lines[i].slice(2))}</li>`);
5363
+ if (/^[-*+]\s+\[[ xX]\]\s+/.test(lines[i]) !== isChecklist) break;
5364
+ const content = lines[i].slice(2);
5365
+ if (isChecklist) {
5366
+ const cbMatch = /^\[([ xX])\][ \t]+/.exec(content);
5367
+ const cbHtml = `<input type="checkbox" contenteditable="false"${cbMatch?.[1]?.toLowerCase() === "x" ? " checked" : ""}>`;
5368
+ const textContent = cbMatch ? content.slice(cbMatch[0].length) : content;
5369
+ items.push(`<li>${cbHtml}${_inline(textContent)}</li>`);
5370
+ } else items.push(`<li>${_inline(content)}</li>`);
5171
5371
  i++;
5172
5372
  }
5173
- out.push(`<ul>${items.join("")}</ul>`);
5373
+ out.push(`<${listTag}>${items.join("")}</${listTag.split(" ")[0]}>`);
5174
5374
  continue;
5175
5375
  }
5176
5376
  if (/^\d+\. /.test(line)) {
@@ -5227,7 +5427,7 @@
5227
5427
  text = text.replace(/_{2}([^_\n]+?)_{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
5228
5428
  text = text.replace(/\*([^*\n]+?)\*/g, (_, c) => `<em>${_esc(c)}</em>`);
5229
5429
  text = text.replace(/_([^_\n]+?)_/g, (_, c) => `<em>${_esc(c)}</em>`);
5230
- text = text.replace(/~~([^\n]+?)~~/g, (_, c) => `<del>${_esc(c)}</del>`);
5430
+ text = text.replace(/~~([^~\n]+?)~~/g, (_, c) => `<del>${_esc(c)}</del>`);
5231
5431
  text = text.replace(/`([^`]+)`/g, (_, c) => `<code>${_esc(c)}</code>`);
5232
5432
  return text;
5233
5433
  }
@@ -6926,7 +7126,7 @@
6926
7126
  const mime = /:(.*?);/.exec(header)?.[1] ?? "image/png";
6927
7127
  const binary = atob(b64);
6928
7128
  const arr = new Uint8Array(binary.length);
6929
- for (let i = 0; i < binary.length; i++) arr[i] = binary.codePointAt(i);
7129
+ for (let i = 0; i < binary.length; i++) arr[i] = binary.charCodeAt(i);
6930
7130
  return new Blob([arr], { type: mime });
6931
7131
  }
6932
7132
  /**
@@ -7532,6 +7732,7 @@
7532
7732
  return overlay;
7533
7733
  }
7534
7734
  _onFileChange() {
7735
+ if (this.context._alive === false) return;
7535
7736
  const file = this._fileInput?.files?.[0];
7536
7737
  if (!file?.type?.startsWith("image/")) return;
7537
7738
  if (!new Set([
@@ -10097,8 +10298,28 @@
10097
10298
  return direction === "asc" ? aText.localeCompare(bText) : bText.localeCompare(aText);
10098
10299
  });
10099
10300
  rows.forEach((row) => tbody.appendChild(row));
10301
+ this._markSortIndicator(table, colIdx, direction);
10100
10302
  this.context.invoke("editor.afterCommand");
10101
10303
  }
10304
+ /**
10305
+ * Marks the header cell of the sorted column with `an-sort-asc`/`an-sort-desc`
10306
+ * so the active sort column and direction are visible, and clears any previous
10307
+ * indicator. No-op for tables without a `<thead>` (no header row to mark).
10308
+ * @param {HTMLTableElement} table
10309
+ * @param {number} colIdx
10310
+ * @param {'asc'|'desc'} direction
10311
+ */
10312
+ _markSortIndicator(table, colIdx, direction) {
10313
+ const thead = table.querySelector("thead");
10314
+ if (!thead) return;
10315
+ thead.querySelectorAll(".an-sort-asc, .an-sort-desc").forEach((el) => {
10316
+ el.classList.remove("an-sort-asc", "an-sort-desc");
10317
+ });
10318
+ const headerRow = thead.querySelector("tr");
10319
+ if (!headerRow) return;
10320
+ const headerCell = getCellAtVisualCol(headerRow, colIdx);
10321
+ if (headerCell) headerCell.classList.add(direction === "asc" ? "an-sort-asc" : "an-sort-desc");
10322
+ }
10102
10323
  _exportTableCSV() {
10103
10324
  const table = this._activeTable;
10104
10325
  if (!table) return;
@@ -16017,6 +16238,7 @@
16017
16238
  * debounce: 200,
16018
16239
  * onSearch: (query, callback) => void,
16019
16240
  * onInsert: (item) => string | null,
16241
+ * onError: (err: Error) => void,
16020
16242
  * mentionClass: 'an-mention',
16021
16243
  * allowSpaces: false,
16022
16244
  * }
@@ -16054,6 +16276,7 @@
16054
16276
  debounce: cfg.debounce ?? 200,
16055
16277
  onSearch: cfg.onSearch,
16056
16278
  onInsert: cfg.onInsert || null,
16279
+ onError: cfg.onError || null,
16057
16280
  mentionClass: cfg.mentionClass || "an-mention",
16058
16281
  allowSpaces: cfg.allowSpaces || false
16059
16282
  };
@@ -16209,7 +16432,14 @@
16209
16432
  this._renderItems(items);
16210
16433
  this._showDropdown();
16211
16434
  };
16212
- const result = this._cfg.onSearch(this._query, cb);
16435
+ let result;
16436
+ try {
16437
+ result = this._cfg.onSearch(this._query, cb);
16438
+ } catch (err) {
16439
+ this._hideDropdown();
16440
+ if (typeof this._cfg.onError === "function") this._cfg.onError(err);
16441
+ return;
16442
+ }
16213
16443
  if (result && typeof result.then === "function") result.then(cb).catch((err) => {
16214
16444
  this._hideDropdown();
16215
16445
  if (typeof this._cfg.onError === "function") this._cfg.onError(err);
@@ -16849,7 +17079,7 @@
16849
17079
  /** All pre-built button definitions — accessible in every module format including UMD/CJS. */
16850
17080
  buttons,
16851
17081
  /** Library version */
16852
- version: "1.8.0"
17082
+ version: "1.8.2"
16853
17083
  };
16854
17084
  /**
16855
17085
  * @param {string|Element|NodeList|Element[]} selector