autumnnote 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/autumnnote.es.js +337 -104
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +308 -88
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/core/dom.js +6 -3
- package/src/js/core/func.js +24 -13
- package/src/js/core/markdown.js +26 -8
- package/src/js/core/sanitise.js +57 -46
- package/src/js/editing/History.js +12 -1
- package/src/js/editing/Style.js +49 -21
- package/src/js/editing/Table.js +53 -13
- package/src/js/editing/Typing.js +9 -2
- package/src/js/i18n/de.js +1 -0
- package/src/js/i18n/en.js +1 -0
- package/src/js/i18n/es.js +1 -0
- package/src/js/i18n/fr.js +1 -0
- package/src/js/i18n/ja.js +1 -0
- package/src/js/i18n/ko.js +1 -0
- package/src/js/i18n/vi.js +1 -0
- package/src/js/i18n/zh.js +1 -0
- package/src/js/module/BubbleToolbar.js +26 -7
- package/src/js/module/Clipboard.js +2 -2
- package/src/js/module/ContextMenu.js +1 -1
- package/src/js/module/FindReplace.js +6 -5
- package/src/js/module/ImageCropOverlay.js +46 -8
- package/src/js/module/ImageResizer.js +7 -0
- package/src/js/module/Mention.js +18 -4
- package/src/js/module/Statusbar.js +2 -1
- package/src/js/module/Toolbar.js +14 -0
package/dist/autumnnote.umd.js
CHANGED
|
@@ -428,9 +428,15 @@
|
|
|
428
428
|
execCommand("outdent");
|
|
429
429
|
}
|
|
430
430
|
/**
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
431
|
+
* Convert a checklist <li> into a paragraph and move any following items into a new checklist.
|
|
432
|
+
*
|
|
433
|
+
* Preserves inline markup from the converted item, strips zero-width space anchors,
|
|
434
|
+
* and replaces empty content with a non‑breaking space. If there are list items
|
|
435
|
+
* after the converted item they are moved into a new <ul class="an-checklist">
|
|
436
|
+
* inserted immediately after the original list. The original <li> is removed and
|
|
437
|
+
* the original list is removed if it becomes empty. Attempts to place the caret
|
|
438
|
+
* at the start of the newly created <p>.
|
|
439
|
+
* @param {HTMLElement} checkLi - The checklist `<li>` element to convert to a `<p>`.
|
|
434
440
|
*/
|
|
435
441
|
function _checklistItemToP(checkLi) {
|
|
436
442
|
const checkUl = checkLi.closest(".an-checklist");
|
|
@@ -439,7 +445,15 @@
|
|
|
439
445
|
const liIndex = allLis.indexOf(checkLi);
|
|
440
446
|
const afterLis = allLis.slice(liIndex + 1);
|
|
441
447
|
const p = document.createElement("p");
|
|
442
|
-
|
|
448
|
+
for (const child of checkLi.childNodes) {
|
|
449
|
+
if (child.nodeType === 1 && child.tagName === "INPUT") continue;
|
|
450
|
+
p.appendChild(child.cloneNode(true));
|
|
451
|
+
}
|
|
452
|
+
p.innerHTML = p.innerHTML.replace(/\u200B/g, "");
|
|
453
|
+
if (!p.hasChildNodes() || !p.textContent.trim()) {
|
|
454
|
+
p.innerHTML = "";
|
|
455
|
+
p.appendChild(document.createTextNode("\xA0"));
|
|
456
|
+
}
|
|
443
457
|
if (afterLis.length > 0) {
|
|
444
458
|
const newUl = document.createElement("ul");
|
|
445
459
|
newUl.className = "an-checklist";
|
|
@@ -470,9 +484,12 @@
|
|
|
470
484
|
*/
|
|
471
485
|
var insertOrderedList = () => execCommand("insertOrderedList");
|
|
472
486
|
/**
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
487
|
+
* Set the line-height on every block-level element that intersects the current selection.
|
|
488
|
+
*
|
|
489
|
+
* If the selection is collapsed, the nearest enclosing block element receives the style.
|
|
490
|
+
* For a non-collapsed selection, all unique block ancestors of text nodes that intersect the range are updated;
|
|
491
|
+
* if none are found, the nearest block ancestor of the range's common ancestor is updated.
|
|
492
|
+
* @param {string} value - Line-height value to apply; typically a unitless multiplier (for example, "1.5").
|
|
476
493
|
*/
|
|
477
494
|
function lineHeight(value) {
|
|
478
495
|
const sel = window.getSelection();
|
|
@@ -507,9 +524,9 @@
|
|
|
507
524
|
return;
|
|
508
525
|
}
|
|
509
526
|
const blocks = /* @__PURE__ */ new Set();
|
|
510
|
-
const iter = document.
|
|
527
|
+
const iter = document.createTreeWalker(range.commonAncestorContainer, NodeFilter.SHOW_TEXT, { acceptNode: (node) => range.intersectsNode(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP });
|
|
511
528
|
let textNode;
|
|
512
|
-
while (textNode = iter.nextNode())
|
|
529
|
+
while (textNode = iter.nextNode()) {
|
|
513
530
|
const block = nearestBlock(textNode);
|
|
514
531
|
if (block) blocks.add(block);
|
|
515
532
|
}
|
|
@@ -591,9 +608,18 @@
|
|
|
591
608
|
return !!(code && !code.closest("pre"));
|
|
592
609
|
}
|
|
593
610
|
/**
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
611
|
+
* Toggle a checklist at the current selection or caret.
|
|
612
|
+
*
|
|
613
|
+
* When the selection is inside an existing checklist `<ul class="an-checklist">`,
|
|
614
|
+
* converts the selected `<li>` items back into `<p>` paragraphs and places the caret
|
|
615
|
+
* at the start of the first converted paragraph. Otherwise creates a checklist:
|
|
616
|
+
* - If the selection is collapsed, converts the nearest block-level ancestor (or inserts
|
|
617
|
+
* a single checklist item at the editable root) into a checklist with one item containing
|
|
618
|
+
* that block's text and places the caret inside the new item.
|
|
619
|
+
* - If the selection is a range, converts each intersecting block element into one checklist
|
|
620
|
+
* item (preserving textual content) and places the caret at the end of the last item.
|
|
621
|
+
*
|
|
622
|
+
* Empty or whitespace-only selections do not create a checklist.
|
|
597
623
|
*/
|
|
598
624
|
function toggleChecklist() {
|
|
599
625
|
const sel = window.getSelection();
|
|
@@ -652,8 +678,9 @@
|
|
|
652
678
|
ul.appendChild(li);
|
|
653
679
|
if (block && BLOCK_TAGS.has(block.tagName)) block.parentNode.replaceChild(ul, block);
|
|
654
680
|
else {
|
|
655
|
-
|
|
656
|
-
|
|
681
|
+
const nativeRange = sel.getRangeAt(0);
|
|
682
|
+
nativeRange.deleteContents();
|
|
683
|
+
nativeRange.insertNode(ul);
|
|
657
684
|
}
|
|
658
685
|
const textNode = li.lastChild;
|
|
659
686
|
const nr = document.createRange();
|
|
@@ -1332,6 +1359,7 @@
|
|
|
1332
1359
|
replaceAriaLabel: "Replace with",
|
|
1333
1360
|
replaceBtn: "Replace",
|
|
1334
1361
|
replaceAllBtn: "Replace All",
|
|
1362
|
+
noResults: "No results",
|
|
1335
1363
|
close: "×"
|
|
1336
1364
|
},
|
|
1337
1365
|
shortcutsDialog: {
|
|
@@ -1684,6 +1712,7 @@
|
|
|
1684
1712
|
replaceAriaLabel: "Thay thế bằng",
|
|
1685
1713
|
replaceBtn: "Thay thế",
|
|
1686
1714
|
replaceAllBtn: "Thay thế tất cả",
|
|
1715
|
+
noResults: "Không có kết quả",
|
|
1687
1716
|
close: "×"
|
|
1688
1717
|
},
|
|
1689
1718
|
shortcutsDialog: {
|
|
@@ -2013,6 +2042,7 @@
|
|
|
2013
2042
|
replacePlaceholder: "置換後…",
|
|
2014
2043
|
replaceAriaLabel: "置換後のテキスト",
|
|
2015
2044
|
replaceBtn: "置換",
|
|
2045
|
+
noResults: "結果なし",
|
|
2016
2046
|
replaceAllBtn: "すべて置換",
|
|
2017
2047
|
close: "×"
|
|
2018
2048
|
},
|
|
@@ -2343,6 +2373,7 @@
|
|
|
2343
2373
|
replacePlaceholder: "替换为…",
|
|
2344
2374
|
replaceAriaLabel: "替换为",
|
|
2345
2375
|
replaceBtn: "替换",
|
|
2376
|
+
noResults: "没有结果",
|
|
2346
2377
|
replaceAllBtn: "全部替换",
|
|
2347
2378
|
close: "×"
|
|
2348
2379
|
},
|
|
@@ -2673,6 +2704,7 @@
|
|
|
2673
2704
|
replacePlaceholder: "Remplacer par…",
|
|
2674
2705
|
replaceAriaLabel: "Remplacer par",
|
|
2675
2706
|
replaceBtn: "Remplacer",
|
|
2707
|
+
noResults: "Aucun résultat",
|
|
2676
2708
|
replaceAllBtn: "Tout remplacer",
|
|
2677
2709
|
close: "×"
|
|
2678
2710
|
},
|
|
@@ -3003,6 +3035,7 @@
|
|
|
3003
3035
|
replacePlaceholder: "Ersetzen durch…",
|
|
3004
3036
|
replaceAriaLabel: "Ersetzen durch",
|
|
3005
3037
|
replaceBtn: "Ersetzen",
|
|
3038
|
+
noResults: "Keine Ergebnisse",
|
|
3006
3039
|
replaceAllBtn: "Alle ersetzen",
|
|
3007
3040
|
close: "×"
|
|
3008
3041
|
},
|
|
@@ -3333,6 +3366,7 @@
|
|
|
3333
3366
|
replacePlaceholder: "Reemplazar con…",
|
|
3334
3367
|
replaceAriaLabel: "Reemplazar con",
|
|
3335
3368
|
replaceBtn: "Reemplazar",
|
|
3369
|
+
noResults: "Sin resultados",
|
|
3336
3370
|
replaceAllBtn: "Reemplazar todo",
|
|
3337
3371
|
close: "×"
|
|
3338
3372
|
},
|
|
@@ -3663,6 +3697,7 @@
|
|
|
3663
3697
|
replacePlaceholder: "바꿀 내용…",
|
|
3664
3698
|
replaceAriaLabel: "바꿀 내용",
|
|
3665
3699
|
replaceBtn: "바꾸기",
|
|
3700
|
+
noResults: "결과 없음",
|
|
3666
3701
|
replaceAllBtn: "모두 바꾸기",
|
|
3667
3702
|
close: "×"
|
|
3668
3703
|
},
|
|
@@ -3883,8 +3918,10 @@
|
|
|
3883
3918
|
"object",
|
|
3884
3919
|
"embed",
|
|
3885
3920
|
"form",
|
|
3886
|
-
"
|
|
3921
|
+
"base"
|
|
3887
3922
|
];
|
|
3923
|
+
/** Tags whose element wrapper is stripped but content (child nodes) is preserved. */
|
|
3924
|
+
var UNWRAP_TAGS = new Set(["button"]);
|
|
3888
3925
|
/** Attributes whose values must be sanitised as URLs. */
|
|
3889
3926
|
var URL_ATTRS = [
|
|
3890
3927
|
"href",
|
|
@@ -3902,60 +3939,61 @@
|
|
|
3902
3939
|
"player.vimeo.com"
|
|
3903
3940
|
]);
|
|
3904
3941
|
/**
|
|
3905
|
-
*
|
|
3906
|
-
* Uses DOMParser so the sanitisation follows normal browser parsing rules —
|
|
3907
|
-
* no regex shortcuts that can be bypassed by encoding tricks.
|
|
3942
|
+
* Produce a sanitized HTML string with dangerous elements and attributes removed.
|
|
3908
3943
|
*
|
|
3909
|
-
*
|
|
3910
|
-
*
|
|
3911
|
-
*
|
|
3912
|
-
* - Rejects javascript: and vbscript: URLs in URL attributes
|
|
3913
|
-
* - Rejects data: URIs everywhere except img[src] (base64 uploads)
|
|
3944
|
+
* Removes disallowed tags and wrappers, strips event-handler attributes, rejects
|
|
3945
|
+
* `javascript:`/`vbscript:` URLs and most `data:` URIs, restricts iframe `src`
|
|
3946
|
+
* to trusted hosts when enabled, and permits only checklist checkboxes as inputs.
|
|
3914
3947
|
*
|
|
3915
|
-
* @param {string} html
|
|
3916
|
-
* @
|
|
3948
|
+
* @param {string} html - HTML fragment to sanitize.
|
|
3949
|
+
* @param {Object} [options]
|
|
3950
|
+
* @param {boolean} [options.allowIframes=false] - If true, `iframe` elements are not removed but their `src` is restricted to trusted hosts and `srcdoc` is removed.
|
|
3951
|
+
* @returns {string} The sanitized HTML fragment.
|
|
3917
3952
|
*/
|
|
3918
3953
|
function sanitiseHTML(html, { allowIframes = false } = {}) {
|
|
3919
3954
|
const doc = new DOMParser().parseFromString(`<body>${html || ""}</body>`, "text/html");
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3955
|
+
const allElements = Array.from(doc.querySelectorAll("*"));
|
|
3956
|
+
const prohibited = new Set(allowIframes ? PROHIBITED_TAGS.filter((t) => t !== "iframe") : PROHIBITED_TAGS);
|
|
3957
|
+
for (const el of allElements) {
|
|
3958
|
+
const tag = el.tagName.toLowerCase();
|
|
3959
|
+
if (UNWRAP_TAGS.has(tag)) {
|
|
3960
|
+
el.replaceWith(...el.childNodes);
|
|
3961
|
+
continue;
|
|
3962
|
+
}
|
|
3963
|
+
if (prohibited.has(tag)) {
|
|
3964
|
+
el.remove();
|
|
3965
|
+
continue;
|
|
3966
|
+
}
|
|
3967
|
+
for (const attr of Array.from(el.attributes)) {
|
|
3925
3968
|
if (attr.name.startsWith("on")) {
|
|
3926
3969
|
el.removeAttribute(attr.name);
|
|
3927
|
-
|
|
3970
|
+
continue;
|
|
3928
3971
|
}
|
|
3929
3972
|
if (URL_ATTRS.includes(attr.name)) {
|
|
3930
3973
|
const val = attr.value.trim();
|
|
3931
3974
|
if (/^(javascript|vbscript):/i.test(val)) {
|
|
3932
3975
|
el.removeAttribute(attr.name);
|
|
3933
|
-
|
|
3976
|
+
continue;
|
|
3934
3977
|
}
|
|
3935
3978
|
if (/^data:/i.test(val) && !(attr.name === "src" && el.tagName === "IMG")) el.removeAttribute(attr.name);
|
|
3936
3979
|
}
|
|
3937
3980
|
if (el.tagName === "IFRAME") {
|
|
3938
3981
|
if (attr.name === "srcdoc") {
|
|
3939
3982
|
el.removeAttribute(attr.name);
|
|
3940
|
-
|
|
3941
|
-
}
|
|
3942
|
-
if (attr.name === "src") {
|
|
3943
|
-
if (!isTrustedIframeSrc(attr.value)) el.removeAttribute(attr.name);
|
|
3944
|
-
return;
|
|
3983
|
+
continue;
|
|
3945
3984
|
}
|
|
3985
|
+
if (attr.name === "src" && !isTrustedIframeSrc(attr.value)) el.removeAttribute(attr.name);
|
|
3946
3986
|
}
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
else Array.from(el.attributes).forEach((attr) => {
|
|
3952
|
-
if (![
|
|
3987
|
+
}
|
|
3988
|
+
if (tag === "input") {
|
|
3989
|
+
if (!(el.closest("ul.an-checklist") !== null && el.closest("li") !== null) || el.getAttribute("type") !== "checkbox") el.remove();
|
|
3990
|
+
else for (const attr of Array.from(el.attributes)) if (![
|
|
3953
3991
|
"type",
|
|
3954
3992
|
"checked",
|
|
3955
3993
|
"contenteditable"
|
|
3956
3994
|
].includes(attr.name)) el.removeAttribute(attr.name);
|
|
3957
|
-
}
|
|
3958
|
-
}
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3959
3997
|
return doc.body.innerHTML;
|
|
3960
3998
|
}
|
|
3961
3999
|
/**
|
|
@@ -4156,7 +4194,18 @@
|
|
|
4156
4194
|
const sel = window.getSelection();
|
|
4157
4195
|
sel.removeAllRanges();
|
|
4158
4196
|
sel.addRange(range);
|
|
4159
|
-
} catch (_) {
|
|
4197
|
+
} catch (_) {
|
|
4198
|
+
try {
|
|
4199
|
+
const fb = document.createRange();
|
|
4200
|
+
fb.setStart(this.editable, 0);
|
|
4201
|
+
fb.collapse(true);
|
|
4202
|
+
const s = window.getSelection();
|
|
4203
|
+
if (s) {
|
|
4204
|
+
s.removeAllRanges();
|
|
4205
|
+
s.addRange(fb);
|
|
4206
|
+
}
|
|
4207
|
+
} catch (_2) {}
|
|
4208
|
+
}
|
|
4160
4209
|
}
|
|
4161
4210
|
_savePoint() {
|
|
4162
4211
|
if (this.stackOffset < this.stack.length - 1) this.stack = this.stack.slice(0, this.stackOffset + 1);
|
|
@@ -4183,6 +4232,10 @@
|
|
|
4183
4232
|
* @returns {{ html: string, images: Object<string,string> }}
|
|
4184
4233
|
*/
|
|
4185
4234
|
_tokenizeImages(html) {
|
|
4235
|
+
if (!html.includes("data:")) return {
|
|
4236
|
+
html,
|
|
4237
|
+
images: {}
|
|
4238
|
+
};
|
|
4186
4239
|
const images = {};
|
|
4187
4240
|
let index = 0;
|
|
4188
4241
|
return {
|
|
@@ -4254,11 +4307,12 @@
|
|
|
4254
4307
|
* Inspired by Summernote's table handling
|
|
4255
4308
|
*/
|
|
4256
4309
|
/**
|
|
4257
|
-
*
|
|
4258
|
-
* @param {number} cols
|
|
4259
|
-
* @param {number} rows
|
|
4260
|
-
* @param {{ headerRow?: boolean }} [opts]
|
|
4261
|
-
* @
|
|
4310
|
+
* Build an HTML table with the given number of columns and rows, optionally including a header row.
|
|
4311
|
+
* @param {number} cols - Number of columns in each row.
|
|
4312
|
+
* @param {number} rows - Total number of rows to create (including header when `headerRow` is true).
|
|
4313
|
+
* @param {{ headerRow?: boolean }} [opts] - Options object.
|
|
4314
|
+
* @param {boolean} [opts.headerRow=false] - When true and `rows > 0`, creates a header row (`<thead>`) plus body rows for the remainder.
|
|
4315
|
+
* @returns {HTMLTableElement} The constructed `<table>` element with a `<tbody>` and optional `<thead>`; each cell contains a `<br>` placeholder.
|
|
4262
4316
|
*/
|
|
4263
4317
|
function createTable(cols, rows, opts = {}) {
|
|
4264
4318
|
const { headerRow = false } = opts;
|
|
@@ -4267,7 +4321,7 @@
|
|
|
4267
4321
|
const thead = createElement("thead");
|
|
4268
4322
|
const tr = createElement("tr");
|
|
4269
4323
|
for (let c = 0; c < cols; c++) {
|
|
4270
|
-
const th = createElement("th", {}, ["
|
|
4324
|
+
const th = createElement("th", {}, [document.createElement("br")]);
|
|
4271
4325
|
tr.appendChild(th);
|
|
4272
4326
|
}
|
|
4273
4327
|
thead.appendChild(tr);
|
|
@@ -4279,7 +4333,7 @@
|
|
|
4279
4333
|
for (let r = 0; r < bodyRows; r++) {
|
|
4280
4334
|
const tr = createElement("tr");
|
|
4281
4335
|
for (let c = 0; c < cols; c++) {
|
|
4282
|
-
const td = createElement("td", {}, ["
|
|
4336
|
+
const td = createElement("td", {}, [document.createElement("br")]);
|
|
4283
4337
|
tr.appendChild(td);
|
|
4284
4338
|
}
|
|
4285
4339
|
tbody.appendChild(tr);
|
|
@@ -4287,13 +4341,52 @@
|
|
|
4287
4341
|
return table;
|
|
4288
4342
|
}
|
|
4289
4343
|
/**
|
|
4290
|
-
*
|
|
4291
|
-
* @param {number} cols
|
|
4292
|
-
* @param {number} rows
|
|
4293
|
-
* @param {{ headerRow?: boolean }} [opts]
|
|
4344
|
+
* Insert a table at the current selection and place the caret into its first cell.
|
|
4345
|
+
* @param {number} cols - Number of columns for the new table.
|
|
4346
|
+
* @param {number} rows - Number of rows for the new table.
|
|
4347
|
+
* @param {{ headerRow?: boolean }} [opts] - Options for table creation.
|
|
4348
|
+
* @param {boolean} [opts.headerRow=false] - If true, include a header row as the first row.
|
|
4294
4349
|
*/
|
|
4295
4350
|
function insertTable(cols, rows, opts = {}) {
|
|
4296
|
-
|
|
4351
|
+
if (cols <= 0 || rows <= 0) return;
|
|
4352
|
+
const table = createTable(cols, rows, opts);
|
|
4353
|
+
const sel = window.getSelection();
|
|
4354
|
+
if (!sel || sel.rangeCount === 0) return;
|
|
4355
|
+
const range = sel.getRangeAt(0);
|
|
4356
|
+
range.deleteContents();
|
|
4357
|
+
const BLOCK = new Set([
|
|
4358
|
+
"P",
|
|
4359
|
+
"DIV",
|
|
4360
|
+
"H1",
|
|
4361
|
+
"H2",
|
|
4362
|
+
"H3",
|
|
4363
|
+
"H4",
|
|
4364
|
+
"H5",
|
|
4365
|
+
"H6",
|
|
4366
|
+
"BLOCKQUOTE",
|
|
4367
|
+
"LI",
|
|
4368
|
+
"PRE"
|
|
4369
|
+
]);
|
|
4370
|
+
let anchor = range.startContainer;
|
|
4371
|
+
if (anchor.nodeType === 3) anchor = anchor.parentElement;
|
|
4372
|
+
while (anchor && !BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentElement) anchor = anchor.parentElement;
|
|
4373
|
+
if (anchor && BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentNode) {
|
|
4374
|
+
anchor.after(table);
|
|
4375
|
+
if (!table.nextElementSibling) {
|
|
4376
|
+
const p = document.createElement("p");
|
|
4377
|
+
p.appendChild(document.createElement("br"));
|
|
4378
|
+
table.after(p);
|
|
4379
|
+
}
|
|
4380
|
+
if (!anchor.textContent.trim() && !anchor.querySelector("img, video, table")) anchor.remove();
|
|
4381
|
+
} else range.insertNode(table);
|
|
4382
|
+
const firstCell = table.querySelector("td, th");
|
|
4383
|
+
if (firstCell) {
|
|
4384
|
+
const nr = document.createRange();
|
|
4385
|
+
nr.setStart(firstCell, 0);
|
|
4386
|
+
nr.collapse(true);
|
|
4387
|
+
sel.removeAllRanges();
|
|
4388
|
+
sel.addRange(nr);
|
|
4389
|
+
}
|
|
4297
4390
|
}
|
|
4298
4391
|
//#endregion
|
|
4299
4392
|
//#region src/js/core/key.js
|
|
@@ -4508,7 +4601,7 @@
|
|
|
4508
4601
|
if (para && para.nodeName.toUpperCase() === "PRE") {
|
|
4509
4602
|
if (event.shiftKey) return false;
|
|
4510
4603
|
event.preventDefault();
|
|
4511
|
-
execCommand("insertText", "
|
|
4604
|
+
execCommand("insertText", " ".repeat(options.tabSize || 4));
|
|
4512
4605
|
return true;
|
|
4513
4606
|
}
|
|
4514
4607
|
if (options.tabSize) {
|
|
@@ -4601,6 +4694,11 @@
|
|
|
4601
4694
|
return true;
|
|
4602
4695
|
}
|
|
4603
4696
|
const para = closestPara(range.sc, editable);
|
|
4697
|
+
if (para && para.nodeName.toUpperCase() === "PRE") {
|
|
4698
|
+
event.preventDefault();
|
|
4699
|
+
execCommand("insertText", "\n");
|
|
4700
|
+
return true;
|
|
4701
|
+
}
|
|
4604
4702
|
if (para && para.nodeName.toUpperCase() === "BLOCKQUOTE") {
|
|
4605
4703
|
const native = range.toNativeRange();
|
|
4606
4704
|
native.setEnd(para, para.childNodes.length);
|
|
@@ -4634,11 +4732,22 @@
|
|
|
4634
4732
|
function htmlToMarkdown(html) {
|
|
4635
4733
|
return _domToMd(new DOMParser().parseFromString(`<body>${html || ""}</body>`, "text/html").body).replace(/\n{3,}/g, "\n\n").trim();
|
|
4636
4734
|
}
|
|
4637
|
-
|
|
4735
|
+
/**
|
|
4736
|
+
* Convert a DOM node subtree into Markdown.
|
|
4737
|
+
*
|
|
4738
|
+
* Recursively produces a Markdown string representing the given DOM node and its descendants,
|
|
4739
|
+
* handling common HTML constructs such as paragraphs, headings, lists (with nested indentation),
|
|
4740
|
+
* blockquotes, fenced and inline code, links, images, tables, horizontal rules, and basic inline emphasis.
|
|
4741
|
+
*
|
|
4742
|
+
* @param {Node} node - The DOM node to convert.
|
|
4743
|
+
* @param {number} [depth=0] - Current nesting depth used to indent nested list items.
|
|
4744
|
+
* @returns {string} The Markdown representation of the node subtree.
|
|
4745
|
+
*/
|
|
4746
|
+
function _domToMd(node, depth = 0) {
|
|
4638
4747
|
if (node.nodeType === 3) return node.textContent.replace(/\s+/g, " ");
|
|
4639
4748
|
if (node.nodeType !== 1) return "";
|
|
4640
4749
|
const tag = node.nodeName.toLowerCase();
|
|
4641
|
-
const inner = () => Array.from(node.childNodes).map(_domToMd).join("");
|
|
4750
|
+
const inner = () => Array.from(node.childNodes).map((n) => _domToMd(n, depth)).join("");
|
|
4642
4751
|
switch (tag) {
|
|
4643
4752
|
case "p":
|
|
4644
4753
|
case "div": return `\n\n${inner()}\n\n`;
|
|
@@ -4678,12 +4787,16 @@
|
|
|
4678
4787
|
case "ul": {
|
|
4679
4788
|
const items = Array.from(node.querySelectorAll(":scope > li"));
|
|
4680
4789
|
if (!items.length) return inner();
|
|
4681
|
-
|
|
4790
|
+
const indent = " ".repeat(depth);
|
|
4791
|
+
const lines = items.map((li) => `${indent}- ${_domToMd(li, depth + 1).trim()}`).join("\n");
|
|
4792
|
+
return depth === 0 ? `\n\n${lines}\n\n` : `\n${lines}`;
|
|
4682
4793
|
}
|
|
4683
4794
|
case "ol": {
|
|
4684
4795
|
const items = Array.from(node.querySelectorAll(":scope > li"));
|
|
4685
4796
|
if (!items.length) return inner();
|
|
4686
|
-
|
|
4797
|
+
const indent = " ".repeat(depth);
|
|
4798
|
+
const lines = items.map((li, i) => `${indent}${i + 1}. ${_domToMd(li, depth + 1).trim()}`).join("\n");
|
|
4799
|
+
return depth === 0 ? `\n\n${lines}\n\n` : `\n${lines}`;
|
|
4687
4800
|
}
|
|
4688
4801
|
case "li": return inner();
|
|
4689
4802
|
case "hr": return "\n\n---\n\n";
|
|
@@ -4707,12 +4820,15 @@
|
|
|
4707
4820
|
}
|
|
4708
4821
|
}
|
|
4709
4822
|
/**
|
|
4710
|
-
*
|
|
4711
|
-
*
|
|
4712
|
-
*
|
|
4823
|
+
* Detects whether a string likely contains Markdown syntax.
|
|
4824
|
+
*
|
|
4825
|
+
* Checks for common Markdown constructs such as ATX headings, unordered or
|
|
4826
|
+
* ordered list items, blockquotes, fenced code blocks, and bold emphasis.
|
|
4827
|
+
* @param {string} text - Input text to inspect for Markdown patterns.
|
|
4828
|
+
* @returns {boolean} `true` if any Markdown-like pattern is present, `false` otherwise.
|
|
4713
4829
|
*/
|
|
4714
4830
|
function isMarkdown(text) {
|
|
4715
|
-
return /^#{1,6} \S|^\s*[-*+] \S|^\s*\d+\. \S|^> \S
|
|
4831
|
+
return /^#{1,6} \S|^\s*[-*+] \S|^\s*\d+\. \S|^> \S|^```|^\*{2}.+?\*{2}/m.test(text);
|
|
4716
4832
|
}
|
|
4717
4833
|
/**
|
|
4718
4834
|
* Converts a Markdown string to an HTML string.
|
|
@@ -5475,6 +5591,8 @@
|
|
|
5475
5591
|
this._disposers = [];
|
|
5476
5592
|
/** @type {Array<() => void>} closers for all open color picker popups */
|
|
5477
5593
|
this._colorPickerClosers = [];
|
|
5594
|
+
/** @type {number|null} rAF handle for debounced refresh */
|
|
5595
|
+
this._refreshRaf = null;
|
|
5478
5596
|
}
|
|
5479
5597
|
initialize() {
|
|
5480
5598
|
this.el = createElement("div", { class: "an-toolbar" });
|
|
@@ -5484,6 +5602,8 @@
|
|
|
5484
5602
|
return this;
|
|
5485
5603
|
}
|
|
5486
5604
|
destroy() {
|
|
5605
|
+
if (this._refreshRaf) cancelAnimationFrame(this._refreshRaf);
|
|
5606
|
+
this._refreshRaf = null;
|
|
5487
5607
|
this._disposers.forEach((d) => d());
|
|
5488
5608
|
this._disposers = [];
|
|
5489
5609
|
if (this.el && this.el.parentNode) this.el.parentNode.removeChild(this.el);
|
|
@@ -5879,6 +5999,13 @@
|
|
|
5879
5999
|
return _faPageLevelReady;
|
|
5880
6000
|
}
|
|
5881
6001
|
refresh() {
|
|
6002
|
+
if (this._refreshRaf) cancelAnimationFrame(this._refreshRaf);
|
|
6003
|
+
this._refreshRaf = requestAnimationFrame(() => {
|
|
6004
|
+
this._refreshRaf = null;
|
|
6005
|
+
this._doRefresh();
|
|
6006
|
+
});
|
|
6007
|
+
}
|
|
6008
|
+
_doRefresh() {
|
|
5882
6009
|
if (!this.el) return;
|
|
5883
6010
|
const btnMap = this._btnMap || /* @__PURE__ */ new Map();
|
|
5884
6011
|
this.el.querySelectorAll("button[data-btn]").forEach((btn) => {
|
|
@@ -6056,7 +6183,7 @@
|
|
|
6056
6183
|
}
|
|
6057
6184
|
update() {
|
|
6058
6185
|
if (!this._wordCountEl || !this._charCountEl) return;
|
|
6059
|
-
const text = this.context.layoutInfo.editable.
|
|
6186
|
+
const text = this.context.layoutInfo.editable.textContent || "";
|
|
6060
6187
|
const words = _countWords(text);
|
|
6061
6188
|
const chars = text.replace(/\n/g, "").length;
|
|
6062
6189
|
const maxWords = this.options.maxWords || 0;
|
|
@@ -6251,7 +6378,7 @@
|
|
|
6251
6378
|
event.preventDefault();
|
|
6252
6379
|
const raw = clipboardData.getData("text/html");
|
|
6253
6380
|
const isWordContent = /<[a-z]+:[a-z]/i.test(raw) || /class="Mso/i.test(raw) || /\bmso-/i.test(raw);
|
|
6254
|
-
const isSocialContent =
|
|
6381
|
+
const isSocialContent = /class="[^"]*\b(?:x[a-z0-9]{6,}|r-[a-z0-9]{3,})\b/.test(raw);
|
|
6255
6382
|
let html = raw;
|
|
6256
6383
|
if (isWordContent) html = this._cleanWordHtml(html);
|
|
6257
6384
|
else if (isSocialContent) html = this._cleanSocialHtml(html);
|
|
@@ -6353,7 +6480,7 @@
|
|
|
6353
6480
|
*/
|
|
6354
6481
|
_dataUrlToBlob(dataUrl) {
|
|
6355
6482
|
const [header, b64] = dataUrl.split(",");
|
|
6356
|
-
const mime = header.match(/:(.*?);/)[1];
|
|
6483
|
+
const mime = header.match(/:(.*?);/)?.[1] ?? "image/png";
|
|
6357
6484
|
const binary = atob(b64);
|
|
6358
6485
|
const arr = new Uint8Array(binary.length);
|
|
6359
6486
|
for (let i = 0; i < binary.length; i++) arr[i] = binary.charCodeAt(i);
|
|
@@ -7359,6 +7486,7 @@
|
|
|
7359
7486
|
_select(img) {
|
|
7360
7487
|
if (this._activeImg && this._activeImg !== img) this._activeImg.classList.remove("an-image-selected");
|
|
7361
7488
|
this._activeImg = img;
|
|
7489
|
+
this._lastOverlayPos = null;
|
|
7362
7490
|
img.classList.add("an-image-selected");
|
|
7363
7491
|
this._updateOverlayPosition();
|
|
7364
7492
|
this._overlay.style.display = "block";
|
|
@@ -7382,6 +7510,14 @@
|
|
|
7382
7510
|
const offsetParent = this._overlay.offsetParent || this._container;
|
|
7383
7511
|
const containerRect = offsetParent.getBoundingClientRect();
|
|
7384
7512
|
const rect = this._activeImg.getBoundingClientRect();
|
|
7513
|
+
const p = this._lastOverlayPos;
|
|
7514
|
+
if (p && p.l === rect.left && p.t === rect.top && p.w === rect.width && p.h === rect.height) return;
|
|
7515
|
+
this._lastOverlayPos = {
|
|
7516
|
+
l: rect.left,
|
|
7517
|
+
t: rect.top,
|
|
7518
|
+
w: rect.width,
|
|
7519
|
+
h: rect.height
|
|
7520
|
+
};
|
|
7385
7521
|
const left = rect.left - containerRect.left + offsetParent.scrollLeft;
|
|
7386
7522
|
const top = rect.top - containerRect.top + offsetParent.scrollTop;
|
|
7387
7523
|
this._overlay.style.left = `${left}px`;
|
|
@@ -13080,7 +13216,7 @@
|
|
|
13080
13216
|
cells.forEach((cell) => {
|
|
13081
13217
|
cell.classList.toggle("active", +cell.dataset.row <= rows && +cell.dataset.col <= cols);
|
|
13082
13218
|
});
|
|
13083
|
-
labelEl.textContent = rows && cols ? `${
|
|
13219
|
+
labelEl.textContent = rows && cols ? `${rows} × ${cols}` : this.context.locale.contextMenu.table || "Insert Table";
|
|
13084
13220
|
};
|
|
13085
13221
|
panel.appendChild(gridEl);
|
|
13086
13222
|
panel.appendChild(labelEl);
|
|
@@ -13720,7 +13856,6 @@
|
|
|
13720
13856
|
if (!editable) return;
|
|
13721
13857
|
const rawMatches = this._findRawMatches(query, editable);
|
|
13722
13858
|
if (rawMatches.length === 0) return;
|
|
13723
|
-
this._currentIndex = 0;
|
|
13724
13859
|
for (let i = rawMatches.length - 1; i >= 0; i--) {
|
|
13725
13860
|
const { node, start, end } = rawMatches[i];
|
|
13726
13861
|
try {
|
|
@@ -13737,6 +13872,7 @@
|
|
|
13737
13872
|
}
|
|
13738
13873
|
this._matches.reverse();
|
|
13739
13874
|
this._matches = this._matches.filter((m) => m.mark);
|
|
13875
|
+
this._currentIndex = 0;
|
|
13740
13876
|
if (this._matches.length === 0) return;
|
|
13741
13877
|
if (this._matches[0] && this._matches[0].mark) {
|
|
13742
13878
|
this._matches[0].mark.className = "an-highlight an-highlight-current";
|
|
@@ -13762,12 +13898,13 @@
|
|
|
13762
13898
|
this._lastCaseSensitive = this._caseSensitive;
|
|
13763
13899
|
}
|
|
13764
13900
|
const re = this._queryRegex;
|
|
13901
|
+
const MAX_RESULTS = 500;
|
|
13765
13902
|
const walker = document.createTreeWalker(root, 4);
|
|
13766
13903
|
let node;
|
|
13767
|
-
while (node = walker.nextNode()) {
|
|
13904
|
+
while ((node = walker.nextNode()) && results.length < MAX_RESULTS) {
|
|
13768
13905
|
re.lastIndex = 0;
|
|
13769
13906
|
let m;
|
|
13770
|
-
while ((m = re.exec(node.textContent)) !== null) results.push({
|
|
13907
|
+
while ((m = re.exec(node.textContent)) !== null && results.length < MAX_RESULTS) results.push({
|
|
13771
13908
|
node,
|
|
13772
13909
|
start: m.index,
|
|
13773
13910
|
end: m.index + m[0].length
|
|
@@ -13854,7 +13991,7 @@
|
|
|
13854
13991
|
const total = this._matches.length;
|
|
13855
13992
|
if (total === 0) {
|
|
13856
13993
|
const query = this._findInput ? this._findInput.value : "";
|
|
13857
|
-
this._counterEl.textContent = query ?
|
|
13994
|
+
this._counterEl.textContent = query ? this.context.locale.findReplace.noResults : "";
|
|
13858
13995
|
} else this._counterEl.textContent = `${this._currentIndex + 1} / ${total}`;
|
|
13859
13996
|
}
|
|
13860
13997
|
};
|
|
@@ -14053,17 +14190,33 @@
|
|
|
14053
14190
|
e.preventDefault();
|
|
14054
14191
|
e.stopPropagation();
|
|
14055
14192
|
this._startHandleDrag(e, id);
|
|
14056
|
-
}))
|
|
14193
|
+
}), on(h, "touchstart", (e) => {
|
|
14194
|
+
e.preventDefault();
|
|
14195
|
+
e.stopPropagation();
|
|
14196
|
+
this._startHandleDrag({
|
|
14197
|
+
clientX: e.touches[0].clientX,
|
|
14198
|
+
clientY: e.touches[0].clientY
|
|
14199
|
+
}, id);
|
|
14200
|
+
}, { passive: false }));
|
|
14057
14201
|
this._handles[id] = h;
|
|
14058
14202
|
cropBox.appendChild(h);
|
|
14059
14203
|
});
|
|
14060
14204
|
this._disposers.push(on(cropBox, "mousedown", (e) => {
|
|
14061
|
-
if (e.target
|
|
14062
|
-
if (e.target
|
|
14205
|
+
if (e.target.classList.contains("an-crop-handle")) return;
|
|
14206
|
+
if (e.target !== cropBox && e.target !== grid) return;
|
|
14063
14207
|
e.preventDefault();
|
|
14064
14208
|
e.stopPropagation();
|
|
14065
14209
|
this._startBoxMove(e);
|
|
14066
|
-
}))
|
|
14210
|
+
}), on(cropBox, "touchstart", (e) => {
|
|
14211
|
+
if (e.target.classList.contains("an-crop-handle")) return;
|
|
14212
|
+
if (e.target !== cropBox && e.target !== grid) return;
|
|
14213
|
+
e.preventDefault();
|
|
14214
|
+
e.stopPropagation();
|
|
14215
|
+
this._startBoxMove({
|
|
14216
|
+
clientX: e.touches[0].clientX,
|
|
14217
|
+
clientY: e.touches[0].clientY
|
|
14218
|
+
});
|
|
14219
|
+
}, { passive: false }));
|
|
14067
14220
|
const infoEl = document.createElement("div");
|
|
14068
14221
|
infoEl.className = "an-crop-info";
|
|
14069
14222
|
infoEl.style.cssText = `
|
|
@@ -14221,15 +14374,26 @@
|
|
|
14221
14374
|
* @param {(e: MouseEvent) => void} onMove
|
|
14222
14375
|
*/
|
|
14223
14376
|
_attachDocDrag(onMove) {
|
|
14377
|
+
const onTouchMove = (e) => {
|
|
14378
|
+
e.preventDefault();
|
|
14379
|
+
onMove({
|
|
14380
|
+
clientX: e.touches[0].clientX,
|
|
14381
|
+
clientY: e.touches[0].clientY
|
|
14382
|
+
});
|
|
14383
|
+
};
|
|
14224
14384
|
const cleanup = () => {
|
|
14225
14385
|
document.removeEventListener("mousemove", onMove);
|
|
14226
14386
|
document.removeEventListener("mouseup", cleanup);
|
|
14387
|
+
document.removeEventListener("touchmove", onTouchMove);
|
|
14388
|
+
document.removeEventListener("touchend", cleanup);
|
|
14227
14389
|
document.body.style.userSelect = "";
|
|
14228
14390
|
document.body.style.cursor = "";
|
|
14229
14391
|
};
|
|
14230
14392
|
document.body.style.userSelect = "none";
|
|
14231
14393
|
document.addEventListener("mousemove", onMove);
|
|
14232
14394
|
document.addEventListener("mouseup", cleanup, { once: true });
|
|
14395
|
+
document.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
14396
|
+
document.addEventListener("touchend", cleanup, { once: true });
|
|
14233
14397
|
}
|
|
14234
14398
|
_bindEsc() {
|
|
14235
14399
|
const handler = (e) => {
|
|
@@ -14270,7 +14434,7 @@
|
|
|
14270
14434
|
height: natH
|
|
14271
14435
|
}, w, h);
|
|
14272
14436
|
if (!canvas) {
|
|
14273
|
-
|
|
14437
|
+
this._showCropError("Cannot crop this image: the image server does not allow cross-origin access. Upload the image directly to use the crop tool.");
|
|
14274
14438
|
this._close(false);
|
|
14275
14439
|
return;
|
|
14276
14440
|
}
|
|
@@ -14287,6 +14451,37 @@
|
|
|
14287
14451
|
this.context.invoke("imageResizer.updateOverlay");
|
|
14288
14452
|
}
|
|
14289
14453
|
/**
|
|
14454
|
+
* Show a non-blocking inline error banner appended to document.body.
|
|
14455
|
+
* Auto-dismisses after 4 seconds.
|
|
14456
|
+
* @param {string} msg
|
|
14457
|
+
*/
|
|
14458
|
+
_showCropError(msg) {
|
|
14459
|
+
const banner = document.createElement("div");
|
|
14460
|
+
banner.setAttribute("role", "alert");
|
|
14461
|
+
banner.style.cssText = [
|
|
14462
|
+
"position:fixed",
|
|
14463
|
+
"bottom:24px",
|
|
14464
|
+
"left:50%",
|
|
14465
|
+
"transform:translateX(-50%)",
|
|
14466
|
+
"z-index:10200",
|
|
14467
|
+
"max-width:420px",
|
|
14468
|
+
"width:max-content",
|
|
14469
|
+
"background:#7f1d1d",
|
|
14470
|
+
"color:#fecaca",
|
|
14471
|
+
"border:1px solid #b91c1c",
|
|
14472
|
+
"border-radius:8px",
|
|
14473
|
+
"padding:12px 18px",
|
|
14474
|
+
"font:13px/1.5 system-ui,sans-serif",
|
|
14475
|
+
"box-shadow:0 4px 16px rgba(0,0,0,.4)",
|
|
14476
|
+
"pointer-events:auto"
|
|
14477
|
+
].join(";");
|
|
14478
|
+
banner.textContent = msg;
|
|
14479
|
+
document.body.appendChild(banner);
|
|
14480
|
+
setTimeout(() => {
|
|
14481
|
+
if (banner.parentNode) banner.parentNode.removeChild(banner);
|
|
14482
|
+
}, 4e3);
|
|
14483
|
+
}
|
|
14484
|
+
/**
|
|
14290
14485
|
* Remove all overlay DOM elements and reset state.
|
|
14291
14486
|
* @param {boolean} _committed - reserved for future use
|
|
14292
14487
|
*/
|
|
@@ -14701,6 +14896,16 @@
|
|
|
14701
14896
|
if (!editable) return;
|
|
14702
14897
|
editable.focus();
|
|
14703
14898
|
document.execCommand("removeFormat");
|
|
14899
|
+
const sel = window.getSelection();
|
|
14900
|
+
if (sel && sel.rangeCount > 0 && !sel.getRangeAt(0).collapsed) {
|
|
14901
|
+
const range = sel.getRangeAt(0);
|
|
14902
|
+
const ancestor = range.commonAncestorContainer;
|
|
14903
|
+
const root = ancestor.nodeType === 1 ? ancestor : ancestor.parentElement;
|
|
14904
|
+
if (root) {
|
|
14905
|
+
const candidates = [root, ...root.querySelectorAll("[style]")];
|
|
14906
|
+
for (const el of candidates) if (el.hasAttribute("style") && range.intersectsNode(el)) el.removeAttribute("style");
|
|
14907
|
+
}
|
|
14908
|
+
}
|
|
14704
14909
|
ctx.invoke("editor.afterCommand");
|
|
14705
14910
|
},
|
|
14706
14911
|
inlineCode: (ctx) => ctx.invoke("editor.inlineCode")
|
|
@@ -14814,6 +15019,7 @@
|
|
|
14814
15019
|
}
|
|
14815
15020
|
document.body.appendChild(el);
|
|
14816
15021
|
this._el = el;
|
|
15022
|
+
this._btnCache = Array.from(el.querySelectorAll(".an-bubble-btn"));
|
|
14817
15023
|
}
|
|
14818
15024
|
_buildColorPicker() {
|
|
14819
15025
|
const picker = document.createElement("div");
|
|
@@ -14899,8 +15105,13 @@
|
|
|
14899
15105
|
editable.focus();
|
|
14900
15106
|
const sel = window.getSelection();
|
|
14901
15107
|
sel.removeAllRanges();
|
|
14902
|
-
|
|
14903
|
-
|
|
15108
|
+
try {
|
|
15109
|
+
sel.addRange(this._savedRange.cloneRange());
|
|
15110
|
+
} catch (_) {
|
|
15111
|
+
return;
|
|
15112
|
+
}
|
|
15113
|
+
const cmd = type === "hiliteColor" ? "hiliteColor" : type;
|
|
15114
|
+
if (!document.execCommand(cmd, false, color) && cmd === "hiliteColor") document.execCommand("backColor", false, color);
|
|
14904
15115
|
this.context.invoke("editor.afterCommand");
|
|
14905
15116
|
const name = type === "hiliteColor" ? "hiliteColor" : "foreColor";
|
|
14906
15117
|
const btn = this._el && this._el.querySelector(`[data-name="${name}"]`);
|
|
@@ -14935,8 +15146,8 @@
|
|
|
14935
15146
|
this._closeColorPicker();
|
|
14936
15147
|
}
|
|
14937
15148
|
_syncActive() {
|
|
14938
|
-
if (!this.
|
|
14939
|
-
this.
|
|
15149
|
+
if (!this._btnCache) return;
|
|
15150
|
+
this._btnCache.forEach((btn) => {
|
|
14940
15151
|
const activeFn = _ACTIVE[btn.dataset.name];
|
|
14941
15152
|
btn.classList.toggle("an-active", !!(activeFn && activeFn()));
|
|
14942
15153
|
});
|
|
@@ -15083,14 +15294,23 @@
|
|
|
15083
15294
|
const el = document.createElement("div");
|
|
15084
15295
|
el.className = "an-mention-dropdown";
|
|
15085
15296
|
el.setAttribute("role", "listbox");
|
|
15297
|
+
el.addEventListener("mousedown", (e) => e.preventDefault());
|
|
15298
|
+
el.addEventListener("click", (e) => {
|
|
15299
|
+
const item = e.target.closest(".an-mention-item");
|
|
15300
|
+
if (item) this._select(+item.dataset.index);
|
|
15301
|
+
});
|
|
15302
|
+
el.addEventListener("mousemove", (e) => {
|
|
15303
|
+
const item = e.target.closest(".an-mention-item");
|
|
15304
|
+
if (item) this._highlightItem(+item.dataset.index);
|
|
15305
|
+
});
|
|
15086
15306
|
document.body.appendChild(el);
|
|
15087
15307
|
this._dropdown = el;
|
|
15088
15308
|
}
|
|
15089
15309
|
_renderItems(items) {
|
|
15090
15310
|
const dd = this._dropdown;
|
|
15091
|
-
dd.innerHTML = "";
|
|
15092
15311
|
this._items = items.slice(0, this._cfg.maxResults);
|
|
15093
15312
|
this._activeIndex = this._items.length > 0 ? 0 : -1;
|
|
15313
|
+
const frag = document.createDocumentFragment();
|
|
15094
15314
|
this._items.forEach((item, i) => {
|
|
15095
15315
|
const li = document.createElement("div");
|
|
15096
15316
|
li.className = "an-mention-item";
|
|
@@ -15106,10 +15326,10 @@
|
|
|
15106
15326
|
const label = document.createElement("span");
|
|
15107
15327
|
label.textContent = item.label;
|
|
15108
15328
|
li.appendChild(label);
|
|
15109
|
-
|
|
15110
|
-
li.addEventListener("click", () => this._select(i));
|
|
15111
|
-
dd.appendChild(li);
|
|
15329
|
+
frag.appendChild(li);
|
|
15112
15330
|
});
|
|
15331
|
+
dd.innerHTML = "";
|
|
15332
|
+
dd.appendChild(frag);
|
|
15113
15333
|
this._highlightItem(this._activeIndex);
|
|
15114
15334
|
}
|
|
15115
15335
|
_highlightItem(index) {
|