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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autumnnote",
3
- "version": "1.6.5",
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 && toolbar.el) {
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 && statusbar.el) {
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();
@@ -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 && node.nodeType === ELEMENT_NODE;
14
+ export const isElement = (node) => node?.nodeType === ELEMENT_NODE;
15
15
  /** @param {Node} node */
16
- export const isText = (node) => node && node.nodeType === TEXT_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 && node.parentNode) {
150
+ if (node?.parentNode) {
151
151
  /** @type {ChildNode} */ (node).remove();
152
152
  }
153
153
  }
@@ -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((codeEl && codeEl.className) || '');
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; };
@@ -35,7 +35,7 @@ export class WrappedRange {
35
35
  range.setStart(this.sc, this.so);
36
36
  range.setEnd(this.ec, this.eo);
37
37
  } catch (_e) {
38
- // Guard against detached nodes
38
+ void _e; // guard against detached nodes
39
39
  }
40
40
  return range;
41
41
  }
@@ -101,14 +101,14 @@ export class History {
101
101
  sel.removeAllRanges();
102
102
  sel.addRange(range);
103
103
  } catch (_) {
104
- // Detached node — fall back to placing cursor at start of editable
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
 
@@ -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 && sel.rangeCount > 0) {
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 = (firstMoved.parentNode === parent) ? firstMoved : (prevSibling ? prevSibling.nextSibling : parent.firstChild);
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;
@@ -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 && after.nodeType === Node.TEXT_NODE) {
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 && after.nodeType === Node.TEXT_NODE) {
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.5',
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 || !this._blobRegistry.size) return;
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 || !dt.files || dt.files.length === 0) return;
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 || !file.type.startsWith('image/')) return;
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 || !this._blobRegistry.size) return html;
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 = (this.options.contextMenu && this.options.contextMenu.items) || defaultItems;
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(on(document, 'click', (e) => this._maybeHide(e)));
140
- this._disposers.push(on(document, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Escape') this.hide(); }));
141
- this._disposers.push(on(globalThis, 'scroll', () => this.hide(), { passive: true }));
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 && winSel.rangeCount > 0) ? winSel.getRangeAt(0).cloneRange() : null;
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 !== undefined ? x : this._lastX;
422
- const ry = y !== undefined ? y : this._lastY;
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 || !editable.contains(node)) return;
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 && el.style[prop]) return el.style[prop];
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');
@@ -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 && sel.rangeCount > 0 && editable.contains(sel.anchorNode)) {
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 && sel.rangeCount > 0) {
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 (!hasText) {
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 && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
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] && this._matches[0].mark) {
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 || !match.mark) return;
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 || !match.mark || !match.mark.parentNode) return;
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 || !mark.parentNode) return;
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 && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
573
+ if (_tdAnchor?.isConnected && !_tdAnchor.contains(range.startContainer)) {
574
574
  range.setStart(_tdAnchor, 0);
575
575
  range.collapse(true);
576
576
  }
@@ -58,6 +58,7 @@ function drawCropToCanvas(img, naturalRect, renderW, renderH) {
58
58
  ctx.getImageData(0, 0, 1, 1);
59
59
  resolve(canvas);
60
60
  } catch (_) {
61
+ void _; // canvas tainted — resolve with null
61
62
  resolve(null);
62
63
  }
63
64
  };
@@ -107,8 +107,8 @@ export class ImageDialog extends BaseDialog {
107
107
  // ---------------------------------------------------------------------------
108
108
 
109
109
  _onFileChange() {
110
- const file = this._fileInput && this._fileInput.files && this._fileInput.files[0];
111
- if (!file || !file.type.startsWith('image/')) return;
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 && this._alignRow.querySelector('input[name="an-img-align"]:checked');
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 && this._overlay.parentNode) {
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 && this._overlay.contains(e.target)) return;
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
- img.style.transform = cleaned
288
- ? `${cleaned} rotate(${next}deg)`
289
- : next === 0 ? '' : `rotate(${next}deg)`;
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 && sel.rangeCount > 0) {
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 && this._el.parentNode) this._el.remove();
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 !== '/' ? 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 && this._copyBtn.classList.remove('an-link-tooltip-btn--copied'), 1000);
216
+ setTimeout(() => this._copyBtn?.classList.remove('an-link-tooltip-btn--copied'), 1000);
217
217
  }
218
218
  }
219
219
 
@@ -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 || !sel.rangeCount) return null;
226
+ if (!sel?.rangeCount) return null;
227
227
  const range = sel.getRangeAt(0);
228
228
  if (!range.collapsed) return null;
229
229