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.
Files changed (49) hide show
  1. package/README.md +4 -4
  2. package/dist/autumnnote.css +0 -2
  3. package/dist/autumnnote.es.js +468 -552
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +461 -546
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +21 -3
  8. package/src/js/Context.js +21 -16
  9. package/src/js/core/dom.js +9 -9
  10. package/src/js/core/env.js +1 -1
  11. package/src/js/core/func.js +1 -1
  12. package/src/js/core/lists.js +1 -1
  13. package/src/js/core/markdown.js +18 -18
  14. package/src/js/core/range.js +6 -6
  15. package/src/js/editing/History.js +4 -4
  16. package/src/js/editing/Style.js +32 -32
  17. package/src/js/editing/Table.js +2 -2
  18. package/src/js/editing/Typing.js +15 -15
  19. package/src/js/index.js +1 -1
  20. package/src/js/module/AutoSaveRestore.js +2 -4
  21. package/src/js/module/BaseDialog.js +126 -0
  22. package/src/js/module/BubbleToolbar.js +25 -25
  23. package/src/js/module/Buttons.js +4 -4
  24. package/src/js/module/Clipboard.js +12 -13
  25. package/src/js/module/CodeTooltip.js +14 -16
  26. package/src/js/module/Codeview.js +3 -5
  27. package/src/js/module/ContextMenu.js +27 -27
  28. package/src/js/module/Editor.js +17 -18
  29. package/src/js/module/EmojiDialog.js +5 -5
  30. package/src/js/module/FindReplace.js +8 -7
  31. package/src/js/module/IconDialog.js +13 -15
  32. package/src/js/module/ImageCropOverlay.js +7 -7
  33. package/src/js/module/ImageDialog.js +12 -82
  34. package/src/js/module/ImageResizer.js +4 -4
  35. package/src/js/module/ImageTooltip.js +14 -14
  36. package/src/js/module/LinkDialog.js +13 -79
  37. package/src/js/module/LinkTooltip.js +12 -12
  38. package/src/js/module/MarkdownShortcuts.js +11 -14
  39. package/src/js/module/Mention.js +8 -10
  40. package/src/js/module/Placeholder.js +1 -1
  41. package/src/js/module/ShortcutsDialog.js +2 -4
  42. package/src/js/module/Statusbar.js +3 -5
  43. package/src/js/module/TableTooltip.js +30 -32
  44. package/src/js/module/Toolbar.js +21 -21
  45. package/src/js/module/VideoDialog.js +17 -87
  46. package/src/js/module/VideoResizer.js +5 -7
  47. package/src/js/module/VideoTooltip.js +7 -7
  48. package/src/js/renderer.js +1 -1
  49. 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
- if (this._textarea && this._textarea.parentNode) {
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.parentNode.removeChild(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 (/^<\//.test(stripped)) indent = Math.max(0, indent - 1);
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 && ctx.layoutInfo.editable;
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 && this.context.layoutInfo.editable;
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(window, 'scroll', () => this.hide(), { passive: true }));
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 && this.el.parentNode) this.el.parentNode.removeChild(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 && this.context.layoutInfo.editable;
337
+ const editable = this.context.layoutInfo?.editable;
338
338
  if (editable && this._savedRange) {
339
339
  editable.focus();
340
- const sel = window.getSelection();
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 && this.context.layoutInfo.editable;
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 = window.getSelection();
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 > window.innerWidth - 8) left = window.innerWidth - w - 8;
428
+ if (left + w > globalThis.innerWidth - 8) left = globalThis.innerWidth - w - 8;
429
429
  if (left < 8) left = 8;
430
- if (top + h > window.innerHeight - 8) top = window.innerHeight - h - 8;
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 = window.getComputedStyle(/** @type {Element} */ (node));
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 && this.context.layoutInfo.editable;
467
+ const editable = this.context.layoutInfo?.editable;
468
468
  if (!editable || !this._savedRange) return;
469
469
  editable.focus();
470
- const sel = window.getSelection();
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 && this.context.layoutInfo.editable;
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 = window.getComputedStyle(/** @type {Element} */ (node));
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 && this.context.layoutInfo.editable;
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 = window.getSelection();
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.setAttribute('data-an-tmp', marker);
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.parentNode.removeChild(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 && this.context.layoutInfo.editable;
595
+ const editable = this.context.layoutInfo?.editable;
596
596
  if (!editable) return;
597
597
 
598
598
  editable.focus();
599
- const sel = window.getSelection();
599
+ const sel = globalThis.getSelection();
600
600
  sel.removeAllRanges();
601
601
  sel.addRange(this._savedRange.cloneRange());
602
602
 
@@ -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 = window.getSelection();
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 = window.getSelection();
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 = window.getSelection();
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 = window.getSelection();
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.replace(/\n/g, '').length;
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.parentNode.removeChild(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.replace(/\u200B/g, '');
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
- .replace(/\u00a0/g, '');
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 = window.getSelection();
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 && !pre.getAttribute('data-language')) {
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 = window.getSelection();
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 = window.getSelection();
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
- .replace(/&/g, '&amp;')
714
- .replace(/"/g, '&quot;')
715
- .replace(/</g, '&lt;')
716
- .replace(/>/g, '&gt;');
712
+ .replaceAll('&', '&amp;')
713
+ .replaceAll('"', '&quot;')
714
+ .replaceAll('<', '&lt;')
715
+ .replaceAll('>', '&gt;');
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.parentNode.removeChild(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 = (L.categories && L.categories[id]) || label;
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 = window.getSelection();
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 && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
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 && this._searchInput.focus(), 50);
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.parentNode.removeChild(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 ((node = walker.nextNode()) && results.length < MAX_RESULTS) {
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
- parent.removeChild(match.mark);
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.parentNode.removeChild(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
- parent.removeChild(mark);
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
- if (this._dialog && this._dialog.parentNode) {
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 = (L.categories && L.categories[id]) || label;
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 = (this._styleSelect && this._styleSelect.value) || 'fa-solid';
530
- const size = (this._sizeSelect && this._sizeSelect.value) || '1em';
531
- const useColor = this._useColorCb ? this._useColorCb.checked : false;
532
- const color = (useColor && this._colorInput) ? this._colorInput.value : '';
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 = (this._styleSelect && this._styleSelect.value) || 'fa-solid';
553
- const size = (this._sizeSelect && this._sizeSelect.value) || '';
554
- const useColor = this._useColorCb ? this._useColorCb.checked : false;
555
- const color = (useColor && this._colorInput) ? this._colorInput.value : '';
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 = window.getSelection();
576
- let range = sel && sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
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 && this._searchInput.focus(), 50);
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(window.location.origin)
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 = window.innerWidth;
344
- const vh = window.innerHeight;
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 > window.innerHeight - margin) tbTop = y - 40 - margin;
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 = img.src.match(/^data:image\/(jpe?g)/i) ? 'image/jpeg' : 'image/png';
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(() => { if (banner.parentNode) banner.parentNode.removeChild(banner); }, 4000);
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
- if (el && el.parentNode) el.parentNode.removeChild(el);
566
+ el?.remove();
567
567
  });
568
568
 
569
569
  this._scrim = null;