autumnnote 1.6.0 → 1.6.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 +4 -4
- package/dist/autumnnote.css +0 -2
- package/dist/autumnnote.es.js +468 -552
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +461 -546
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +21 -3
- package/src/js/Context.js +21 -16
- package/src/js/core/dom.js +9 -9
- package/src/js/core/env.js +1 -1
- package/src/js/core/func.js +1 -1
- package/src/js/core/lists.js +1 -1
- package/src/js/core/markdown.js +18 -18
- package/src/js/core/range.js +6 -6
- package/src/js/editing/History.js +4 -4
- package/src/js/editing/Style.js +32 -32
- package/src/js/editing/Table.js +2 -2
- package/src/js/editing/Typing.js +15 -15
- package/src/js/index.js +1 -1
- package/src/js/module/AutoSaveRestore.js +2 -4
- package/src/js/module/BaseDialog.js +126 -0
- package/src/js/module/BubbleToolbar.js +25 -25
- package/src/js/module/Buttons.js +4 -4
- package/src/js/module/Clipboard.js +12 -13
- package/src/js/module/CodeTooltip.js +14 -16
- package/src/js/module/Codeview.js +3 -5
- package/src/js/module/ContextMenu.js +27 -27
- package/src/js/module/Editor.js +17 -18
- package/src/js/module/EmojiDialog.js +5 -5
- package/src/js/module/FindReplace.js +8 -7
- package/src/js/module/IconDialog.js +13 -15
- package/src/js/module/ImageCropOverlay.js +7 -7
- package/src/js/module/ImageDialog.js +12 -82
- package/src/js/module/ImageResizer.js +4 -4
- package/src/js/module/ImageTooltip.js +14 -14
- package/src/js/module/LinkDialog.js +13 -79
- package/src/js/module/LinkTooltip.js +12 -12
- package/src/js/module/MarkdownShortcuts.js +11 -14
- package/src/js/module/Mention.js +8 -10
- package/src/js/module/Placeholder.js +1 -1
- package/src/js/module/ShortcutsDialog.js +2 -4
- package/src/js/module/Statusbar.js +3 -5
- package/src/js/module/TableTooltip.js +30 -32
- package/src/js/module/Toolbar.js +21 -21
- package/src/js/module/VideoDialog.js +17 -87
- package/src/js/module/VideoResizer.js +5 -7
- package/src/js/module/VideoTooltip.js +7 -7
- package/src/js/renderer.js +1 -1
- package/src/styles/autumnnote.scss +0 -3
|
@@ -26,9 +26,7 @@ export class Codeview {
|
|
|
26
26
|
destroy() {
|
|
27
27
|
this._disposers.forEach((d) => d());
|
|
28
28
|
this._disposers = [];
|
|
29
|
-
|
|
30
|
-
this._textarea.parentNode.removeChild(this._textarea);
|
|
31
|
-
}
|
|
29
|
+
this._textarea?.remove();
|
|
32
30
|
this._textarea = null;
|
|
33
31
|
}
|
|
34
32
|
|
|
@@ -74,7 +72,7 @@ export class Codeview {
|
|
|
74
72
|
const { editable } = this.context.layoutInfo;
|
|
75
73
|
// Sanitise the HTML typed in the textarea before applying (allow iframes for video embeds)
|
|
76
74
|
editable.innerHTML = sanitiseHTML(this._textarea.value, { allowIframes: true });
|
|
77
|
-
this._textarea.
|
|
75
|
+
this._textarea.remove();
|
|
78
76
|
this._textarea = null;
|
|
79
77
|
editable.style.display = '';
|
|
80
78
|
this._active = false;
|
|
@@ -108,7 +106,7 @@ export class Codeview {
|
|
|
108
106
|
.map((line) => {
|
|
109
107
|
const stripped = line.trim();
|
|
110
108
|
if (!stripped) return '';
|
|
111
|
-
if (
|
|
109
|
+
if (stripped.startsWith('</')) indent = Math.max(0, indent - 1);
|
|
112
110
|
const out = ' '.repeat(indent) + stripped;
|
|
113
111
|
if (
|
|
114
112
|
/^<[^/!][^>]*[^/]>/.test(stripped) &&
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ContextMenu.js - Right-click context menu for editor actions
|
|
1
|
+
// ContextMenu.js - Right-click context menu for editor actions
|
|
2
2
|
import { createElement, on } from '../core/dom.js';
|
|
3
3
|
import { sanitiseHTML } from '../core/sanitise.js';
|
|
4
4
|
|
|
@@ -60,7 +60,7 @@ const defaultItems = [
|
|
|
60
60
|
{ name: 'copy', label: 'Copy', icon: ICONS.copy, action: () => document.execCommand('copy') },
|
|
61
61
|
{ name: 'paste', label: 'Paste', icon: ICONS.paste, action: (ctx) => {
|
|
62
62
|
if (!navigator.clipboard) return;
|
|
63
|
-
const editable = ctx.layoutInfo
|
|
63
|
+
const editable = ctx.layoutInfo?.editable;
|
|
64
64
|
if (!editable) return;
|
|
65
65
|
// Prefer read() to get rich HTML; fall back to readText() for plain text
|
|
66
66
|
const doInsert = (html, text) => {
|
|
@@ -131,14 +131,14 @@ export class ContextMenu {
|
|
|
131
131
|
|
|
132
132
|
this._renderItems(this._items);
|
|
133
133
|
|
|
134
|
-
const editable = this.context.layoutInfo
|
|
134
|
+
const editable = this.context.layoutInfo?.editable;
|
|
135
135
|
if (editable) {
|
|
136
136
|
this._disposers.push(on(editable, 'contextmenu', (e) => this._onContextMenu(e)));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
this._disposers.push(on(document, 'click', (e) => this._maybeHide(e)));
|
|
140
140
|
this._disposers.push(on(document, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Escape') this.hide(); }));
|
|
141
|
-
this._disposers.push(on(
|
|
141
|
+
this._disposers.push(on(globalThis, 'scroll', () => this.hide(), { passive: true }));
|
|
142
142
|
|
|
143
143
|
return this;
|
|
144
144
|
}
|
|
@@ -148,7 +148,7 @@ export class ContextMenu {
|
|
|
148
148
|
this._menuDisposers = [];
|
|
149
149
|
this._disposers.forEach((d) => { try { d(); } catch (_e) {} });
|
|
150
150
|
this._disposers = [];
|
|
151
|
-
if (this.el
|
|
151
|
+
if (this.el) this.el.remove();
|
|
152
152
|
this.el = null;
|
|
153
153
|
}
|
|
154
154
|
|
|
@@ -176,8 +176,8 @@ export class ContextMenu {
|
|
|
176
176
|
backBtn.appendChild(createElement('span', { class: 'an-context-label' }, [backLabel]));
|
|
177
177
|
const off = on(backBtn, 'click', (e) => {
|
|
178
178
|
e.stopPropagation();
|
|
179
|
-
const curLeft = parseFloat(this.el.style.left);
|
|
180
|
-
const curTop = parseFloat(this.el.style.top);
|
|
179
|
+
const curLeft = Number.parseFloat(this.el.style.left);
|
|
180
|
+
const curTop = Number.parseFloat(this.el.style.top);
|
|
181
181
|
this._renderItems(it.navigate());
|
|
182
182
|
this._reposition(curLeft, curTop);
|
|
183
183
|
});
|
|
@@ -212,8 +212,8 @@ export class ContextMenu {
|
|
|
212
212
|
btn.appendChild(chevron);
|
|
213
213
|
const off = on(btn, 'click', (e) => {
|
|
214
214
|
e.stopPropagation();
|
|
215
|
-
const curLeft = parseFloat(this.el.style.left);
|
|
216
|
-
const curTop = parseFloat(this.el.style.top);
|
|
215
|
+
const curLeft = Number.parseFloat(this.el.style.left);
|
|
216
|
+
const curTop = Number.parseFloat(this.el.style.top);
|
|
217
217
|
this._renderItems(it.navigate());
|
|
218
218
|
this._reposition(curLeft, curTop);
|
|
219
219
|
});
|
|
@@ -334,10 +334,10 @@ export class ContextMenu {
|
|
|
334
334
|
if (!cell) return;
|
|
335
335
|
const rows = +cell.dataset.row;
|
|
336
336
|
const cols = +cell.dataset.col;
|
|
337
|
-
const editable = this.context.layoutInfo
|
|
337
|
+
const editable = this.context.layoutInfo?.editable;
|
|
338
338
|
if (editable && this._savedRange) {
|
|
339
339
|
editable.focus();
|
|
340
|
-
const sel =
|
|
340
|
+
const sel = globalThis.getSelection();
|
|
341
341
|
sel.removeAllRanges();
|
|
342
342
|
sel.addRange(this._savedRange.cloneRange());
|
|
343
343
|
}
|
|
@@ -372,13 +372,13 @@ export class ContextMenu {
|
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
_onContextMenu(event) {
|
|
375
|
-
const editable = this.context.layoutInfo
|
|
375
|
+
const editable = this.context.layoutInfo?.editable;
|
|
376
376
|
if (!editable) return;
|
|
377
377
|
if (!editable.contains(event.target)) return;
|
|
378
378
|
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
379
379
|
event.preventDefault();
|
|
380
380
|
|
|
381
|
-
const winSel =
|
|
381
|
+
const winSel = globalThis.getSelection();
|
|
382
382
|
this._savedRange = (winSel && winSel.rangeCount > 0) ? winSel.getRangeAt(0).cloneRange() : null;
|
|
383
383
|
this._renderItems(this._items);
|
|
384
384
|
|
|
@@ -425,9 +425,9 @@ export class ContextMenu {
|
|
|
425
425
|
let left = rx;
|
|
426
426
|
let top = ry;
|
|
427
427
|
// Clamp to viewport so the menu never overflows the screen edge
|
|
428
|
-
if (left + w >
|
|
428
|
+
if (left + w > globalThis.innerWidth - 8) left = globalThis.innerWidth - w - 8;
|
|
429
429
|
if (left < 8) left = 8;
|
|
430
|
-
if (top + h >
|
|
430
|
+
if (top + h > globalThis.innerHeight - 8) top = globalThis.innerHeight - h - 8;
|
|
431
431
|
if (top < 8) top = 8;
|
|
432
432
|
this.el.style.left = `${left}px`;
|
|
433
433
|
this.el.style.top = `${top}px`;
|
|
@@ -454,7 +454,7 @@ export class ContextMenu {
|
|
|
454
454
|
let node = range.startContainer;
|
|
455
455
|
if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
|
|
456
456
|
if (!node) return type === 'foreColor' ? '#000000' : 'transparent';
|
|
457
|
-
const cs =
|
|
457
|
+
const cs = globalThis.getComputedStyle(/** @type {Element} */ (node));
|
|
458
458
|
if (type === 'foreColor') {
|
|
459
459
|
return cs.color || '#000000';
|
|
460
460
|
}
|
|
@@ -464,10 +464,10 @@ export class ContextMenu {
|
|
|
464
464
|
|
|
465
465
|
/** Restore selection, apply a color command, then hide the menu. */
|
|
466
466
|
_applyColor(type, color) {
|
|
467
|
-
const editable = this.context.layoutInfo
|
|
467
|
+
const editable = this.context.layoutInfo?.editable;
|
|
468
468
|
if (!editable || !this._savedRange) return;
|
|
469
469
|
editable.focus();
|
|
470
|
-
const sel =
|
|
470
|
+
const sel = globalThis.getSelection();
|
|
471
471
|
sel.removeAllRanges();
|
|
472
472
|
sel.addRange(this._savedRange.cloneRange());
|
|
473
473
|
document.execCommand(type, false, color);
|
|
@@ -482,20 +482,20 @@ export class ContextMenu {
|
|
|
482
482
|
copyFormat() {
|
|
483
483
|
const range = this._savedRange;
|
|
484
484
|
if (!range) return;
|
|
485
|
-
const editable = this.context.layoutInfo
|
|
485
|
+
const editable = this.context.layoutInfo?.editable;
|
|
486
486
|
let node = range.startContainer;
|
|
487
487
|
if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
|
|
488
488
|
if (!node || !editable || !editable.contains(node)) return;
|
|
489
489
|
|
|
490
490
|
// Walk up to collect explicitly-set inline properties from the nearest styled ancestor
|
|
491
|
-
const cs =
|
|
491
|
+
const cs = globalThis.getComputedStyle(/** @type {Element} */ (node));
|
|
492
492
|
|
|
493
493
|
// Detect if an explicit font-family / font-size is set on an element (not just inherited)
|
|
494
494
|
const explicitFontFamily = this._findExplicitStyle(node, editable, 'fontFamily');
|
|
495
495
|
const explicitFontSize = this._findExplicitStyle(node, editable, 'fontSize');
|
|
496
496
|
|
|
497
497
|
this._copiedFormat = {
|
|
498
|
-
bold: parseInt(cs.fontWeight, 10) >= 700,
|
|
498
|
+
bold: Number.parseInt(cs.fontWeight, 10) >= 700,
|
|
499
499
|
italic: cs.fontStyle === 'italic' || cs.fontStyle === 'oblique',
|
|
500
500
|
underline: (cs.textDecorationLine || '').includes('underline'),
|
|
501
501
|
strikethrough: (cs.textDecorationLine || '').includes('line-through'),
|
|
@@ -533,13 +533,13 @@ export class ContextMenu {
|
|
|
533
533
|
pasteFormat() {
|
|
534
534
|
if (!this._copiedFormat || !this._savedRange) return;
|
|
535
535
|
const fmt = this._copiedFormat;
|
|
536
|
-
const editable = this.context.layoutInfo
|
|
536
|
+
const editable = this.context.layoutInfo?.editable;
|
|
537
537
|
if (!editable) return;
|
|
538
538
|
|
|
539
539
|
// CRITICAL: focus the editable FIRST, then restore selection.
|
|
540
540
|
// Calling focus() after addRange() can wipe the selection in Chrome/Firefox.
|
|
541
541
|
editable.focus();
|
|
542
|
-
const sel =
|
|
542
|
+
const sel = globalThis.getSelection();
|
|
543
543
|
sel.removeAllRanges();
|
|
544
544
|
sel.addRange(this._savedRange.cloneRange());
|
|
545
545
|
|
|
@@ -575,14 +575,14 @@ export class ContextMenu {
|
|
|
575
575
|
document.execCommand('fontSize', false, '7');
|
|
576
576
|
// Mark only the NEWLY created font[size="7"] elements.
|
|
577
577
|
editable.querySelectorAll('font[size="7"]').forEach((el) => {
|
|
578
|
-
if (!preExisting.has(el)) el.
|
|
578
|
+
if (!preExisting.has(el)) /** @type {HTMLElement} */ (el).dataset.anTmp = marker;
|
|
579
579
|
});
|
|
580
580
|
editable.querySelectorAll(`[data-an-tmp="${marker}"]`).forEach((el) => {
|
|
581
581
|
const span = document.createElement('span');
|
|
582
582
|
span.style.fontSize = fmt.fontSize;
|
|
583
583
|
el.parentNode.insertBefore(span, el);
|
|
584
584
|
while (el.firstChild) span.appendChild(el.firstChild);
|
|
585
|
-
el.
|
|
585
|
+
el.remove();
|
|
586
586
|
});
|
|
587
587
|
}
|
|
588
588
|
|
|
@@ -592,11 +592,11 @@ export class ContextMenu {
|
|
|
592
592
|
/** Strip all inline formatting from the saved selection. */
|
|
593
593
|
removeFormat() {
|
|
594
594
|
if (!this._savedRange) return;
|
|
595
|
-
const editable = this.context.layoutInfo
|
|
595
|
+
const editable = this.context.layoutInfo?.editable;
|
|
596
596
|
if (!editable) return;
|
|
597
597
|
|
|
598
598
|
editable.focus();
|
|
599
|
-
const sel =
|
|
599
|
+
const sel = globalThis.getSelection();
|
|
600
600
|
sel.removeAllRanges();
|
|
601
601
|
sel.addRange(this._savedRange.cloneRange());
|
|
602
602
|
|
package/src/js/module/Editor.js
CHANGED
|
@@ -61,7 +61,7 @@ export class Editor {
|
|
|
61
61
|
// Refresh toolbar on selection change, scoped to this editor
|
|
62
62
|
const onSelChange = () => {
|
|
63
63
|
if (!this.context._alive) return;
|
|
64
|
-
const sel =
|
|
64
|
+
const sel = globalThis.getSelection();
|
|
65
65
|
if (sel && sel.rangeCount > 0 && editable.contains(sel.anchorNode)) {
|
|
66
66
|
this.context.invoke('toolbar.refresh');
|
|
67
67
|
if (typeof this.options.onSelectionChange === 'function') {
|
|
@@ -85,7 +85,7 @@ export class Editor {
|
|
|
85
85
|
// lands WHERE the user actually clicked (middle, end of text…).
|
|
86
86
|
// keyup : arrow-key navigation may land at <li>[0]; move to start-of-text.
|
|
87
87
|
const fixChecklistCursor = (event) => {
|
|
88
|
-
const sel =
|
|
88
|
+
const sel = globalThis.getSelection();
|
|
89
89
|
if (!sel || !sel.rangeCount) return;
|
|
90
90
|
const r = sel.getRangeAt(0);
|
|
91
91
|
if (!r.collapsed) return;
|
|
@@ -180,7 +180,7 @@ export class Editor {
|
|
|
180
180
|
/** @type {string|null} 'superscript' | 'subscript' | null */
|
|
181
181
|
let _compositionSupSub = null;
|
|
182
182
|
const onCompositionStart = () => {
|
|
183
|
-
const sel =
|
|
183
|
+
const sel = globalThis.getSelection();
|
|
184
184
|
if (!sel || !sel.rangeCount) { _compositionSupSub = null; return; }
|
|
185
185
|
let node = sel.getRangeAt(0).startContainer;
|
|
186
186
|
if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
|
|
@@ -195,13 +195,12 @@ export class Editor {
|
|
|
195
195
|
const tag = _compositionSupSub;
|
|
196
196
|
_compositionSupSub = null;
|
|
197
197
|
if (!tag) return;
|
|
198
|
-
const sel =
|
|
198
|
+
const sel = globalThis.getSelection();
|
|
199
199
|
if (!sel || !sel.rangeCount) return;
|
|
200
200
|
let node = sel.getRangeAt(0).startContainer;
|
|
201
201
|
if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
|
|
202
202
|
const el = /** @type {Element} */ (node);
|
|
203
|
-
const inContext = el
|
|
204
|
-
(tag === 'superscript' ? el.closest('sup') : el.closest('sub'));
|
|
203
|
+
const inContext = tag === 'superscript' ? el?.closest('sup') : el?.closest('sub');
|
|
205
204
|
if (!inContext) {
|
|
206
205
|
// The composed character escaped the sup/sub — re-apply the format.
|
|
207
206
|
document.execCommand(tag);
|
|
@@ -288,7 +287,7 @@ export class Editor {
|
|
|
288
287
|
if (!type.startsWith('insert')) return;
|
|
289
288
|
|
|
290
289
|
const text = this.context.layoutInfo.editable.innerText || '';
|
|
291
|
-
const chars = text.
|
|
290
|
+
const chars = text.replaceAll('\n', '').length;
|
|
292
291
|
|
|
293
292
|
if (maxChars && chars >= maxChars) {
|
|
294
293
|
event.preventDefault();
|
|
@@ -353,7 +352,7 @@ export class Editor {
|
|
|
353
352
|
const editable = this.context.layoutInfo.editable;
|
|
354
353
|
editable.querySelectorAll('figure.an-figure').forEach((fig) => {
|
|
355
354
|
if (!fig.querySelector('img')) {
|
|
356
|
-
fig.
|
|
355
|
+
fig.remove();
|
|
357
356
|
}
|
|
358
357
|
});
|
|
359
358
|
}
|
|
@@ -396,7 +395,7 @@ export class Editor {
|
|
|
396
395
|
*/
|
|
397
396
|
getHTML() {
|
|
398
397
|
// Strip zero-width spaces inserted after icons to allow caret placement.
|
|
399
|
-
const raw = this.context.layoutInfo.editable.innerHTML.
|
|
398
|
+
const raw = this.context.layoutInfo.editable.innerHTML.replaceAll('\u200B', '');
|
|
400
399
|
// Replace any blob: URLs (lightweight DOM references to pasted/dropped images)
|
|
401
400
|
// with their original data URLs so the returned HTML is fully self-contained.
|
|
402
401
|
return this.context.invoke('clipboard.resolveImages', raw) ?? raw;
|
|
@@ -451,7 +450,7 @@ export class Editor {
|
|
|
451
450
|
isEmpty() {
|
|
452
451
|
const text = (this.context.layoutInfo.editable.innerText || '')
|
|
453
452
|
.trim()
|
|
454
|
-
.
|
|
453
|
+
.replaceAll('\u00a0', '');
|
|
455
454
|
const hasMedia = !!this.context.layoutInfo.editable.querySelector('img, video, iframe, table');
|
|
456
455
|
return !text && !hasMedia;
|
|
457
456
|
}
|
|
@@ -557,7 +556,7 @@ export class Editor {
|
|
|
557
556
|
// Auto-detect the programming language when the user formats a code block.
|
|
558
557
|
// Only runs when converting TO <pre> and the block has no language yet.
|
|
559
558
|
if (tagName === 'pre') {
|
|
560
|
-
const sel =
|
|
559
|
+
const sel = globalThis.getSelection();
|
|
561
560
|
if (sel && sel.rangeCount > 0) {
|
|
562
561
|
const container = sel.getRangeAt(0).commonAncestorContainer;
|
|
563
562
|
const pre = /** @type {Element|null} */ (
|
|
@@ -565,7 +564,7 @@ export class Editor {
|
|
|
565
564
|
? /** @type {Element} */ (container).closest('pre')
|
|
566
565
|
: (/** @type {Element|null} */ (container.parentElement))?.closest('pre')
|
|
567
566
|
);
|
|
568
|
-
if (pre &&
|
|
567
|
+
if (pre && !/** @type {HTMLElement} */ (pre).dataset.language) {
|
|
569
568
|
const code = pre.textContent || '';
|
|
570
569
|
const lang = detectLang(code);
|
|
571
570
|
if (lang) {
|
|
@@ -618,7 +617,7 @@ export class Editor {
|
|
|
618
617
|
* @param {boolean} [openInNewTab=false]
|
|
619
618
|
*/
|
|
620
619
|
insertLink(url, text, openInNewTab = false) {
|
|
621
|
-
const sel =
|
|
620
|
+
const sel = globalThis.getSelection();
|
|
622
621
|
if (!sel || sel.rangeCount === 0) return;
|
|
623
622
|
const safeUrl = sanitiseUrl(url);
|
|
624
623
|
if (!safeUrl) return;
|
|
@@ -693,7 +692,7 @@ export class Editor {
|
|
|
693
692
|
// ---------------------------------------------------------------------------
|
|
694
693
|
|
|
695
694
|
_getClosestAnchor() {
|
|
696
|
-
const sel =
|
|
695
|
+
const sel = globalThis.getSelection();
|
|
697
696
|
if (!sel || sel.rangeCount === 0) return null;
|
|
698
697
|
let node = sel.getRangeAt(0).startContainer;
|
|
699
698
|
while (node) {
|
|
@@ -710,10 +709,10 @@ export class Editor {
|
|
|
710
709
|
*/
|
|
711
710
|
_escapeAttr(str) {
|
|
712
711
|
return String(str ?? '')
|
|
713
|
-
.
|
|
714
|
-
.
|
|
715
|
-
.
|
|
716
|
-
.
|
|
712
|
+
.replaceAll('&', '&')
|
|
713
|
+
.replaceAll('"', '"')
|
|
714
|
+
.replaceAll('<', '<')
|
|
715
|
+
.replaceAll('>', '>');
|
|
717
716
|
}
|
|
718
717
|
|
|
719
718
|
// --- delegated to shared sanitise.js ---
|
|
@@ -526,7 +526,7 @@ export class EmojiDialog {
|
|
|
526
526
|
this._disposers.forEach((d) => d());
|
|
527
527
|
this._disposers = [];
|
|
528
528
|
if (this._dialog && this._dialog.parentNode) {
|
|
529
|
-
this._dialog.
|
|
529
|
+
this._dialog.remove();
|
|
530
530
|
}
|
|
531
531
|
this._dialog = null;
|
|
532
532
|
}
|
|
@@ -592,7 +592,7 @@ export class EmojiDialog {
|
|
|
592
592
|
catBar.appendChild(allTab);
|
|
593
593
|
EMOJI_CATS.forEach(({ id, label }) => {
|
|
594
594
|
const tab = createElement('button', { type: 'button', class: 'an-icon-cat', 'data-cat': id });
|
|
595
|
-
tab.textContent =
|
|
595
|
+
tab.textContent = L.categories?.[id] || label;
|
|
596
596
|
catBar.appendChild(tab);
|
|
597
597
|
});
|
|
598
598
|
this._catBar = catBar;
|
|
@@ -685,7 +685,7 @@ export class EmojiDialog {
|
|
|
685
685
|
// 1. Restore selection while dialog is still mounted (same pattern as ImageDialog)
|
|
686
686
|
if (savedRange) savedRange.select();
|
|
687
687
|
|
|
688
|
-
const sel =
|
|
688
|
+
const sel = globalThis.getSelection();
|
|
689
689
|
let range = sel && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
|
|
690
690
|
if (!range) {
|
|
691
691
|
range = document.createRange();
|
|
@@ -703,7 +703,7 @@ export class EmojiDialog {
|
|
|
703
703
|
const _tdAnchor = /** @type {Element|null} */ (_sc.nodeType === 1 ? _sc : _sc.parentElement)
|
|
704
704
|
?.closest('td, th');
|
|
705
705
|
range.deleteContents();
|
|
706
|
-
if (_tdAnchor
|
|
706
|
+
if (_tdAnchor?.isConnected && !_tdAnchor.contains(range.startContainer)) {
|
|
707
707
|
range.setStart(_tdAnchor, 0);
|
|
708
708
|
range.collapse(true);
|
|
709
709
|
}
|
|
@@ -732,7 +732,7 @@ export class EmojiDialog {
|
|
|
732
732
|
if (this._dialog) {
|
|
733
733
|
this._dialog.style.display = 'flex';
|
|
734
734
|
this._removeTrap = trapFocus(this._dialog, () => this._close());
|
|
735
|
-
setTimeout(() => this._searchInput
|
|
735
|
+
setTimeout(() => this._searchInput?.focus(), 50);
|
|
736
736
|
}
|
|
737
737
|
}
|
|
738
738
|
|
|
@@ -61,7 +61,7 @@ export class FindReplace {
|
|
|
61
61
|
this._disposers.forEach((d) => d());
|
|
62
62
|
this._disposers = [];
|
|
63
63
|
if (this._dialog && this._dialog.parentNode) {
|
|
64
|
-
this._dialog.
|
|
64
|
+
this._dialog.remove();
|
|
65
65
|
}
|
|
66
66
|
this._dialog = null;
|
|
67
67
|
}
|
|
@@ -352,13 +352,14 @@ export class FindReplace {
|
|
|
352
352
|
// Cap results to prevent blocking the main thread on very large documents
|
|
353
353
|
const MAX_RESULTS = 500;
|
|
354
354
|
const walker = document.createTreeWalker(root, 0x4 /* NodeFilter.SHOW_TEXT */);
|
|
355
|
-
let node;
|
|
356
|
-
while (
|
|
355
|
+
let node = walker.nextNode();
|
|
356
|
+
while (node && results.length < MAX_RESULTS) {
|
|
357
357
|
re.lastIndex = 0;
|
|
358
358
|
let m;
|
|
359
|
-
while ((m = re.exec(node.textContent)) !== null && results.length < MAX_RESULTS) {
|
|
359
|
+
while ((m = re.exec(/** @type {Text} */ (node).textContent)) !== null && results.length < MAX_RESULTS) {
|
|
360
360
|
results.push({ node: /** @type {Text} */ (node), start: m.index, end: m.index + m[0].length });
|
|
361
361
|
}
|
|
362
|
+
node = walker.nextNode();
|
|
362
363
|
}
|
|
363
364
|
return results;
|
|
364
365
|
}
|
|
@@ -408,7 +409,7 @@ export class FindReplace {
|
|
|
408
409
|
const parent = match.mark.parentNode;
|
|
409
410
|
const textNode = document.createTextNode(replacement);
|
|
410
411
|
parent.insertBefore(textNode, match.mark);
|
|
411
|
-
|
|
412
|
+
match.mark.remove();
|
|
412
413
|
parent.normalize();
|
|
413
414
|
this.context.invoke('editor.afterCommand');
|
|
414
415
|
const savedIndex = this._currentIndex;
|
|
@@ -428,7 +429,7 @@ export class FindReplace {
|
|
|
428
429
|
if (!mark || !mark.parentNode) return;
|
|
429
430
|
const textNode = document.createTextNode(replacement);
|
|
430
431
|
mark.parentNode.insertBefore(textNode, mark);
|
|
431
|
-
mark.
|
|
432
|
+
mark.remove();
|
|
432
433
|
});
|
|
433
434
|
|
|
434
435
|
if (this.context.layoutInfo.editable) {
|
|
@@ -456,7 +457,7 @@ export class FindReplace {
|
|
|
456
457
|
const parent = mark.parentNode;
|
|
457
458
|
if (!parent) return;
|
|
458
459
|
while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
|
|
459
|
-
|
|
460
|
+
mark.remove();
|
|
460
461
|
});
|
|
461
462
|
// Merge adjacent text nodes after unwrapping
|
|
462
463
|
editable.normalize();
|
|
@@ -297,9 +297,7 @@ export class IconDialog {
|
|
|
297
297
|
destroy() {
|
|
298
298
|
this._disposers.forEach((d) => d());
|
|
299
299
|
this._disposers = [];
|
|
300
|
-
|
|
301
|
-
this._dialog.parentNode.removeChild(this._dialog);
|
|
302
|
-
}
|
|
300
|
+
this._dialog?.remove();
|
|
303
301
|
this._dialog = null;
|
|
304
302
|
}
|
|
305
303
|
|
|
@@ -370,7 +368,7 @@ export class IconDialog {
|
|
|
370
368
|
catBar.appendChild(allTab);
|
|
371
369
|
ICON_CATEGORIES.forEach(({ id, label }) => {
|
|
372
370
|
const tab = createElement('button', { type: 'button', class: 'an-icon-cat', 'data-cat': id });
|
|
373
|
-
tab.textContent =
|
|
371
|
+
tab.textContent = L.categories?.[id] || label;
|
|
374
372
|
catBar.appendChild(tab);
|
|
375
373
|
});
|
|
376
374
|
this._catBar = catBar;
|
|
@@ -526,10 +524,10 @@ export class IconDialog {
|
|
|
526
524
|
this._preview.innerHTML = '<span class="an-icon-preview-hint">Select an icon</span>';
|
|
527
525
|
return;
|
|
528
526
|
}
|
|
529
|
-
const cls =
|
|
530
|
-
const size =
|
|
531
|
-
const useColor = this._useColorCb
|
|
532
|
-
const color =
|
|
527
|
+
const cls = this._styleSelect?.value || 'fa-solid';
|
|
528
|
+
const size = this._sizeSelect?.value || '1em';
|
|
529
|
+
const useColor = this._useColorCb?.checked ?? false;
|
|
530
|
+
const color = useColor ? (this._colorInput?.value ?? '') : '';
|
|
533
531
|
const styleAttr = [
|
|
534
532
|
size ? `font-size:${size}` : '',
|
|
535
533
|
color ? `color:${color}` : '',
|
|
@@ -549,10 +547,10 @@ export class IconDialog {
|
|
|
549
547
|
_onInsert() {
|
|
550
548
|
if (!this._selectedIcon) return;
|
|
551
549
|
|
|
552
|
-
const cls =
|
|
553
|
-
const size =
|
|
554
|
-
const useColor = this._useColorCb
|
|
555
|
-
const color =
|
|
550
|
+
const cls = this._styleSelect?.value || 'fa-solid';
|
|
551
|
+
const size = this._sizeSelect?.value || '';
|
|
552
|
+
const useColor = this._useColorCb?.checked ?? false;
|
|
553
|
+
const color = useColor ? (this._colorInput?.value ?? '') : '';
|
|
556
554
|
const styleParts = [
|
|
557
555
|
size ? `font-size:${size}` : '',
|
|
558
556
|
color ? `color:${color}` : '',
|
|
@@ -572,8 +570,8 @@ export class IconDialog {
|
|
|
572
570
|
if (savedRange) savedRange.select();
|
|
573
571
|
|
|
574
572
|
// 2. Get the now-live range from the selection
|
|
575
|
-
const sel =
|
|
576
|
-
let range = sel
|
|
573
|
+
const sel = globalThis.getSelection();
|
|
574
|
+
let range = (sel?.rangeCount ?? 0) > 0 ? sel.getRangeAt(0) : null;
|
|
577
575
|
if (!range) {
|
|
578
576
|
range = document.createRange();
|
|
579
577
|
range.selectNodeContents(editable);
|
|
@@ -639,7 +637,7 @@ export class IconDialog {
|
|
|
639
637
|
if (this._dialog) {
|
|
640
638
|
this._dialog.style.display = 'flex';
|
|
641
639
|
this._removeTrap = trapFocus(this._dialog, () => this._close());
|
|
642
|
-
setTimeout(() => this._searchInput
|
|
640
|
+
setTimeout(() => this._searchInput?.focus(), 50);
|
|
643
641
|
}
|
|
644
642
|
}
|
|
645
643
|
|
|
@@ -66,7 +66,7 @@ function drawCropToCanvas(img, naturalRect, renderW, renderH) {
|
|
|
66
66
|
if (
|
|
67
67
|
img.src.startsWith('data:') ||
|
|
68
68
|
img.src.startsWith('blob:') ||
|
|
69
|
-
img.src.startsWith(
|
|
69
|
+
img.src.startsWith(globalThis.location.origin)
|
|
70
70
|
) {
|
|
71
71
|
tryDraw(img);
|
|
72
72
|
return;
|
|
@@ -340,8 +340,8 @@ export class ImageCropOverlay {
|
|
|
340
340
|
this._cropBox.style.height = `${h}px`;
|
|
341
341
|
|
|
342
342
|
// Scrim cutout via clip-path polygon (punches a transparent hole)
|
|
343
|
-
const vw =
|
|
344
|
-
const vh =
|
|
343
|
+
const vw = globalThis.innerWidth;
|
|
344
|
+
const vh = globalThis.innerHeight;
|
|
345
345
|
// Outer rect → inner rectangle (counterclockwise) = hole
|
|
346
346
|
this._scrim.style.clipPath = [
|
|
347
347
|
`polygon(`,
|
|
@@ -367,7 +367,7 @@ export class ImageCropOverlay {
|
|
|
367
367
|
// Position toolbar below crop box (or above if near bottom)
|
|
368
368
|
const margin = 8;
|
|
369
369
|
let tbTop = y + h + margin;
|
|
370
|
-
if (tbTop + 40 >
|
|
370
|
+
if (tbTop + 40 > globalThis.innerHeight - margin) tbTop = y - 40 - margin;
|
|
371
371
|
this._toolbar.style.left = `${x}px`;
|
|
372
372
|
this._toolbar.style.top = `${tbTop}px`;
|
|
373
373
|
}
|
|
@@ -514,7 +514,7 @@ export class ImageCropOverlay {
|
|
|
514
514
|
}
|
|
515
515
|
|
|
516
516
|
// Determine output format: preserve JPEG, fall back to PNG
|
|
517
|
-
const fmt =
|
|
517
|
+
const fmt = /^data:image\/(jpe?g)/i.exec(img.src) ? 'image/jpeg' : 'image/png';
|
|
518
518
|
const quality = fmt === 'image/jpeg' ? 0.92 : undefined;
|
|
519
519
|
const newSrc = canvas.toDataURL(fmt, quality);
|
|
520
520
|
|
|
@@ -551,7 +551,7 @@ export class ImageCropOverlay {
|
|
|
551
551
|
].join(';');
|
|
552
552
|
banner.textContent = msg;
|
|
553
553
|
document.body.appendChild(banner);
|
|
554
|
-
setTimeout(() => {
|
|
554
|
+
setTimeout(() => { banner.remove(); }, 4000);
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
/**
|
|
@@ -563,7 +563,7 @@ export class ImageCropOverlay {
|
|
|
563
563
|
this._disposers = [];
|
|
564
564
|
|
|
565
565
|
[this._scrim, this._cropBox, this._toolbar].forEach((el) => {
|
|
566
|
-
|
|
566
|
+
el?.remove();
|
|
567
567
|
});
|
|
568
568
|
|
|
569
569
|
this._scrim = null;
|