autumnnote 1.2.0 → 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/README.md +1 -1
- package/dist/autumnnote.es.js +343 -108
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +314 -92
- package/dist/autumnnote.umd.js.map +1 -1
- package/dist/image/banner.png +0 -0
- package/dist/image/favicon.ico +0 -0
- 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 +28 -7
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);
|
|
@@ -5558,6 +5678,7 @@
|
|
|
5558
5678
|
const openPopup = () => {
|
|
5559
5679
|
isOpen = true;
|
|
5560
5680
|
const rect = btn.getBoundingClientRect();
|
|
5681
|
+
popup.style.visibility = "hidden";
|
|
5561
5682
|
popup.style.display = "block";
|
|
5562
5683
|
const pw = popup.offsetWidth;
|
|
5563
5684
|
const ph = popup.offsetHeight;
|
|
@@ -5567,13 +5688,12 @@
|
|
|
5567
5688
|
if (top + ph > window.innerHeight - 8) top = rect.top - ph - 4;
|
|
5568
5689
|
popup.style.left = `${left}px`;
|
|
5569
5690
|
popup.style.top = `${top}px`;
|
|
5691
|
+
popup.style.visibility = "";
|
|
5570
5692
|
btn.setAttribute("aria-expanded", "true");
|
|
5571
5693
|
};
|
|
5572
5694
|
const closePopup = () => {
|
|
5573
5695
|
isOpen = false;
|
|
5574
5696
|
popup.style.display = "none";
|
|
5575
|
-
popup.style.top = "";
|
|
5576
|
-
popup.style.left = "";
|
|
5577
5697
|
btn.setAttribute("aria-expanded", "false");
|
|
5578
5698
|
setHighlight(0, 0);
|
|
5579
5699
|
};
|
|
@@ -5600,9 +5720,11 @@
|
|
|
5600
5720
|
const d5 = on(document, "click", () => {
|
|
5601
5721
|
if (isOpen) closePopup();
|
|
5602
5722
|
});
|
|
5603
|
-
this._disposers.push(d1, d2, d3, d4, d5)
|
|
5723
|
+
this._disposers.push(d1, d2, d3, d4, d5, () => {
|
|
5724
|
+
if (popup.parentNode) popup.parentNode.removeChild(popup);
|
|
5725
|
+
});
|
|
5604
5726
|
wrap.appendChild(btn);
|
|
5605
|
-
|
|
5727
|
+
document.body.appendChild(popup);
|
|
5606
5728
|
return wrap;
|
|
5607
5729
|
}
|
|
5608
5730
|
/**
|
|
@@ -5877,6 +5999,13 @@
|
|
|
5877
5999
|
return _faPageLevelReady;
|
|
5878
6000
|
}
|
|
5879
6001
|
refresh() {
|
|
6002
|
+
if (this._refreshRaf) cancelAnimationFrame(this._refreshRaf);
|
|
6003
|
+
this._refreshRaf = requestAnimationFrame(() => {
|
|
6004
|
+
this._refreshRaf = null;
|
|
6005
|
+
this._doRefresh();
|
|
6006
|
+
});
|
|
6007
|
+
}
|
|
6008
|
+
_doRefresh() {
|
|
5880
6009
|
if (!this.el) return;
|
|
5881
6010
|
const btnMap = this._btnMap || /* @__PURE__ */ new Map();
|
|
5882
6011
|
this.el.querySelectorAll("button[data-btn]").forEach((btn) => {
|
|
@@ -6054,7 +6183,7 @@
|
|
|
6054
6183
|
}
|
|
6055
6184
|
update() {
|
|
6056
6185
|
if (!this._wordCountEl || !this._charCountEl) return;
|
|
6057
|
-
const text = this.context.layoutInfo.editable.
|
|
6186
|
+
const text = this.context.layoutInfo.editable.textContent || "";
|
|
6058
6187
|
const words = _countWords(text);
|
|
6059
6188
|
const chars = text.replace(/\n/g, "").length;
|
|
6060
6189
|
const maxWords = this.options.maxWords || 0;
|
|
@@ -6249,7 +6378,7 @@
|
|
|
6249
6378
|
event.preventDefault();
|
|
6250
6379
|
const raw = clipboardData.getData("text/html");
|
|
6251
6380
|
const isWordContent = /<[a-z]+:[a-z]/i.test(raw) || /class="Mso/i.test(raw) || /\bmso-/i.test(raw);
|
|
6252
|
-
const isSocialContent =
|
|
6381
|
+
const isSocialContent = /class="[^"]*\b(?:x[a-z0-9]{6,}|r-[a-z0-9]{3,})\b/.test(raw);
|
|
6253
6382
|
let html = raw;
|
|
6254
6383
|
if (isWordContent) html = this._cleanWordHtml(html);
|
|
6255
6384
|
else if (isSocialContent) html = this._cleanSocialHtml(html);
|
|
@@ -6351,7 +6480,7 @@
|
|
|
6351
6480
|
*/
|
|
6352
6481
|
_dataUrlToBlob(dataUrl) {
|
|
6353
6482
|
const [header, b64] = dataUrl.split(",");
|
|
6354
|
-
const mime = header.match(/:(.*?);/)[1];
|
|
6483
|
+
const mime = header.match(/:(.*?);/)?.[1] ?? "image/png";
|
|
6355
6484
|
const binary = atob(b64);
|
|
6356
6485
|
const arr = new Uint8Array(binary.length);
|
|
6357
6486
|
for (let i = 0; i < binary.length; i++) arr[i] = binary.charCodeAt(i);
|
|
@@ -7357,6 +7486,7 @@
|
|
|
7357
7486
|
_select(img) {
|
|
7358
7487
|
if (this._activeImg && this._activeImg !== img) this._activeImg.classList.remove("an-image-selected");
|
|
7359
7488
|
this._activeImg = img;
|
|
7489
|
+
this._lastOverlayPos = null;
|
|
7360
7490
|
img.classList.add("an-image-selected");
|
|
7361
7491
|
this._updateOverlayPosition();
|
|
7362
7492
|
this._overlay.style.display = "block";
|
|
@@ -7380,6 +7510,14 @@
|
|
|
7380
7510
|
const offsetParent = this._overlay.offsetParent || this._container;
|
|
7381
7511
|
const containerRect = offsetParent.getBoundingClientRect();
|
|
7382
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
|
+
};
|
|
7383
7521
|
const left = rect.left - containerRect.left + offsetParent.scrollLeft;
|
|
7384
7522
|
const top = rect.top - containerRect.top + offsetParent.scrollTop;
|
|
7385
7523
|
this._overlay.style.left = `${left}px`;
|
|
@@ -13078,7 +13216,7 @@
|
|
|
13078
13216
|
cells.forEach((cell) => {
|
|
13079
13217
|
cell.classList.toggle("active", +cell.dataset.row <= rows && +cell.dataset.col <= cols);
|
|
13080
13218
|
});
|
|
13081
|
-
labelEl.textContent = rows && cols ? `${
|
|
13219
|
+
labelEl.textContent = rows && cols ? `${rows} × ${cols}` : this.context.locale.contextMenu.table || "Insert Table";
|
|
13082
13220
|
};
|
|
13083
13221
|
panel.appendChild(gridEl);
|
|
13084
13222
|
panel.appendChild(labelEl);
|
|
@@ -13718,7 +13856,6 @@
|
|
|
13718
13856
|
if (!editable) return;
|
|
13719
13857
|
const rawMatches = this._findRawMatches(query, editable);
|
|
13720
13858
|
if (rawMatches.length === 0) return;
|
|
13721
|
-
this._currentIndex = 0;
|
|
13722
13859
|
for (let i = rawMatches.length - 1; i >= 0; i--) {
|
|
13723
13860
|
const { node, start, end } = rawMatches[i];
|
|
13724
13861
|
try {
|
|
@@ -13735,6 +13872,7 @@
|
|
|
13735
13872
|
}
|
|
13736
13873
|
this._matches.reverse();
|
|
13737
13874
|
this._matches = this._matches.filter((m) => m.mark);
|
|
13875
|
+
this._currentIndex = 0;
|
|
13738
13876
|
if (this._matches.length === 0) return;
|
|
13739
13877
|
if (this._matches[0] && this._matches[0].mark) {
|
|
13740
13878
|
this._matches[0].mark.className = "an-highlight an-highlight-current";
|
|
@@ -13760,12 +13898,13 @@
|
|
|
13760
13898
|
this._lastCaseSensitive = this._caseSensitive;
|
|
13761
13899
|
}
|
|
13762
13900
|
const re = this._queryRegex;
|
|
13901
|
+
const MAX_RESULTS = 500;
|
|
13763
13902
|
const walker = document.createTreeWalker(root, 4);
|
|
13764
13903
|
let node;
|
|
13765
|
-
while (node = walker.nextNode()) {
|
|
13904
|
+
while ((node = walker.nextNode()) && results.length < MAX_RESULTS) {
|
|
13766
13905
|
re.lastIndex = 0;
|
|
13767
13906
|
let m;
|
|
13768
|
-
while ((m = re.exec(node.textContent)) !== null) results.push({
|
|
13907
|
+
while ((m = re.exec(node.textContent)) !== null && results.length < MAX_RESULTS) results.push({
|
|
13769
13908
|
node,
|
|
13770
13909
|
start: m.index,
|
|
13771
13910
|
end: m.index + m[0].length
|
|
@@ -13852,7 +13991,7 @@
|
|
|
13852
13991
|
const total = this._matches.length;
|
|
13853
13992
|
if (total === 0) {
|
|
13854
13993
|
const query = this._findInput ? this._findInput.value : "";
|
|
13855
|
-
this._counterEl.textContent = query ?
|
|
13994
|
+
this._counterEl.textContent = query ? this.context.locale.findReplace.noResults : "";
|
|
13856
13995
|
} else this._counterEl.textContent = `${this._currentIndex + 1} / ${total}`;
|
|
13857
13996
|
}
|
|
13858
13997
|
};
|
|
@@ -14051,17 +14190,33 @@
|
|
|
14051
14190
|
e.preventDefault();
|
|
14052
14191
|
e.stopPropagation();
|
|
14053
14192
|
this._startHandleDrag(e, id);
|
|
14054
|
-
}))
|
|
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 }));
|
|
14055
14201
|
this._handles[id] = h;
|
|
14056
14202
|
cropBox.appendChild(h);
|
|
14057
14203
|
});
|
|
14058
14204
|
this._disposers.push(on(cropBox, "mousedown", (e) => {
|
|
14059
|
-
if (e.target
|
|
14060
|
-
if (e.target
|
|
14205
|
+
if (e.target.classList.contains("an-crop-handle")) return;
|
|
14206
|
+
if (e.target !== cropBox && e.target !== grid) return;
|
|
14061
14207
|
e.preventDefault();
|
|
14062
14208
|
e.stopPropagation();
|
|
14063
14209
|
this._startBoxMove(e);
|
|
14064
|
-
}))
|
|
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 }));
|
|
14065
14220
|
const infoEl = document.createElement("div");
|
|
14066
14221
|
infoEl.className = "an-crop-info";
|
|
14067
14222
|
infoEl.style.cssText = `
|
|
@@ -14219,15 +14374,26 @@
|
|
|
14219
14374
|
* @param {(e: MouseEvent) => void} onMove
|
|
14220
14375
|
*/
|
|
14221
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
|
+
};
|
|
14222
14384
|
const cleanup = () => {
|
|
14223
14385
|
document.removeEventListener("mousemove", onMove);
|
|
14224
14386
|
document.removeEventListener("mouseup", cleanup);
|
|
14387
|
+
document.removeEventListener("touchmove", onTouchMove);
|
|
14388
|
+
document.removeEventListener("touchend", cleanup);
|
|
14225
14389
|
document.body.style.userSelect = "";
|
|
14226
14390
|
document.body.style.cursor = "";
|
|
14227
14391
|
};
|
|
14228
14392
|
document.body.style.userSelect = "none";
|
|
14229
14393
|
document.addEventListener("mousemove", onMove);
|
|
14230
14394
|
document.addEventListener("mouseup", cleanup, { once: true });
|
|
14395
|
+
document.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
14396
|
+
document.addEventListener("touchend", cleanup, { once: true });
|
|
14231
14397
|
}
|
|
14232
14398
|
_bindEsc() {
|
|
14233
14399
|
const handler = (e) => {
|
|
@@ -14268,7 +14434,7 @@
|
|
|
14268
14434
|
height: natH
|
|
14269
14435
|
}, w, h);
|
|
14270
14436
|
if (!canvas) {
|
|
14271
|
-
|
|
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.");
|
|
14272
14438
|
this._close(false);
|
|
14273
14439
|
return;
|
|
14274
14440
|
}
|
|
@@ -14285,6 +14451,37 @@
|
|
|
14285
14451
|
this.context.invoke("imageResizer.updateOverlay");
|
|
14286
14452
|
}
|
|
14287
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
|
+
/**
|
|
14288
14485
|
* Remove all overlay DOM elements and reset state.
|
|
14289
14486
|
* @param {boolean} _committed - reserved for future use
|
|
14290
14487
|
*/
|
|
@@ -14699,6 +14896,16 @@
|
|
|
14699
14896
|
if (!editable) return;
|
|
14700
14897
|
editable.focus();
|
|
14701
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
|
+
}
|
|
14702
14909
|
ctx.invoke("editor.afterCommand");
|
|
14703
14910
|
},
|
|
14704
14911
|
inlineCode: (ctx) => ctx.invoke("editor.inlineCode")
|
|
@@ -14812,6 +15019,7 @@
|
|
|
14812
15019
|
}
|
|
14813
15020
|
document.body.appendChild(el);
|
|
14814
15021
|
this._el = el;
|
|
15022
|
+
this._btnCache = Array.from(el.querySelectorAll(".an-bubble-btn"));
|
|
14815
15023
|
}
|
|
14816
15024
|
_buildColorPicker() {
|
|
14817
15025
|
const picker = document.createElement("div");
|
|
@@ -14897,8 +15105,13 @@
|
|
|
14897
15105
|
editable.focus();
|
|
14898
15106
|
const sel = window.getSelection();
|
|
14899
15107
|
sel.removeAllRanges();
|
|
14900
|
-
|
|
14901
|
-
|
|
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);
|
|
14902
15115
|
this.context.invoke("editor.afterCommand");
|
|
14903
15116
|
const name = type === "hiliteColor" ? "hiliteColor" : "foreColor";
|
|
14904
15117
|
const btn = this._el && this._el.querySelector(`[data-name="${name}"]`);
|
|
@@ -14933,8 +15146,8 @@
|
|
|
14933
15146
|
this._closeColorPicker();
|
|
14934
15147
|
}
|
|
14935
15148
|
_syncActive() {
|
|
14936
|
-
if (!this.
|
|
14937
|
-
this.
|
|
15149
|
+
if (!this._btnCache) return;
|
|
15150
|
+
this._btnCache.forEach((btn) => {
|
|
14938
15151
|
const activeFn = _ACTIVE[btn.dataset.name];
|
|
14939
15152
|
btn.classList.toggle("an-active", !!(activeFn && activeFn()));
|
|
14940
15153
|
});
|
|
@@ -15081,14 +15294,23 @@
|
|
|
15081
15294
|
const el = document.createElement("div");
|
|
15082
15295
|
el.className = "an-mention-dropdown";
|
|
15083
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
|
+
});
|
|
15084
15306
|
document.body.appendChild(el);
|
|
15085
15307
|
this._dropdown = el;
|
|
15086
15308
|
}
|
|
15087
15309
|
_renderItems(items) {
|
|
15088
15310
|
const dd = this._dropdown;
|
|
15089
|
-
dd.innerHTML = "";
|
|
15090
15311
|
this._items = items.slice(0, this._cfg.maxResults);
|
|
15091
15312
|
this._activeIndex = this._items.length > 0 ? 0 : -1;
|
|
15313
|
+
const frag = document.createDocumentFragment();
|
|
15092
15314
|
this._items.forEach((item, i) => {
|
|
15093
15315
|
const li = document.createElement("div");
|
|
15094
15316
|
li.className = "an-mention-item";
|
|
@@ -15104,10 +15326,10 @@
|
|
|
15104
15326
|
const label = document.createElement("span");
|
|
15105
15327
|
label.textContent = item.label;
|
|
15106
15328
|
li.appendChild(label);
|
|
15107
|
-
|
|
15108
|
-
li.addEventListener("click", () => this._select(i));
|
|
15109
|
-
dd.appendChild(li);
|
|
15329
|
+
frag.appendChild(li);
|
|
15110
15330
|
});
|
|
15331
|
+
dd.innerHTML = "";
|
|
15332
|
+
dd.appendChild(frag);
|
|
15111
15333
|
this._highlightItem(this._activeIndex);
|
|
15112
15334
|
}
|
|
15113
15335
|
_highlightItem(index) {
|