autumnnote 1.6.5 → 1.6.7
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 +89 -75
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +87 -73
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/Context.js +4 -4
- package/src/js/core/dom.js +3 -3
- package/src/js/core/markdown.js +2 -2
- package/src/js/core/range.js +1 -1
- package/src/js/editing/History.js +2 -2
- package/src/js/editing/Style.js +7 -5
- package/src/js/editing/Typing.js +2 -2
- package/src/js/index.js +1 -1
- package/src/js/module/AutoSaveRestore.js +3 -2
- package/src/js/module/BubbleToolbar.js +1 -1
- package/src/js/module/Clipboard.js +4 -4
- package/src/js/module/CodeTooltip.js +1 -1
- package/src/js/module/ContextMenu.js +14 -12
- package/src/js/module/Editor.js +6 -6
- package/src/js/module/EmojiDialog.js +1 -1
- package/src/js/module/FindReplace.js +6 -7
- package/src/js/module/IconDialog.js +1 -1
- package/src/js/module/ImageCropOverlay.js +1 -0
- package/src/js/module/ImageDialog.js +3 -3
- package/src/js/module/ImageResizer.js +2 -2
- package/src/js/module/ImageTooltip.js +5 -3
- package/src/js/module/LinkDialog.js +1 -1
- package/src/js/module/LinkTooltip.js +3 -3
- package/src/js/module/Mention.js +2 -2
- package/src/js/module/TableTooltip.js +50 -32
- package/src/js/module/Toolbar.js +13 -10
- package/src/js/module/VideoDialog.js +5 -2
- package/src/js/renderer.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autumnnote",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"description": "WYSIWYG rich-text editor built with vanilla JavaScript — zero dependencies, no jQuery. Dark mode, @mention, markdown shortcuts, bubble toolbar. React and Vue 3 wrappers included.",
|
|
5
5
|
"main": "dist/autumnnote.umd.js",
|
|
6
6
|
"module": "dist/autumnnote.es.js",
|
package/src/js/Context.js
CHANGED
|
@@ -89,13 +89,13 @@ export class Context {
|
|
|
89
89
|
|
|
90
90
|
// 3. Attach toolbar/statusbar to container
|
|
91
91
|
const toolbar = this._modules.get('toolbar');
|
|
92
|
-
if (toolbar
|
|
92
|
+
if (toolbar?.el) {
|
|
93
93
|
container.insertBefore(toolbar.el, editable);
|
|
94
94
|
this.layoutInfo.toolbar = toolbar.el;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
const statusbar = this._modules.get('statusbar');
|
|
98
|
-
if (statusbar
|
|
98
|
+
if (statusbar?.el) {
|
|
99
99
|
container.appendChild(statusbar.el);
|
|
100
100
|
this.layoutInfo.statusbar = statusbar.el;
|
|
101
101
|
}
|
|
@@ -256,7 +256,7 @@ export class Context {
|
|
|
256
256
|
const key = this.options.autoSaveKey;
|
|
257
257
|
localStorage.setItem(key, this.getHTML());
|
|
258
258
|
localStorage.setItem(key + ':asrmeta', JSON.stringify({ savedAt: Date.now() }));
|
|
259
|
-
} catch (_) {}
|
|
259
|
+
} catch (_) { void _; }
|
|
260
260
|
});
|
|
261
261
|
this._disposers.push(d4);
|
|
262
262
|
}
|
|
@@ -551,7 +551,7 @@ export class Context {
|
|
|
551
551
|
|
|
552
552
|
for (const { plugin } of this._plugins.values()) {
|
|
553
553
|
if (typeof plugin.uninstall === 'function') {
|
|
554
|
-
try { plugin.uninstall(this); } catch (_) {}
|
|
554
|
+
try { plugin.uninstall(this); } catch (_) { void _; }
|
|
555
555
|
}
|
|
556
556
|
}
|
|
557
557
|
this._plugins.clear();
|
package/src/js/core/dom.js
CHANGED
|
@@ -11,9 +11,9 @@ export const ELEMENT_NODE = 1;
|
|
|
11
11
|
export const TEXT_NODE = 3;
|
|
12
12
|
|
|
13
13
|
/** @param {Node} node */
|
|
14
|
-
export const isElement = (node) => node
|
|
14
|
+
export const isElement = (node) => node?.nodeType === ELEMENT_NODE;
|
|
15
15
|
/** @param {Node} node */
|
|
16
|
-
export const isText = (node) => node
|
|
16
|
+
export const isText = (node) => node?.nodeType === TEXT_NODE;
|
|
17
17
|
/** @param {Node} node */
|
|
18
18
|
export const isVoid = (node) => isElement(node) && /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i.test(node.nodeName);
|
|
19
19
|
/** @param {Node} node */
|
|
@@ -147,7 +147,7 @@ export function createElement(tag, attrs = {}, childNodes = []) {
|
|
|
147
147
|
* @param {Node} node
|
|
148
148
|
*/
|
|
149
149
|
export function remove(node) {
|
|
150
|
-
if (node
|
|
150
|
+
if (node?.parentNode) {
|
|
151
151
|
/** @type {ChildNode} */ (node).remove();
|
|
152
152
|
}
|
|
153
153
|
}
|
package/src/js/core/markdown.js
CHANGED
|
@@ -67,7 +67,7 @@ function _domToMd(node, depth = 0) {
|
|
|
67
67
|
}
|
|
68
68
|
case 'pre': {
|
|
69
69
|
const codeEl = el.querySelector('code');
|
|
70
|
-
const langMatch = /language-(\S+)/.exec(
|
|
70
|
+
const langMatch = /language-(\S+)/.exec(codeEl?.className || '');
|
|
71
71
|
const lang = langMatch ? langMatch[1] : '';
|
|
72
72
|
const content = (codeEl || el).textContent || '';
|
|
73
73
|
return `\n\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
|
|
@@ -105,7 +105,7 @@ function _domToMd(node, depth = 0) {
|
|
|
105
105
|
const rows = Array.from(el.querySelectorAll('tr'));
|
|
106
106
|
if (!rows.length) return inner();
|
|
107
107
|
const cellTexts = rows.map((tr) =>
|
|
108
|
-
Array.from(tr.querySelectorAll('th, td')).map((c) => c.textContent.trim().replaceAll('|',
|
|
108
|
+
Array.from(tr.querySelectorAll('th, td')).map((c) => c.textContent.trim().replaceAll('|', String.raw`\|`)),
|
|
109
109
|
);
|
|
110
110
|
const cols = Math.max(...cellTexts.map((r) => r.length));
|
|
111
111
|
const padRow = (row) => { const r = [...row]; while (r.length < cols) r.push(''); return r; };
|
package/src/js/core/range.js
CHANGED
|
@@ -101,14 +101,14 @@ export class History {
|
|
|
101
101
|
sel.removeAllRanges();
|
|
102
102
|
sel.addRange(range);
|
|
103
103
|
} catch (_) {
|
|
104
|
-
//
|
|
104
|
+
void _; // detached node — fall back to placing cursor at start of editable
|
|
105
105
|
try {
|
|
106
106
|
const fb = document.createRange();
|
|
107
107
|
fb.setStart(this.editable, 0);
|
|
108
108
|
fb.collapse(true);
|
|
109
109
|
const s = globalThis.getSelection();
|
|
110
110
|
if (s) { s.removeAllRanges(); s.addRange(fb); }
|
|
111
|
-
} catch (_2) { /* fully give up */ }
|
|
111
|
+
} catch (_2) { void _2; /* fully give up */ }
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
|
package/src/js/editing/Style.js
CHANGED
|
@@ -130,7 +130,7 @@ export function fontSize(size, editable = document) {
|
|
|
130
130
|
// Only applies when there IS an active selection (sel.rangeCount > 0); when
|
|
131
131
|
// there is no selection at all (e.g. jsdom unit tests) fall through to the
|
|
132
132
|
// execCommand path so the font-replacement logic still runs.
|
|
133
|
-
if (wasCollapsed && sel
|
|
133
|
+
if (wasCollapsed && sel?.rangeCount > 0) {
|
|
134
134
|
try {
|
|
135
135
|
const range = sel.getRangeAt(0);
|
|
136
136
|
const span = document.createElement('span');
|
|
@@ -143,7 +143,7 @@ export function fontSize(size, editable = document) {
|
|
|
143
143
|
nr.collapse(true);
|
|
144
144
|
sel.removeAllRanges();
|
|
145
145
|
sel.addRange(nr);
|
|
146
|
-
} catch (_) { /* ignore range errors on unusual DOM structures */ }
|
|
146
|
+
} catch (_) { void _; /* ignore range errors on unusual DOM structures */ }
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
149
|
|
|
@@ -176,7 +176,7 @@ export function fontSize(size, editable = document) {
|
|
|
176
176
|
nr.setEnd(endNode, endNode.nodeType === Node.TEXT_NODE ? endNode.textContent.length : endNode.childNodes.length);
|
|
177
177
|
sel.removeAllRanges();
|
|
178
178
|
sel.addRange(nr);
|
|
179
|
-
} catch (_) { /* ignore range errors on unusual DOM structures */ }
|
|
179
|
+
} catch (_) { void _; /* ignore range errors on unusual DOM structures */ }
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -432,7 +432,9 @@ export function toggleInlineCode(_editable) {
|
|
|
432
432
|
const lastMoved = movedChildren.at(-1);
|
|
433
433
|
const nr = document.createRange();
|
|
434
434
|
// Use the (possibly merged) live node if still in the DOM.
|
|
435
|
-
const anchorNode =
|
|
435
|
+
const anchorNode = firstMoved.parentNode === parent
|
|
436
|
+
? firstMoved
|
|
437
|
+
: (prevSibling ? prevSibling.nextSibling : parent.firstChild);
|
|
436
438
|
if (anchorNode) {
|
|
437
439
|
nr.setStart(anchorNode, 0);
|
|
438
440
|
const endAnchor = (lastMoved.parentNode === parent) ? lastMoved : anchorNode;
|
|
@@ -440,7 +442,7 @@ export function toggleInlineCode(_editable) {
|
|
|
440
442
|
sel.removeAllRanges();
|
|
441
443
|
sel.addRange(nr);
|
|
442
444
|
}
|
|
443
|
-
} catch (_) { /* ignore */ }
|
|
445
|
+
} catch (_) { void _; /* ignore */ }
|
|
444
446
|
}
|
|
445
447
|
} else {
|
|
446
448
|
if (range.collapsed) return;
|
package/src/js/editing/Typing.js
CHANGED
|
@@ -128,7 +128,7 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
128
128
|
const icon = textNode.nextSibling;
|
|
129
129
|
const after = icon.nextSibling;
|
|
130
130
|
event.preventDefault();
|
|
131
|
-
if (after
|
|
131
|
+
if (after?.nodeType === Node.TEXT_NODE) {
|
|
132
132
|
const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
|
|
133
133
|
return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
|
|
134
134
|
}
|
|
@@ -142,7 +142,7 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
142
142
|
const icon = textNode.nextSibling.nextSibling;
|
|
143
143
|
const after = icon.nextSibling;
|
|
144
144
|
event.preventDefault();
|
|
145
|
-
if (after
|
|
145
|
+
if (after?.nodeType === Node.TEXT_NODE) {
|
|
146
146
|
const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
|
|
147
147
|
return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
|
|
148
148
|
}
|
package/src/js/index.js
CHANGED
|
@@ -141,7 +141,7 @@ const AutumnNote = {
|
|
|
141
141
|
registerButton(btnDef) { registerButton(btnDef); return this; },
|
|
142
142
|
|
|
143
143
|
/** Library version */
|
|
144
|
-
version: '1.6.
|
|
144
|
+
version: '1.6.7',
|
|
145
145
|
};
|
|
146
146
|
|
|
147
147
|
// ---------------------------------------------------------------------------
|
|
@@ -30,6 +30,7 @@ export class AutoSaveRestore {
|
|
|
30
30
|
saved = localStorage.getItem(key);
|
|
31
31
|
meta = JSON.parse(localStorage.getItem(metaKey) || '{}');
|
|
32
32
|
} catch (_) {
|
|
33
|
+
void _;
|
|
33
34
|
return this;
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -39,7 +40,7 @@ export class AutoSaveRestore {
|
|
|
39
40
|
if (timeout > 0) {
|
|
40
41
|
const ageMs = Date.now() - (meta.savedAt || 0);
|
|
41
42
|
if (ageMs > timeout * 86400000) {
|
|
42
|
-
try { localStorage.removeItem(key); localStorage.removeItem(metaKey); } catch (_) {}
|
|
43
|
+
try { localStorage.removeItem(key); localStorage.removeItem(metaKey); } catch (_) { void _; }
|
|
43
44
|
return this;
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -113,7 +114,7 @@ export class AutoSaveRestore {
|
|
|
113
114
|
try {
|
|
114
115
|
localStorage.removeItem(key);
|
|
115
116
|
localStorage.removeItem(key + ':asrmeta');
|
|
116
|
-
} catch (_) {}
|
|
117
|
+
} catch (_) { void _; }
|
|
117
118
|
this._removeBanner();
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -311,7 +311,7 @@ export class BubbleToolbar {
|
|
|
311
311
|
editable.focus();
|
|
312
312
|
const sel = globalThis.getSelection();
|
|
313
313
|
sel.removeAllRanges();
|
|
314
|
-
try { sel.addRange(this._savedRange.cloneRange()); } catch (_) { return; }
|
|
314
|
+
try { sel.addRange(this._savedRange.cloneRange()); } catch (_) { void _; return; }
|
|
315
315
|
|
|
316
316
|
// Firefox does not support 'hiliteColor'; fall back to 'backColor'
|
|
317
317
|
const cmd = type === 'hiliteColor' ? 'hiliteColor' : type;
|
|
@@ -64,7 +64,7 @@ export class Clipboard {
|
|
|
64
64
|
* @param {Node} node
|
|
65
65
|
*/
|
|
66
66
|
_revokeRemovedBlobs(node) {
|
|
67
|
-
if (!this._blobRegistry
|
|
67
|
+
if (!this._blobRegistry?.size) return;
|
|
68
68
|
const imgs = /** @type {Element[]} */ ([]);
|
|
69
69
|
if (node.nodeName === 'IMG') {
|
|
70
70
|
imgs.push(/** @type {Element} */ (node));
|
|
@@ -267,7 +267,7 @@ export class Clipboard {
|
|
|
267
267
|
|
|
268
268
|
_onDrop(event) {
|
|
269
269
|
const dt = event.dataTransfer;
|
|
270
|
-
if (!dt
|
|
270
|
+
if (!dt?.files?.length) return;
|
|
271
271
|
|
|
272
272
|
const imageFiles = Array.from(dt.files).filter((f) => f.type.startsWith('image/'));
|
|
273
273
|
if (imageFiles.length === 0) return;
|
|
@@ -302,7 +302,7 @@ export class Clipboard {
|
|
|
302
302
|
const UNSUPPORTED = new Set(['image/tiff', 'image/x-tiff', 'image/bmp', 'image/x-bmp', 'image/x-ms-bmp']);
|
|
303
303
|
const maxBytes = (this.options.maxImageSize || 5) * 1024 * 1024;
|
|
304
304
|
files.forEach((file) => {
|
|
305
|
-
if (!file
|
|
305
|
+
if (!file?.type?.startsWith('image/')) return;
|
|
306
306
|
if (UNSUPPORTED.has(file.type)) {
|
|
307
307
|
const message = `Image format "${file.type}" is not supported for display in web browsers. Please convert to PNG, JPEG, or WebP first.`;
|
|
308
308
|
this.context.triggerEvent('imageError', { file, message });
|
|
@@ -339,7 +339,7 @@ export class Clipboard {
|
|
|
339
339
|
* @returns {string}
|
|
340
340
|
*/
|
|
341
341
|
resolveImages(html) {
|
|
342
|
-
if (!this._blobRegistry
|
|
342
|
+
if (!this._blobRegistry?.size) return html;
|
|
343
343
|
return html.replace(/blob:[^"'> \t\n\r]*/g, (url) => this._blobRegistry.get(url) || url);
|
|
344
344
|
}
|
|
345
345
|
|
|
@@ -273,7 +273,7 @@ export class CodeTooltip {
|
|
|
273
273
|
ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0';
|
|
274
274
|
document.body.appendChild(ta);
|
|
275
275
|
ta.select();
|
|
276
|
-
try { document.execCommand('copy'); this._flashCopied(); } catch (_) {}
|
|
276
|
+
try { document.execCommand('copy'); this._flashCopied(); } catch (_) { void _; }
|
|
277
277
|
ta.remove();
|
|
278
278
|
}
|
|
279
279
|
}
|
|
@@ -114,7 +114,7 @@ export class ContextMenu {
|
|
|
114
114
|
constructor(context) {
|
|
115
115
|
this.context = context;
|
|
116
116
|
this.options = context.options || {};
|
|
117
|
-
this._items =
|
|
117
|
+
this._items = this.options.contextMenu?.items || defaultItems;
|
|
118
118
|
this.el = null;
|
|
119
119
|
this._disposers = [];
|
|
120
120
|
this._menuDisposers = []; // disposers for dynamically-rendered menu buttons
|
|
@@ -136,17 +136,19 @@ export class ContextMenu {
|
|
|
136
136
|
this._disposers.push(on(editable, 'contextmenu', (e) => this._onContextMenu(e)));
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
this._disposers.push(
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
this._disposers.push(
|
|
140
|
+
on(document, 'click', (e) => this._maybeHide(e)),
|
|
141
|
+
on(document, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Escape') this.hide(); }),
|
|
142
|
+
on(globalThis, 'scroll', () => this.hide(), { passive: true }),
|
|
143
|
+
);
|
|
142
144
|
|
|
143
145
|
return this;
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
destroy() {
|
|
147
|
-
this._menuDisposers.forEach((d) => { try { d(); } catch (_e) {} });
|
|
149
|
+
this._menuDisposers.forEach((d) => { try { d(); } catch (_e) { void _e; } });
|
|
148
150
|
this._menuDisposers = [];
|
|
149
|
-
this._disposers.forEach((d) => { try { d(); } catch (_e) {} });
|
|
151
|
+
this._disposers.forEach((d) => { try { d(); } catch (_e) { void _e; } });
|
|
150
152
|
this._disposers = [];
|
|
151
153
|
if (this.el) this.el.remove();
|
|
152
154
|
this.el = null;
|
|
@@ -379,7 +381,7 @@ export class ContextMenu {
|
|
|
379
381
|
event.preventDefault();
|
|
380
382
|
|
|
381
383
|
const winSel = globalThis.getSelection();
|
|
382
|
-
this._savedRange = (winSel
|
|
384
|
+
this._savedRange = (winSel?.rangeCount > 0) ? winSel.getRangeAt(0).cloneRange() : null;
|
|
383
385
|
this._renderItems(this._items);
|
|
384
386
|
|
|
385
387
|
// Open below the selected text so the selection stays visible.
|
|
@@ -395,7 +397,7 @@ export class ContextMenu {
|
|
|
395
397
|
// Keep X at click position (feels natural); Y just below the selection.
|
|
396
398
|
openY = selRect.bottom + 4;
|
|
397
399
|
}
|
|
398
|
-
} catch (_) {}
|
|
400
|
+
} catch (_) { void _; }
|
|
399
401
|
}
|
|
400
402
|
|
|
401
403
|
this._lastX = openX;
|
|
@@ -418,8 +420,8 @@ export class ContextMenu {
|
|
|
418
420
|
|
|
419
421
|
_reposition(x, y) {
|
|
420
422
|
if (!this.el) return;
|
|
421
|
-
const rx = x
|
|
422
|
-
const ry = y
|
|
423
|
+
const rx = x === undefined ? this._lastX : x;
|
|
424
|
+
const ry = y === undefined ? this._lastY : y;
|
|
423
425
|
const w = this.el.offsetWidth;
|
|
424
426
|
const h = this.el.offsetHeight;
|
|
425
427
|
let left = rx;
|
|
@@ -485,7 +487,7 @@ export class ContextMenu {
|
|
|
485
487
|
const editable = this.context.layoutInfo?.editable;
|
|
486
488
|
let node = range.startContainer;
|
|
487
489
|
if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
|
|
488
|
-
if (!node || !editable
|
|
490
|
+
if (!node || !editable?.contains(node)) return;
|
|
489
491
|
|
|
490
492
|
// Walk up to collect explicitly-set inline properties from the nearest styled ancestor
|
|
491
493
|
const cs = globalThis.getComputedStyle(/** @type {Element} */ (node));
|
|
@@ -510,7 +512,7 @@ export class ContextMenu {
|
|
|
510
512
|
_findExplicitStyle(node, boundary, prop) {
|
|
511
513
|
let el = node;
|
|
512
514
|
while (el && el !== boundary && el !== document.body) {
|
|
513
|
-
if (el.style
|
|
515
|
+
if (el.style?.[prop]) return el.style[prop];
|
|
514
516
|
// also check font element attributes
|
|
515
517
|
if (el.nodeName === 'FONT') {
|
|
516
518
|
if (prop === 'fontFamily' && el.getAttribute('face')) return el.getAttribute('face');
|
package/src/js/module/Editor.js
CHANGED
|
@@ -62,7 +62,7 @@ export class Editor {
|
|
|
62
62
|
const onSelChange = () => {
|
|
63
63
|
if (!this.context._alive) return;
|
|
64
64
|
const sel = globalThis.getSelection();
|
|
65
|
-
if (sel
|
|
65
|
+
if (sel?.rangeCount > 0 && editable.contains(sel.anchorNode)) {
|
|
66
66
|
this.context.invoke('toolbar.refresh');
|
|
67
67
|
if (typeof this.options.onSelectionChange === 'function') {
|
|
68
68
|
this.options.onSelectionChange(this.context);
|
|
@@ -557,7 +557,7 @@ export class Editor {
|
|
|
557
557
|
// Only runs when converting TO <pre> and the block has no language yet.
|
|
558
558
|
if (tagName === 'pre') {
|
|
559
559
|
const sel = globalThis.getSelection();
|
|
560
|
-
if (sel
|
|
560
|
+
if (sel?.rangeCount > 0) {
|
|
561
561
|
const container = sel.getRangeAt(0).commonAncestorContainer;
|
|
562
562
|
const pre = /** @type {Element|null} */ (
|
|
563
563
|
container.nodeType === 1
|
|
@@ -623,10 +623,7 @@ export class Editor {
|
|
|
623
623
|
if (!safeUrl) return;
|
|
624
624
|
|
|
625
625
|
const hasText = sel.toString().trim().length > 0;
|
|
626
|
-
if (
|
|
627
|
-
const displayText = this._escapeAttr(text || safeUrl);
|
|
628
|
-
Style.execCommand('insertHTML', `<a href="${this._escapeAttr(safeUrl)}"${openInNewTab ? ' target="_blank" rel="noopener noreferrer"' : ''}>${displayText}</a>`);
|
|
629
|
-
} else {
|
|
626
|
+
if (hasText) {
|
|
630
627
|
Style.execCommand('createLink', safeUrl);
|
|
631
628
|
if (openInNewTab) {
|
|
632
629
|
const link = this._getClosestAnchor();
|
|
@@ -635,6 +632,9 @@ export class Editor {
|
|
|
635
632
|
/** @type {Element} */ (link).setAttribute('rel', 'noopener noreferrer');
|
|
636
633
|
}
|
|
637
634
|
}
|
|
635
|
+
} else {
|
|
636
|
+
const displayText = this._escapeAttr(text || safeUrl);
|
|
637
|
+
Style.execCommand('insertHTML', `<a href="${this._escapeAttr(safeUrl)}"${openInNewTab ? ' target="_blank" rel="noopener noreferrer"' : ''}>${displayText}</a>`);
|
|
638
638
|
}
|
|
639
639
|
this.afterCommand();
|
|
640
640
|
}
|
|
@@ -666,7 +666,7 @@ export class EmojiDialog extends BaseDialog {
|
|
|
666
666
|
if (savedRange) savedRange.select();
|
|
667
667
|
|
|
668
668
|
const sel = globalThis.getSelection();
|
|
669
|
-
let range = sel
|
|
669
|
+
let range = sel?.rangeCount > 0 ? sel.getRangeAt(0) : null;
|
|
670
670
|
if (!range) {
|
|
671
671
|
range = document.createRange();
|
|
672
672
|
range.selectNodeContents(editable);
|
|
@@ -298,8 +298,7 @@ export class FindReplace extends BaseDialog {
|
|
|
298
298
|
range.surroundContents(mark);
|
|
299
299
|
this._matches.push({ mark });
|
|
300
300
|
} catch (_) {
|
|
301
|
-
// surroundContents fails when the range crosses element boundaries
|
|
302
|
-
// This can happen with <br> inside matched text — skip safely.
|
|
301
|
+
void _; // surroundContents fails when the range crosses element boundaries
|
|
303
302
|
this._matches.push({ mark: null });
|
|
304
303
|
}
|
|
305
304
|
}
|
|
@@ -311,7 +310,7 @@ export class FindReplace extends BaseDialog {
|
|
|
311
310
|
if (this._matches.length === 0) return;
|
|
312
311
|
|
|
313
312
|
// Highlight the first (current) match
|
|
314
|
-
if (this._matches[0]
|
|
313
|
+
if (this._matches[0]?.mark) {
|
|
315
314
|
this._matches[0].mark.className = 'an-highlight an-highlight-current';
|
|
316
315
|
this._matches[0].mark.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
317
316
|
}
|
|
@@ -328,7 +327,7 @@ export class FindReplace extends BaseDialog {
|
|
|
328
327
|
// Reuse compiled regex when query and case-sensitivity haven't changed
|
|
329
328
|
if (this._lastQuery !== query || this._lastCaseSensitive !== this._caseSensitive) {
|
|
330
329
|
const flags = this._caseSensitive ? 'g' : 'gi';
|
|
331
|
-
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g,
|
|
330
|
+
const escaped = query.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
332
331
|
this._queryRegex = new RegExp(escaped, flags);
|
|
333
332
|
this._lastQuery = query;
|
|
334
333
|
this._lastCaseSensitive = this._caseSensitive;
|
|
@@ -370,7 +369,7 @@ export class FindReplace extends BaseDialog {
|
|
|
370
369
|
|
|
371
370
|
_scrollToMatch(index) {
|
|
372
371
|
const match = this._matches[index];
|
|
373
|
-
if (!match
|
|
372
|
+
if (!match?.mark) return;
|
|
374
373
|
// Update CSS classes
|
|
375
374
|
this._matches.forEach((m, i) => {
|
|
376
375
|
if (m.mark) {
|
|
@@ -389,7 +388,7 @@ export class FindReplace extends BaseDialog {
|
|
|
389
388
|
_replace() {
|
|
390
389
|
if (this._matches.length === 0 || this._currentIndex < 0) return;
|
|
391
390
|
const match = this._matches[this._currentIndex];
|
|
392
|
-
if (!match
|
|
391
|
+
if (!match?.mark?.parentNode) return;
|
|
393
392
|
|
|
394
393
|
const replacement = this._replaceInput ? this._replaceInput.value : '';
|
|
395
394
|
const parent = match.mark.parentNode;
|
|
@@ -412,7 +411,7 @@ export class FindReplace extends BaseDialog {
|
|
|
412
411
|
const replacement = this._replaceInput ? this._replaceInput.value : '';
|
|
413
412
|
|
|
414
413
|
this._matches.forEach(({ mark }) => {
|
|
415
|
-
if (!mark
|
|
414
|
+
if (!mark?.parentNode) return;
|
|
416
415
|
const textNode = document.createTextNode(replacement);
|
|
417
416
|
mark.parentNode.insertBefore(textNode, mark);
|
|
418
417
|
mark.remove();
|
|
@@ -570,7 +570,7 @@ export class IconDialog extends BaseDialog {
|
|
|
570
570
|
const _tdAnchor = /** @type {Element|null} */ (_sc.nodeType === 1 ? _sc : _sc.parentElement)
|
|
571
571
|
?.closest('td, th');
|
|
572
572
|
range.deleteContents();
|
|
573
|
-
if (_tdAnchor
|
|
573
|
+
if (_tdAnchor?.isConnected && !_tdAnchor.contains(range.startContainer)) {
|
|
574
574
|
range.setStart(_tdAnchor, 0);
|
|
575
575
|
range.collapse(true);
|
|
576
576
|
}
|
|
@@ -107,8 +107,8 @@ export class ImageDialog extends BaseDialog {
|
|
|
107
107
|
// ---------------------------------------------------------------------------
|
|
108
108
|
|
|
109
109
|
_onFileChange() {
|
|
110
|
-
const file = this._fileInput
|
|
111
|
-
if (!file
|
|
110
|
+
const file = this._fileInput?.files?.[0];
|
|
111
|
+
if (!file?.type?.startsWith('image/')) return;
|
|
112
112
|
|
|
113
113
|
// C2: Only allow web-displayable formats. TIFF, BMP, RAW and similar
|
|
114
114
|
// formats are not rendered by browsers — reject them with a clear message.
|
|
@@ -146,7 +146,7 @@ export class ImageDialog extends BaseDialog {
|
|
|
146
146
|
_onInsert() {
|
|
147
147
|
const src = this._urlInput.value.trim();
|
|
148
148
|
const alt = this._altInput.value.trim();
|
|
149
|
-
const alignRadio = this._alignRow
|
|
149
|
+
const alignRadio = this._alignRow?.querySelector('input[name="an-img-align"]:checked');
|
|
150
150
|
const align = alignRadio ? /** @type {HTMLInputElement} */ (alignRadio).value : '';
|
|
151
151
|
|
|
152
152
|
if (!src) {
|
|
@@ -75,7 +75,7 @@ export class ImageResizer {
|
|
|
75
75
|
this._positionRaf = null;
|
|
76
76
|
}
|
|
77
77
|
this._deselect();
|
|
78
|
-
if (this._overlay
|
|
78
|
+
if (this._overlay?.parentNode) {
|
|
79
79
|
this._overlay.remove();
|
|
80
80
|
}
|
|
81
81
|
this._overlay = null;
|
|
@@ -140,7 +140,7 @@ export class ImageResizer {
|
|
|
140
140
|
_onDocClick(e) {
|
|
141
141
|
if (!this._activeImg) return;
|
|
142
142
|
if (e.target === this._activeImg) return;
|
|
143
|
-
if (this._overlay
|
|
143
|
+
if (this._overlay?.contains(e.target)) return;
|
|
144
144
|
// Don't deselect while interacting with the context menu
|
|
145
145
|
if (e.target.closest('.an-contextmenu')) return;
|
|
146
146
|
this._deselect();
|
|
@@ -284,9 +284,11 @@ export class ImageTooltip {
|
|
|
284
284
|
const next = (prev + delta + 360) % 360; // normalise to [0, 360)
|
|
285
285
|
// Preserve any other transform functions (e.g. scale), replace only rotate()
|
|
286
286
|
const cleaned = current.replace(/rotate\(-?[\d.]+deg\)/, '').trim();
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
287
|
+
if (cleaned) {
|
|
288
|
+
img.style.transform = `${cleaned} rotate(${next}deg)`;
|
|
289
|
+
} else {
|
|
290
|
+
img.style.transform = next === 0 ? '' : `rotate(${next}deg)`;
|
|
291
|
+
}
|
|
290
292
|
this.context.invoke('editor.afterCommand');
|
|
291
293
|
this.context.invoke('imageResizer.updateOverlay');
|
|
292
294
|
requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
|
|
@@ -88,7 +88,7 @@ export class LinkDialog extends BaseDialog {
|
|
|
88
88
|
// Try to find currently selected anchor
|
|
89
89
|
const sel = globalThis.getSelection();
|
|
90
90
|
let anchor = null;
|
|
91
|
-
if (sel
|
|
91
|
+
if (sel?.rangeCount > 0) {
|
|
92
92
|
let node = sel.getRangeAt(0).startContainer;
|
|
93
93
|
while (node) {
|
|
94
94
|
if (node.nodeName === 'A') { anchor = node; break; }
|
|
@@ -57,7 +57,7 @@ export class LinkTooltip {
|
|
|
57
57
|
this._clearTimers();
|
|
58
58
|
this._disposers.forEach((d) => d());
|
|
59
59
|
this._disposers = [];
|
|
60
|
-
if (this._el
|
|
60
|
+
if (this._el?.parentNode) this._el.remove();
|
|
61
61
|
this._el = null;
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -178,7 +178,7 @@ export class LinkTooltip {
|
|
|
178
178
|
_truncateUrl(url) {
|
|
179
179
|
try {
|
|
180
180
|
const u = new URL(url);
|
|
181
|
-
const display = u.host + (u.pathname
|
|
181
|
+
const display = u.host + (u.pathname === '/' ? '' : u.pathname);
|
|
182
182
|
return display.length > 48 ? display.slice(0, 48) + '…' : display;
|
|
183
183
|
} catch {
|
|
184
184
|
return url.length > 48 ? url.slice(0, 48) + '…' : url;
|
|
@@ -213,7 +213,7 @@ export class LinkTooltip {
|
|
|
213
213
|
// Brief visual feedback
|
|
214
214
|
if (this._copyBtn) {
|
|
215
215
|
this._copyBtn.classList.add('an-link-tooltip-btn--copied');
|
|
216
|
-
setTimeout(() => this._copyBtn
|
|
216
|
+
setTimeout(() => this._copyBtn?.classList.remove('an-link-tooltip-btn--copied'), 1000);
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
219
|
|
package/src/js/module/Mention.js
CHANGED
|
@@ -205,7 +205,7 @@ export class Mention {
|
|
|
205
205
|
this._caretRect = candidate;
|
|
206
206
|
return;
|
|
207
207
|
}
|
|
208
|
-
} catch (_) {}
|
|
208
|
+
} catch (_) { void _; }
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
// Fallback: use getClientRects() on the current collapsed selection
|
|
@@ -223,7 +223,7 @@ export class Mention {
|
|
|
223
223
|
|
|
224
224
|
_getQueryAtCursor() {
|
|
225
225
|
const sel = globalThis.getSelection();
|
|
226
|
-
if (!sel
|
|
226
|
+
if (!sel?.rangeCount) return null;
|
|
227
227
|
const range = sel.getRangeAt(0);
|
|
228
228
|
if (!range.collapsed) return null;
|
|
229
229
|
|