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.
- package/README.md +17 -5
- package/dist/autumnnote.css +18 -1
- package/dist/autumnnote.es.js +350 -120
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +350 -120
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/js/core/markdown.js +32 -6
- package/src/js/editing/Style.js +288 -159
- package/src/js/editing/Table.js +10 -2
- package/src/js/editing/Typing.js +21 -4
- package/src/js/i18n/de.js +10 -0
- package/src/js/i18n/es.js +10 -0
- package/src/js/i18n/fr.js +10 -0
- package/src/js/i18n/ja.js +10 -0
- package/src/js/i18n/ko.js +10 -0
- package/src/js/i18n/vi.js +8 -0
- package/src/js/i18n/zh.js +10 -0
- package/src/js/index.js +1 -1
- package/src/js/module/Clipboard.js +1 -1
- package/src/js/module/ImageDialog.js +1 -0
- package/src/js/module/Mention.js +10 -1
- package/src/js/module/TableTooltip.js +21 -0
- package/src/styles/autumnnote.scss +20 -3
- package/types/index.d.ts +3 -3
package/dist/autumnnote.umd.js
CHANGED
|
@@ -520,13 +520,74 @@
|
|
|
520
520
|
} catch {}
|
|
521
521
|
}
|
|
522
522
|
/**
|
|
523
|
-
* Inserts an unordered list or converts
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
675
|
-
if (
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
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
|
-
|
|
767
|
+
listEl.before(p);
|
|
691
768
|
if (!firstP) firstP = p;
|
|
692
|
-
li.remove();
|
|
693
769
|
});
|
|
694
|
-
|
|
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
|
-
|
|
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
|
-
|
|
706
|
-
const
|
|
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
|
-
|
|
719
|
-
|
|
720
|
-
const
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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
|
-
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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} \
|
|
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
|
-
|
|
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(
|
|
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(/~~([
|
|
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.
|
|
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
|
-
|
|
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.
|
|
17082
|
+
version: "1.8.2"
|
|
16853
17083
|
};
|
|
16854
17084
|
/**
|
|
16855
17085
|
* @param {string|Element|NodeList|Element[]} selector
|