autumnnote 1.6.3 → 1.6.6

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.3",
3
+ "version": "1.6.6",
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();
@@ -13,7 +13,7 @@
13
13
  * @returns {string|null}
14
14
  */
15
15
  export function detectLang(code) {
16
- if (!code || !code.trim()) return null;
16
+ if (!code?.trim()) return null;
17
17
  const s = code.trim();
18
18
 
19
19
  // ── TypeScript ─────────────────────────────────────────────────────────────
@@ -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
  }
@@ -305,11 +305,9 @@ export function trapFocus(container, onEscape) {
305
305
  e.preventDefault();
306
306
  /** @type {HTMLElement} */ (last).focus();
307
307
  }
308
- } else {
309
- if (document.activeElement === last) {
310
- e.preventDefault();
311
- /** @type {HTMLElement} */ (first).focus();
312
- }
308
+ } else if (document.activeElement === last) {
309
+ e.preventDefault();
310
+ /** @type {HTMLElement} */ (first).focus();
313
311
  }
314
312
  };
315
313
 
@@ -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
 
@@ -41,7 +41,7 @@ export const italic = () => execCommand('italic');
41
41
  */
42
42
  export function underline() {
43
43
  const sel = globalThis.getSelection();
44
- if (!sel || !sel.rangeCount) return;
44
+ if (!sel?.rangeCount) return;
45
45
  let container = sel.getRangeAt(0).commonAncestorContainer;
46
46
  if (container.nodeType === 3) container = container.parentElement;
47
47
  // Check if we're inside a <u> (DOM truth), to guard against unreliable queryCommandState
@@ -65,7 +65,7 @@ export function underline() {
65
65
  */
66
66
  export function strikethrough() {
67
67
  const sel = globalThis.getSelection();
68
- if (!sel || !sel.rangeCount) return;
68
+ if (!sel?.rangeCount) return;
69
69
  // Use startContainer for consistent detection across collapsed and range
70
70
  // selections — commonAncestorContainer can miss ancestor <s>/<strike> tags
71
71
  // when the selection spans across nested inline elements.
@@ -120,7 +120,7 @@ export const fontName = (name) => execCommand('fontName', name);
120
120
  */
121
121
  export function fontSize(size, editable = document) {
122
122
  const sel = globalThis.getSelection();
123
- const wasCollapsed = !sel || !sel.rangeCount || sel.getRangeAt(0).collapsed;
123
+ const wasCollapsed = !sel?.rangeCount || sel.getRangeAt(0).collapsed;
124
124
 
125
125
  // B-I-3/4: For a collapsed (caret) selection the browser's execCommand
126
126
  // 'fontSize' leaves an internal "pending" state of size-7 (=48px) instead of
@@ -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
 
@@ -223,7 +223,7 @@ export const indent = () => execCommand('indent');
223
223
  */
224
224
  export function outdent() {
225
225
  const sel = globalThis.getSelection();
226
- if (sel && sel.rangeCount) {
226
+ if (sel?.rangeCount) {
227
227
  let container = sel.getRangeAt(0).commonAncestorContainer;
228
228
  if (container.nodeType === 3) container = container.parentElement;
229
229
  const checkLi = /** @type {Element|null} */ (container)?.closest('.an-checklist li');
@@ -286,7 +286,7 @@ function _checklistItemToP(checkLi) {
286
286
  try {
287
287
  const nr = document.createRange();
288
288
  const firstChild = p.firstChild;
289
- nr.setStart(firstChild && firstChild.nodeType === 3 ? firstChild : p, 0);
289
+ nr.setStart(firstChild?.nodeType === 3 ? firstChild : p, 0);
290
290
  nr.collapse(true);
291
291
  const s = globalThis.getSelection();
292
292
  if (s) { s.removeAllRanges(); s.addRange(nr); }
@@ -406,7 +406,7 @@ export function currentStyle(editable) {
406
406
  */
407
407
  export function toggleInlineCode(_editable) {
408
408
  const sel = globalThis.getSelection();
409
- if (!sel || !sel.rangeCount) return;
409
+ if (!sel?.rangeCount) return;
410
410
  const range = sel.getRangeAt(0);
411
411
  let container = range.commonAncestorContainer;
412
412
  if (container.nodeType === 3) container = container.parentElement;
@@ -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;
@@ -477,7 +479,7 @@ export function toggleInlineCode(_editable) {
477
479
  */
478
480
  export function isInlineCode() {
479
481
  const sel = globalThis.getSelection();
480
- if (!sel || !sel.rangeCount) return false;
482
+ if (!sel?.rangeCount) return false;
481
483
  let sc = sel.getRangeAt(0).startContainer;
482
484
  if (sc.nodeType === 3) sc = sc.parentElement;
483
485
  const code = /** @type {Element|null} */ (sc)?.closest('code');
@@ -504,7 +506,7 @@ export function isInlineCode() {
504
506
  */
505
507
  export function toggleChecklist() {
506
508
  const sel = globalThis.getSelection();
507
- if (!sel || !sel.rangeCount) return;
509
+ if (!sel?.rangeCount) return;
508
510
  const range = sel.getRangeAt(0);
509
511
  let container = range.commonAncestorContainer;
510
512
  if (container.nodeType === 3) container = container.parentElement;
@@ -552,7 +554,7 @@ export function toggleChecklist() {
552
554
  // and convert it into a single checklist item.
553
555
  const BLOCK_TAGS = new Set(['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI']);
554
556
  let block = /** @type {Element|null} */ (container);
555
- while (block && block.parentNode && !BLOCK_TAGS.has(block.tagName)) {
557
+ while (block?.parentNode && !BLOCK_TAGS.has(block.tagName)) {
556
558
  block = /** @type {Element|null} */ (block.parentNode);
557
559
  }
558
560
  // Fallback: if no block element found (e.g. cursor directly in editable root), use the
@@ -674,7 +676,7 @@ export function toggleChecklist() {
674
676
  */
675
677
  export function isInChecklist() {
676
678
  const sel = globalThis.getSelection();
677
- if (!sel || !sel.rangeCount) return false;
679
+ if (!sel?.rangeCount) return false;
678
680
  let container = sel.getRangeAt(0).commonAncestorContainer;
679
681
  if (container.nodeType === 3) container = container.parentElement;
680
682
  return !!(/** @type {Element|null} */ (container)?.closest('.an-checklist li'));
@@ -68,7 +68,7 @@ export function handleKeydown(event, editable, options = {}) {
68
68
  // (not a text node), causing the next Backspace to miss Cases A/B and
69
69
  // requiring an extra keypress when two icons are adjacent.
70
70
  const nr = document.createRange();
71
- if (prevNode && prevNode.nodeType === Node.TEXT_NODE) {
71
+ if (prevNode?.nodeType === Node.TEXT_NODE) {
72
72
  nr.setStart(prevNode, prevNode.textContent.length);
73
73
  } else if (prevNode) {
74
74
  nr.setStartAfter(prevNode);
@@ -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
  }
@@ -168,7 +168,7 @@ export function handleKeydown(event, editable, options = {}) {
168
168
  if (isFAIcon(next)) {
169
169
  const after = next.nextSibling;
170
170
  event.preventDefault();
171
- if (after && after.nodeType === Node.TEXT_NODE) {
171
+ if (after?.nodeType === Node.TEXT_NODE) {
172
172
  const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
173
173
  return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
174
174
  }
@@ -178,7 +178,7 @@ export function handleKeydown(event, editable, options = {}) {
178
178
  const icon = next.nextSibling;
179
179
  const after = icon.nextSibling;
180
180
  event.preventDefault();
181
- if (after && after.nodeType === Node.TEXT_NODE) {
181
+ if (after?.nodeType === Node.TEXT_NODE) {
182
182
  const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
183
183
  return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
184
184
  }
@@ -341,11 +341,11 @@ export function handleKeydown(event, editable, options = {}) {
341
341
  // may normalise it to element-level, placing the caret before the
342
342
  // absolutely-positioned checkbox. \u200B is stripped by getHTML().
343
343
  let cursorNode = newLi.childNodes[1]; // first child after checkbox
344
- if (!cursorNode || cursorNode.nodeType !== Node.TEXT_NODE) {
344
+ if (cursorNode?.nodeType !== Node.TEXT_NODE) {
345
345
  cursorNode = document.createTextNode('\u200B');
346
346
  newLi.appendChild(cursorNode);
347
347
  }
348
- checkLi.insertAdjacentElement('afterend', newLi);
348
+ checkLi.after(newLi);
349
349
 
350
350
  const nr = document.createRange();
351
351
  nr.setStart(cursorNode, 0);
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.3',
144
+ version: '1.6.6',
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
 
@@ -51,7 +51,7 @@ export class BaseDialog {
51
51
  if (this._dialog) {
52
52
  this._dialog.style.display = 'flex';
53
53
  this._removeTrap = trapFocus(this._dialog, () => this._close());
54
- setTimeout(() => this._firstInput && this._firstInput.focus(), 50);
54
+ setTimeout(() => this._firstInput?.focus(), 50);
55
55
  }
56
56
  }
57
57
 
@@ -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;
@@ -389,7 +389,7 @@ export class BubbleToolbar {
389
389
  if (!this._btnCache) return;
390
390
  this._btnCache.forEach((btn) => {
391
391
  const activeFn = _ACTIVE[/** @type {HTMLElement} */ (btn).dataset.name];
392
- btn.classList.toggle('an-active', !!(activeFn && activeFn()));
392
+ btn.classList.toggle('an-active', !!(activeFn?.()));
393
393
  });
394
394
  }
395
395
 
@@ -397,7 +397,7 @@ export class BubbleToolbar {
397
397
  _syncColorStrips() {
398
398
  if (!this._el) return;
399
399
  const sel = globalThis.getSelection();
400
- if (!sel || !sel.rangeCount) return;
400
+ if (!sel?.rangeCount) return;
401
401
  let node = sel.getRangeAt(0).startContainer;
402
402
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
403
403
  if (!node) return;
@@ -102,10 +102,10 @@ export const underlineBtn = btn('underline', 'underline', 'Underline (Ctrl+U)',
102
102
  // consistent behaviour across both collapsed and range selections.
103
103
  if (document.queryCommandState('underline')) return true;
104
104
  const sel = globalThis.getSelection();
105
- if (!sel || !sel.rangeCount) return false;
105
+ if (!sel?.rangeCount) return false;
106
106
  let sc = sel.getRangeAt(0).startContainer;
107
107
  if (sc.nodeType === 3) sc = sc.parentElement;
108
- return !!(sc && /** @type {Element} */ (sc).closest('u'));
108
+ return !!(/** @type {Element} */ (sc)?.closest('u'));
109
109
  });
110
110
  export const strikeBtn = btn('strikethrough', 'strikethrough', 'Strikethrough', () => Style.strikethrough(), () => document.queryCommandState('strikeThrough'));
111
111
  export const superscriptBtn = btn('superscript', 'superscript', 'Superscript', () => Style.superscript(), () => document.queryCommandState('superscript'));
@@ -180,11 +180,11 @@ export const fontSizeBtn = {
180
180
  getValue: (ctx) => {
181
181
  try {
182
182
  const sel = globalThis.getSelection();
183
- if (sel && sel.rangeCount) {
183
+ if (sel?.rangeCount) {
184
184
  let el = /** @type {Element|null} */ (sel.getRangeAt(0).startContainer);
185
- if (el && el.nodeType === 3) el = el.parentElement;
186
- while (el && el.nodeType === 1 && !/** @type {HTMLElement} */ (el).style.fontSize) el = el.parentElement;
187
- const size = (el && /** @type {HTMLElement} */ (el).style.fontSize) ? /** @type {HTMLElement} */ (el).style.fontSize : '';
185
+ if (el?.nodeType === 3) el = el.parentElement;
186
+ while (el?.nodeType === 1 && !/** @type {HTMLElement} */ (el).style.fontSize) el = el.parentElement;
187
+ const size = /** @type {HTMLElement} */ (el)?.style.fontSize || '';
188
188
  if (size) return size;
189
189
  }
190
190
  // Fallback: read the base font size from the editable element itself
@@ -281,10 +281,10 @@ export const lineHeightBtn = {
281
281
  getValue: () => {
282
282
  try {
283
283
  const sel = globalThis.getSelection();
284
- if (!sel || !sel.rangeCount) return '';
284
+ if (!sel?.rangeCount) return '';
285
285
  const BLOCKS = new Set(['P','DIV','H1','H2','H3','H4','H5','H6','LI','BLOCKQUOTE','PRE','TD','TH']);
286
286
  let el = /** @type {Element|null} */ (sel.getRangeAt(0).startContainer);
287
- if (el && el.nodeType === 3) el = el.parentElement;
287
+ if (el?.nodeType === 3) el = el.parentElement;
288
288
  while (el && !BLOCKS.has(/** @type {Element} */ (el).tagName)) el = el.parentElement;
289
289
  if (!el) return '';
290
290
  return /** @type {HTMLElement} */ (el).style.lineHeight || getComputedStyle(/** @type {Element} */ (el)).lineHeight || '';
@@ -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);
@@ -86,7 +86,7 @@ export class Editor {
86
86
  // keyup : arrow-key navigation may land at <li>[0]; move to start-of-text.
87
87
  const fixChecklistCursor = (event) => {
88
88
  const sel = globalThis.getSelection();
89
- if (!sel || !sel.rangeCount) return;
89
+ if (!sel?.rangeCount) return;
90
90
  const r = sel.getRangeAt(0);
91
91
  if (!r.collapsed) return;
92
92
  const sc = r.startContainer;
@@ -102,7 +102,7 @@ export class Editor {
102
102
 
103
103
  // For mouse events: ask the browser where the pointer landed so the
104
104
  // cursor respects the actual click position inside the text.
105
- if (event && event.type === 'mouseup') {
105
+ if (event?.type === 'mouseup') {
106
106
  let caret = null;
107
107
  if (document.caretRangeFromPoint) {
108
108
  caret = document.caretRangeFromPoint(event.clientX, event.clientY);
@@ -181,7 +181,7 @@ export class Editor {
181
181
  let _compositionSupSub = null;
182
182
  const onCompositionStart = () => {
183
183
  const sel = globalThis.getSelection();
184
- if (!sel || !sel.rangeCount) { _compositionSupSub = null; return; }
184
+ if (!sel?.rangeCount) { _compositionSupSub = null; return; }
185
185
  let node = sel.getRangeAt(0).startContainer;
186
186
  if (node.nodeType === Node.TEXT_NODE) node = node.parentElement;
187
187
  if (node) {
@@ -196,7 +196,7 @@ export class Editor {
196
196
  _compositionSupSub = null;
197
197
  if (!tag) return;
198
198
  const sel = globalThis.getSelection();
199
- if (!sel || !sel.rangeCount) return;
199
+ if (!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);
@@ -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);