autumnnote 1.6.0 → 1.6.1

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 (47) hide show
  1. package/README.md +4 -4
  2. package/dist/autumnnote.css +0 -2
  3. package/dist/autumnnote.es.js +348 -342
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +341 -336
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +21 -3
  8. package/src/js/Context.js +16 -14
  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/module/AutoSaveRestore.js +2 -4
  20. package/src/js/module/BubbleToolbar.js +25 -25
  21. package/src/js/module/Buttons.js +4 -4
  22. package/src/js/module/Clipboard.js +12 -13
  23. package/src/js/module/CodeTooltip.js +14 -16
  24. package/src/js/module/Codeview.js +3 -5
  25. package/src/js/module/ContextMenu.js +27 -27
  26. package/src/js/module/Editor.js +17 -18
  27. package/src/js/module/EmojiDialog.js +5 -5
  28. package/src/js/module/FindReplace.js +8 -7
  29. package/src/js/module/IconDialog.js +13 -15
  30. package/src/js/module/ImageCropOverlay.js +7 -7
  31. package/src/js/module/ImageDialog.js +1 -1
  32. package/src/js/module/ImageResizer.js +4 -4
  33. package/src/js/module/ImageTooltip.js +14 -14
  34. package/src/js/module/LinkDialog.js +2 -2
  35. package/src/js/module/LinkTooltip.js +12 -12
  36. package/src/js/module/MarkdownShortcuts.js +11 -14
  37. package/src/js/module/Mention.js +8 -10
  38. package/src/js/module/Placeholder.js +1 -1
  39. package/src/js/module/ShortcutsDialog.js +2 -4
  40. package/src/js/module/Statusbar.js +3 -5
  41. package/src/js/module/TableTooltip.js +30 -32
  42. package/src/js/module/Toolbar.js +21 -21
  43. package/src/js/module/VideoDialog.js +8 -8
  44. package/src/js/module/VideoResizer.js +5 -7
  45. package/src/js/module/VideoTooltip.js +7 -7
  46. package/src/js/renderer.js +1 -1
  47. package/src/styles/autumnnote.scss +0 -3
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "autumnnote",
3
- "version": "1.6.0",
4
- "description": "Fast. Lightweight. Reliable. Efficient. A modern WYSIWYG editor built with vanilla JavaScript, no jQuery required.",
3
+ "version": "1.6.1",
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",
7
7
  "types": "types/index.d.ts",
@@ -58,7 +58,25 @@
58
58
  "dark-mode",
59
59
  "i18n",
60
60
  "typescript",
61
- "esm"
61
+ "esm",
62
+ "javascript-wysiwyg",
63
+ "wysiwyg-vanilla",
64
+ "froala-alternative",
65
+ "ckeditor-alternative",
66
+ "zero-dependencies",
67
+ "react-wysiwyg",
68
+ "vue-wysiwyg",
69
+ "prosemirror-alternative",
70
+ "trix-alternative",
71
+ "slate-alternative",
72
+ "vue3",
73
+ "quill",
74
+ "tinymce",
75
+ "react",
76
+ "vue",
77
+ "plugin-api",
78
+ "rtl",
79
+ "autosave"
62
80
  ],
63
81
  "homepage": "https://cmm-cmm.github.io/Autumn-Note/",
64
82
  "repository": {
package/src/js/Context.js CHANGED
@@ -477,21 +477,18 @@ export class Context {
477
477
  a.style.display = 'none';
478
478
  document.body.appendChild(a);
479
479
  a.click();
480
- document.body.removeChild(a);
480
+ a.remove();
481
481
  URL.revokeObjectURL(url);
482
482
  }
483
483
 
484
484
  /**
485
- * Opens the editor content in a new window and triggers the browser print dialog.
485
+ * Opens the editor content in a new globalThis and triggers the browser print dialog.
486
486
  * @param {string} [title='']
487
487
  */
488
488
  print(title = '') {
489
489
  const content = this.getHTML();
490
- const safeTitle = (title || '').replace(/</g, '&lt;').replace(/>/g, '&gt;');
491
- const w = window.open('', '_blank');
492
- if (!w) return; // popup blocked by browser
493
- w.document.write(
494
- '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">' +
490
+ const safeTitle = (title || '').replace(/[<>&"']/g, (c) => `&#${c.charCodeAt(0)};`);
491
+ const markup = '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">' +
495
492
  `<title>${safeTitle}</title>` +
496
493
  '<style>' +
497
494
  'body{font-family:system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;font-size:14px;line-height:1.6;padding:20mm;color:#111827;}' +
@@ -502,10 +499,15 @@ export class Context {
502
499
  'pre{background:#f3f4f6;padding:.75em 1em;border-radius:4px;overflow-x:auto;}' +
503
500
  'table{border-collapse:collapse;}td,th{border:1px solid #d1d5db;padding:4px 8px;}' +
504
501
  '</style>' +
505
- `</head><body>${content}</body></html>`,
506
- );
507
- w.document.close();
508
- w.onload = () => { w.print(); };
502
+ `</head><body>${content}</body></html>`;
503
+ const blob = new Blob([markup], { type: 'text/html' });
504
+ const url = URL.createObjectURL(blob);
505
+ const w = globalThis.open(url, '_blank');
506
+ if (!w) { URL.revokeObjectURL(url); return; } // popup blocked by browser
507
+ w.addEventListener('load', () => {
508
+ w.print();
509
+ URL.revokeObjectURL(url);
510
+ });
509
511
  }
510
512
 
511
513
  /**
@@ -555,11 +557,11 @@ export class Context {
555
557
  this._disposers = [];
556
558
 
557
559
  const container = this.layoutInfo.container;
558
- const wasDark = container && container.classList.contains('an-theme-dark');
559
- if (container && container.parentNode) {
560
+ const wasDark = container?.classList.contains('an-theme-dark');
561
+ if (container?.parentNode) {
560
562
  // Restore original element
561
563
  this.targetEl.style.display = '';
562
- container.parentNode.removeChild(container);
564
+ container.remove();
563
565
  }
564
566
  // If this was a dark editor and no other dark containers remain, clean up body
565
567
  if (wasDark && !document.querySelector('.an-container.an-theme-dark')) {
@@ -148,7 +148,7 @@ export function createElement(tag, attrs = {}, childNodes = []) {
148
148
  */
149
149
  export function remove(node) {
150
150
  if (node && node.parentNode) {
151
- node.parentNode.removeChild(node);
151
+ /** @type {ChildNode} */ (node).remove();
152
152
  }
153
153
  }
154
154
 
@@ -162,7 +162,7 @@ export function unwrap(node) {
162
162
  while (node.firstChild) {
163
163
  parent.insertBefore(node.firstChild, node);
164
164
  }
165
- parent.removeChild(node);
165
+ /** @type {ChildNode} */ (node).remove();
166
166
  }
167
167
 
168
168
  /**
@@ -238,7 +238,7 @@ export function placeCaret(el) {
238
238
  const range = document.createRange();
239
239
  range.selectNodeContents(el);
240
240
  range.collapse(false);
241
- const sel = window.getSelection();
241
+ const sel = globalThis.getSelection();
242
242
  if (sel) {
243
243
  sel.removeAllRanges();
244
244
  sel.addRange(range);
@@ -292,14 +292,14 @@ export function trapFocus(container, onEscape) {
292
292
  const handler = (e) => {
293
293
  if (e.key === 'Escape') {
294
294
  e.stopPropagation();
295
- onEscape && onEscape();
295
+ onEscape?.();
296
296
  return;
297
297
  }
298
298
  if (e.key !== 'Tab') return;
299
299
  const els = getFocusable();
300
300
  if (!els.length) return;
301
301
  const first = els[0];
302
- const last = els[els.length - 1];
302
+ const last = els.at(-1);
303
303
  if (e.shiftKey) {
304
304
  if (document.activeElement === first) {
305
305
  e.preventDefault();
@@ -347,16 +347,16 @@ export function makeDraggable(handle, box) {
347
347
  box.dataset.anDragPinned = '1';
348
348
  }
349
349
 
350
- const startX = e.clientX - parseFloat(box.style.left);
351
- const startY = e.clientY - parseFloat(box.style.top);
350
+ const startX = e.clientX - Number.parseFloat(box.style.left);
351
+ const startY = e.clientY - Number.parseFloat(box.style.top);
352
352
 
353
353
  handle.style.cursor = 'grabbing';
354
354
 
355
355
  const onMove = (ev) => {
356
356
  const bw = box.offsetWidth;
357
357
  const bh = box.offsetHeight;
358
- box.style.left = `${Math.max(0, Math.min(ev.clientX - startX, window.innerWidth - bw))}px`;
359
- box.style.top = `${Math.max(0, Math.min(ev.clientY - startY, window.innerHeight - bh))}px`;
358
+ box.style.left = `${Math.max(0, Math.min(ev.clientX - startX, globalThis.innerWidth - bw))}px`;
359
+ box.style.top = `${Math.max(0, Math.min(ev.clientY - startY, globalThis.innerHeight - bh))}px`;
360
360
  };
361
361
 
362
362
  const onUp = () => {
@@ -19,7 +19,7 @@ export const env = {
19
19
  /** True if running on mobile */
20
20
  isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent),
21
21
  /** True if touch is supported */
22
- isTouch: 'ontouchstart' in window || navigator.maxTouchPoints > 0,
22
+ isTouch: 'ontouchstart' in globalThis || navigator.maxTouchPoints > 0,
23
23
  /** Modifier key name depending on platform */
24
24
  modifierKey: /Macintosh/.test(userAgent) ? 'metaKey' : 'ctrlKey',
25
25
  };
@@ -31,7 +31,7 @@ export function debounce(fn, delay) {
31
31
  /**
32
32
  * Create a wrapper that limits how often `fn` can be invoked while ensuring the last call in a burst is executed.
33
33
  * @param {Function} fn - Function to be throttled.
34
- * @param {number} limit - Time window in milliseconds during which at most one call is allowed.
34
+ * @param {number} limit - Time globalThis in milliseconds during which at most one call is allowed.
35
35
  * @returns {Function} A wrapper function that invokes `fn` at most once per `limit` milliseconds; calls preserve `this` and original arguments and schedule a trailing invocation for the final call in a burst.
36
36
  */
37
37
  export function throttle(fn, limit) {
@@ -52,7 +52,7 @@ export function tail(arr, n = 1) {
52
52
  * @returns {T[]}
53
53
  */
54
54
  export function flatten(arr) {
55
- return arr.reduce((acc, val) => acc.concat(val), []);
55
+ return arr.flat();
56
56
  }
57
57
 
58
58
  /**
@@ -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 = ((codeEl && codeEl.className) || '').match(/language-(\S+)/);
70
+ const langMatch = /language-(\S+)/.exec((codeEl && 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,13 +105,13 @@ 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().replace(/\|/g, '\\|')),
108
+ Array.from(tr.querySelectorAll('th, td')).map((c) => c.textContent.trim().replaceAll('|', '\\|')),
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; };
112
112
  let md = '\n\n';
113
113
  md += `| ${padRow(cellTexts[0]).join(' | ')} |\n`;
114
- md += `| ${Array(cols).fill('---').join(' | ')} |\n`;
114
+ md += `| ${new Array(cols).fill('---').join(' | ')} |\n`;
115
115
  for (let r = 1; r < cellTexts.length; r++) {
116
116
  md += `| ${padRow(cellTexts[r]).join(' | ')} |\n`;
117
117
  }
@@ -139,7 +139,7 @@ export function isMarkdown(text) {
139
139
  * @returns {string}
140
140
  */
141
141
  export function markdownToHTML(text) {
142
- const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
142
+ const lines = text.replaceAll('\r\n', '\n').replaceAll('\r', '\n').split('\n');
143
143
  const out = [];
144
144
  let i = 0;
145
145
 
@@ -147,7 +147,7 @@ export function markdownToHTML(text) {
147
147
  const line = lines[i];
148
148
 
149
149
  // ---- Fenced code block ``` lang ... ``` -----------------------------------
150
- const fenceMatch = line.match(/^```(\S*)$/);
150
+ const fenceMatch = /^```(\S*)$/.exec(line);
151
151
  if (fenceMatch) {
152
152
  const lang = fenceMatch[1];
153
153
  const codeLines = [];
@@ -170,7 +170,7 @@ export function markdownToHTML(text) {
170
170
  }
171
171
 
172
172
  // ---- ATX Headings # – ###### -------------------------------------------
173
- const hMatch = line.match(/^(#{1,6})\s+(.+)$/);
173
+ const hMatch = /^(#{1,6})\s+(.+)$/.exec(line);
174
174
  if (hMatch) {
175
175
  const level = hMatch[1].length;
176
176
  out.push(`<h${level}>${_inline(hMatch[2])}</h${level}>`);
@@ -228,10 +228,10 @@ export function markdownToHTML(text) {
228
228
  bodyRows.push(_parseTableRow(lines[i]));
229
229
  i++;
230
230
  }
231
- const thead = `<thead><tr>${headerCells.map((c) => `<th>${_inline(c)}</th>`).join('')}</tr></thead>`;
232
- const tbody = bodyRows.length
233
- ? `<tbody>${bodyRows.map((row) => `<tr>${row.map((c) => `<td>${_inline(c)}</td>`).join('')}</tr>`).join('')}</tbody>`
234
- : '';
231
+ const thCells = headerCells.map((c) => `<th>${_inline(c)}</th>`).join('');
232
+ const thead = `<thead><tr>${thCells}</tr></thead>`;
233
+ const renderRow = (row) => `<tr>${row.map((c) => `<td>${_inline(c)}</td>`).join('')}</tr>`;
234
+ const tbody = bodyRows.length ? `<tbody>${bodyRows.map(renderRow).join('')}</tbody>` : '';
235
235
  out.push(`<table>${thead}${tbody}</table>`);
236
236
  continue;
237
237
  }
@@ -298,16 +298,16 @@ function _inline(text) {
298
298
 
299
299
  function _esc(v) {
300
300
  return String(v)
301
- .replace(/&/g, '&amp;')
302
- .replace(/</g, '&lt;')
303
- .replace(/>/g, '&gt;');
301
+ .replaceAll('&', '&amp;')
302
+ .replaceAll('<', '&lt;')
303
+ .replaceAll('>', '&gt;');
304
304
  }
305
305
 
306
306
  function _escAttr(v) {
307
307
  return String(v)
308
- .replace(/&/g, '&amp;')
309
- .replace(/"/g, '&quot;')
310
- .replace(/'/g, '&#39;')
311
- .replace(/</g, '&lt;')
312
- .replace(/>/g, '&gt;');
308
+ .replaceAll('&', '&amp;')
309
+ .replaceAll('"', '&quot;')
310
+ .replaceAll("'", '&#39;')
311
+ .replaceAll('<', '&lt;')
312
+ .replaceAll('>', '&gt;');
313
313
  }
@@ -41,10 +41,10 @@ export class WrappedRange {
41
41
  }
42
42
 
43
43
  /**
44
- * Select this wrapped range in the window.
44
+ * Select this wrapped range in the globalThis.
45
45
  */
46
46
  select() {
47
- const sel = window.getSelection();
47
+ const sel = globalThis.getSelection();
48
48
  if (!sel) return;
49
49
  sel.removeAllRanges();
50
50
  sel.addRange(this.toNativeRange());
@@ -116,13 +116,13 @@ export function fromNativeRange(range) {
116
116
  }
117
117
 
118
118
  /**
119
- * Returns a WrappedRange for the current window selection,
119
+ * Returns a WrappedRange for the current globalThis selection,
120
120
  * optionally restricted to a given editable element.
121
121
  * @param {HTMLElement} [editable]
122
122
  * @returns {WrappedRange|null}
123
123
  */
124
124
  export function currentRange(editable) {
125
- const sel = window.getSelection();
125
+ const sel = globalThis.getSelection();
126
126
  if (!sel || sel.rangeCount === 0) return null;
127
127
  const native = sel.getRangeAt(0);
128
128
  // Optionally check that the selection is inside the editable element
@@ -161,7 +161,7 @@ export function collapsedRange(node, offset = 0) {
161
161
  * @returns {boolean}
162
162
  */
163
163
  export function isSelectionInside(el) {
164
- const sel = window.getSelection();
164
+ const sel = globalThis.getSelection();
165
165
  if (!sel || sel.rangeCount === 0) return false;
166
166
  return el.contains(sel.getRangeAt(0).commonAncestorContainer);
167
167
  }
@@ -171,7 +171,7 @@ export function isSelectionInside(el) {
171
171
  * @param {Function} fn
172
172
  */
173
173
  export function withSavedRange(fn) {
174
- const sel = window.getSelection();
174
+ const sel = globalThis.getSelection();
175
175
  if (!sel || sel.rangeCount === 0) {
176
176
  fn(null);
177
177
  return;
@@ -31,7 +31,7 @@ export class History {
31
31
  * @returns {{ start: number, end: number }|null}
32
32
  */
33
33
  _serializeSelection() {
34
- const sel = window.getSelection();
34
+ const sel = globalThis.getSelection();
35
35
  if (!sel || sel.rangeCount === 0) return null;
36
36
  const range = sel.getRangeAt(0);
37
37
  if (!this.editable.contains(range.startContainer)) return null;
@@ -97,7 +97,7 @@ export class History {
97
97
  const range = document.createRange();
98
98
  range.setStart(startNode, startOff);
99
99
  range.setEnd(endNode, endOff);
100
- const sel = window.getSelection();
100
+ const sel = globalThis.getSelection();
101
101
  sel.removeAllRanges();
102
102
  sel.addRange(range);
103
103
  } catch (_) {
@@ -106,7 +106,7 @@ export class History {
106
106
  const fb = document.createRange();
107
107
  fb.setStart(this.editable, 0);
108
108
  fb.collapse(true);
109
- const s = window.getSelection();
109
+ const s = globalThis.getSelection();
110
110
  if (s) { s.removeAllRanges(); s.addRange(fb); }
111
111
  } catch (_2) { /* fully give up */ }
112
112
  }
@@ -181,7 +181,7 @@ export class History {
181
181
  const current = this._serialize();
182
182
  const { html: tokenized } = this._tokenizeImages(current);
183
183
  const prev = this.stack[this.stackOffset];
184
- if (prev && prev.html === tokenized) return; // No change
184
+ if (prev?.html === tokenized) return; // No change
185
185
  this._savePoint();
186
186
  }
187
187
 
@@ -40,19 +40,19 @@ export const italic = () => execCommand('italic');
40
40
  * execCommand's state detection is unreliable.
41
41
  */
42
42
  export function underline() {
43
- const sel = window.getSelection();
43
+ const sel = globalThis.getSelection();
44
44
  if (!sel || !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
48
- const uEl = container && /** @type {Element} */ (container).closest('u');
48
+ const uEl = /** @type {Element|null} */ (container)?.closest('u');
49
49
  const nativeState = document.queryCommandState('underline');
50
50
  if (uEl && !nativeState) {
51
51
  // Browser doesn't recognise the underline state (e.g. inside <code>).
52
52
  // Manually unwrap the <u> element.
53
53
  const parent = uEl.parentNode;
54
54
  while (uEl.firstChild) parent.insertBefore(uEl.firstChild, uEl);
55
- parent.removeChild(uEl);
55
+ uEl.remove();
56
56
  return;
57
57
  }
58
58
  execCommand('underline');
@@ -64,21 +64,21 @@ export function underline() {
64
64
  * execCommand's state detection is unreliable (mirrors underline() logic).
65
65
  */
66
66
  export function strikethrough() {
67
- const sel = window.getSelection();
67
+ const sel = globalThis.getSelection();
68
68
  if (!sel || !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.
72
72
  let sc = sel.getRangeAt(0).startContainer;
73
73
  if (sc.nodeType === 3) sc = sc.parentElement;
74
- const sEl = sc && (/** @type {Element} */ (sc).closest('s') || /** @type {Element} */ (sc).closest('strike'));
74
+ const sEl = /** @type {Element|null} */ (sc)?.closest('s') || /** @type {Element|null} */ (sc)?.closest('strike');
75
75
  const nativeState = document.queryCommandState('strikeThrough');
76
76
  if (sEl && !nativeState) {
77
77
  // Browser doesn’t recognise the strikethrough state (e.g. inside <code>
78
78
  // or deeply nested inline formats). Manually unwrap the <s>/<strike>.
79
79
  const parent = sEl.parentNode;
80
80
  while (sEl.firstChild) parent.insertBefore(sEl.firstChild, sEl);
81
- parent.removeChild(sEl);
81
+ sEl.remove();
82
82
  return;
83
83
  }
84
84
  execCommand('strikeThrough');
@@ -119,7 +119,7 @@ export const fontName = (name) => execCommand('fontName', name);
119
119
  * @param {HTMLElement|Document} [editable] - scoping element to avoid touching nodes outside this editor
120
120
  */
121
121
  export function fontSize(size, editable = document) {
122
- const sel = window.getSelection();
122
+ const sel = globalThis.getSelection();
123
123
  const wasCollapsed = !sel || !sel.rangeCount || sel.getRangeAt(0).collapsed;
124
124
 
125
125
  // B-I-3/4: For a collapsed (caret) selection the browser's execCommand
@@ -158,7 +158,7 @@ export function fontSize(size, editable = document) {
158
158
  span.style.fontSize = size;
159
159
  el.parentNode.insertBefore(span, el);
160
160
  while (el.firstChild) span.appendChild(el.firstChild);
161
- el.parentNode.removeChild(el);
161
+ el.remove();
162
162
  newSpans.push(span);
163
163
  });
164
164
 
@@ -167,7 +167,7 @@ export function fontSize(size, editable = document) {
167
167
  // value until the next selectionchange event).
168
168
  if (!wasCollapsed && sel && newSpans.length > 0) {
169
169
  const first = newSpans[0];
170
- const last = newSpans[newSpans.length - 1];
170
+ const last = newSpans.at(-1);
171
171
  try {
172
172
  const nr = document.createRange();
173
173
  const startNode = first.firstChild || first;
@@ -222,11 +222,11 @@ export const indent = () => execCommand('indent');
222
222
  * (which would destroy the ul > li checklist structure).
223
223
  */
224
224
  export function outdent() {
225
- const sel = window.getSelection();
225
+ const sel = globalThis.getSelection();
226
226
  if (sel && sel.rangeCount) {
227
227
  let container = sel.getRangeAt(0).commonAncestorContainer;
228
228
  if (container.nodeType === 3) container = container.parentElement;
229
- const checkLi = container && /** @type {Element} */ (container).closest('.an-checklist li');
229
+ const checkLi = /** @type {Element|null} */ (container)?.closest('.an-checklist li');
230
230
  if (checkLi) {
231
231
  _checklistItemToP(/** @type {HTMLElement} */ (checkLi));
232
232
  return;
@@ -261,7 +261,7 @@ function _checklistItemToP(checkLi) {
261
261
  p.appendChild(child.cloneNode(true));
262
262
  }
263
263
  // Strip ZWS anchors left over from checklist markup
264
- p.innerHTML = p.innerHTML.replace(/\u200B/g, '');
264
+ p.innerHTML = p.innerHTML.replaceAll('\u200B', '');
265
265
  if (!p.hasChildNodes() || !p.textContent.trim()) {
266
266
  p.innerHTML = '';
267
267
  p.appendChild(document.createTextNode('\u00a0'));
@@ -279,8 +279,8 @@ function _checklistItemToP(checkLi) {
279
279
  checkUl.parentNode.insertBefore(p, checkUl.nextSibling);
280
280
 
281
281
  // Remove current li from checkUl; delete checkUl if now empty
282
- checkUl.removeChild(checkLi);
283
- if (checkUl.children.length === 0) checkUl.parentNode.removeChild(checkUl);
282
+ checkLi.remove();
283
+ if (checkUl.children.length === 0) checkUl.remove();
284
284
 
285
285
  // Place caret at start of the new <p>
286
286
  try {
@@ -288,7 +288,7 @@ function _checklistItemToP(checkLi) {
288
288
  const firstChild = p.firstChild;
289
289
  nr.setStart(firstChild && firstChild.nodeType === 3 ? firstChild : p, 0);
290
290
  nr.collapse(true);
291
- const s = window.getSelection();
291
+ const s = globalThis.getSelection();
292
292
  if (s) { s.removeAllRanges(); s.addRange(nr); }
293
293
  } catch {}
294
294
  }
@@ -316,7 +316,7 @@ export const insertOrderedList = () => execCommand('insertOrderedList');
316
316
  * @param {string} value - Line-height value to apply; typically a unitless multiplier (for example, "1.5").
317
317
  */
318
318
  export function lineHeight(value) {
319
- const sel = window.getSelection();
319
+ const sel = globalThis.getSelection();
320
320
  if (!sel || sel.rangeCount === 0) return;
321
321
 
322
322
  const range = sel.getRangeAt(0);
@@ -376,7 +376,7 @@ export function currentStyle(editable) {
376
376
  const el = /** @type {Element|null} */ (isElement(container) ? container : container.parentElement);
377
377
  if (!el) return {};
378
378
 
379
- const computed = window.getComputedStyle(el);
379
+ const computed = globalThis.getComputedStyle(el);
380
380
 
381
381
  return {
382
382
  bold: document.queryCommandState('bold'),
@@ -405,12 +405,12 @@ export function currentStyle(editable) {
405
405
  * @param {HTMLElement} [_editable]
406
406
  */
407
407
  export function toggleInlineCode(_editable) {
408
- const sel = window.getSelection();
408
+ const sel = globalThis.getSelection();
409
409
  if (!sel || !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;
413
- const codeEl = container && /** @type {Element} */ (container).closest('code');
413
+ const codeEl = /** @type {Element|null} */ (container)?.closest('code');
414
414
  if (codeEl && !codeEl.closest('pre')) {
415
415
  // Unwrap — save range endpoints relative to surrounding text so we can
416
416
  // restore the selection after normalize() merges adjacent text nodes.
@@ -419,17 +419,17 @@ export function toggleInlineCode(_editable) {
419
419
  const prevSibling = codeEl.previousSibling;
420
420
  const movedChildren = Array.from(codeEl.childNodes);
421
421
  while (codeEl.firstChild) parent.insertBefore(codeEl.firstChild, codeEl);
422
- parent.removeChild(codeEl);
422
+ codeEl.remove();
423
423
  // Normalize only the immediate parent to merge adjacent text nodes without
424
424
  // invalidating distant selection anchors (full editable.normalize() can
425
425
  // cause selection offsets to shift, making subsequent format toggles miss).
426
- if (parent && parent.normalize) parent.normalize();
426
+ parent?.normalize();
427
427
  // Restore selection to the text that was inside the unwrapped <code>.
428
428
  if (movedChildren.length > 0) {
429
429
  try {
430
430
  // After normalize, find the merged text node that contains the content.
431
431
  const firstMoved = movedChildren[0];
432
- const lastMoved = movedChildren[movedChildren.length - 1];
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
435
  const anchorNode = (firstMoved.parentNode === parent) ? firstMoved : (prevSibling ? prevSibling.nextSibling : parent.firstChild);
@@ -476,11 +476,11 @@ export function toggleInlineCode(_editable) {
476
476
  * @returns {boolean}
477
477
  */
478
478
  export function isInlineCode() {
479
- const sel = window.getSelection();
479
+ const sel = globalThis.getSelection();
480
480
  if (!sel || !sel.rangeCount) return false;
481
481
  let sc = sel.getRangeAt(0).startContainer;
482
482
  if (sc.nodeType === 3) sc = sc.parentElement;
483
- const code = sc && /** @type {Element} */ (sc).closest('code');
483
+ const code = /** @type {Element|null} */ (sc)?.closest('code');
484
484
  return !!(code && !code.closest('pre'));
485
485
  }
486
486
 
@@ -503,13 +503,13 @@ export function isInlineCode() {
503
503
  * Empty or whitespace-only selections do not create a checklist.
504
504
  */
505
505
  export function toggleChecklist() {
506
- const sel = window.getSelection();
506
+ const sel = globalThis.getSelection();
507
507
  if (!sel || !sel.rangeCount) return;
508
508
  const range = sel.getRangeAt(0);
509
509
  let container = range.commonAncestorContainer;
510
510
  if (container.nodeType === 3) container = container.parentElement;
511
511
 
512
- const ul = container && /** @type {Element} */ (container).closest('.an-checklist');
512
+ const ul = /** @type {Element|null} */ (container)?.closest('.an-checklist');
513
513
  if (ul) {
514
514
  // If selection covers multiple <li>, convert them all
515
515
  const selectedLis = Array.from(ul.querySelectorAll('li')).filter((li) =>
@@ -523,14 +523,14 @@ export function toggleChecklist() {
523
523
  if (child.nodeType === 1 && /** @type {Element} */ (child).tagName === 'INPUT') continue;
524
524
  p.appendChild(child.cloneNode(true));
525
525
  }
526
- p.innerHTML = p.innerHTML.replace(/\u200b/g, '');
526
+ p.innerHTML = p.innerHTML.replaceAll('\u200b', '');
527
527
  if (!p.hasChildNodes() || !p.textContent.trim()) {
528
528
  p.innerHTML = '';
529
529
  p.appendChild(document.createTextNode('\u00a0'));
530
530
  }
531
531
  ul.parentNode.insertBefore(p, ul);
532
532
  if (!firstP) firstP = p;
533
- ul.removeChild(li);
533
+ li.remove();
534
534
  });
535
535
  if (ul.children.length === 0) ul.remove();
536
536
  // Move caret to first converted paragraph
@@ -561,7 +561,7 @@ export function toggleChecklist() {
561
561
  ? Array.from(block.childNodes)
562
562
  .map((n) => n.textContent)
563
563
  .join('')
564
- .replace(/\u00a0/g, ' ')
564
+ .replaceAll('\u00a0', ' ')
565
565
  : '';
566
566
 
567
567
  const ul = document.createElement('ul');
@@ -656,7 +656,7 @@ export function toggleChecklist() {
656
656
  // Insert the new list before the first block, then remove all source blocks.
657
657
  const firstBlock = blocks[0];
658
658
  firstBlock.parentNode.insertBefore(newUl, firstBlock);
659
- blocks.forEach((block) => block.parentNode && block.parentNode.removeChild(block));
659
+ blocks.forEach((block) => block.remove());
660
660
 
661
661
  // Move caret to end of the last checklist item.
662
662
  if (lastTextNode) {
@@ -673,9 +673,9 @@ export function toggleChecklist() {
673
673
  * @returns {boolean}
674
674
  */
675
675
  export function isInChecklist() {
676
- const sel = window.getSelection();
676
+ const sel = globalThis.getSelection();
677
677
  if (!sel || !sel.rangeCount) return false;
678
678
  let container = sel.getRangeAt(0).commonAncestorContainer;
679
679
  if (container.nodeType === 3) container = container.parentElement;
680
- return !!(container && /** @type {Element} */ (container).closest('.an-checklist li'));
680
+ return !!(/** @type {Element|null} */ (container)?.closest('.an-checklist li'));
681
681
  }
@@ -56,7 +56,7 @@ export function insertTable(cols, rows, opts = {}) {
56
56
  if (cols <= 0 || rows <= 0) return;
57
57
  const table = createTable(cols, rows, opts);
58
58
 
59
- const sel = window.getSelection();
59
+ const sel = globalThis.getSelection();
60
60
  if (!sel || sel.rangeCount === 0) return;
61
61
  const range = sel.getRangeAt(0);
62
62
  range.deleteContents();
@@ -64,7 +64,7 @@ export function insertTable(cols, rows, opts = {}) {
64
64
  // Walk up to find the nearest block-level ancestor to insert after
65
65
  const BLOCK = new Set(['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI', 'PRE']);
66
66
  let anchor = /** @type {Element|null} */ (range.startContainer);
67
- if (anchor && anchor.nodeType === 3) anchor = anchor.parentElement;
67
+ if (anchor?.nodeType === 3) anchor = anchor.parentElement;
68
68
  while (anchor && !BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentElement) {
69
69
  anchor = anchor.parentElement;
70
70
  }