autumnnote 1.6.3 → 1.6.5

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.5",
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",
@@ -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 ─────────────────────────────────────────────────────────────
@@ -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
 
@@ -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
@@ -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;
@@ -477,7 +477,7 @@ export function toggleInlineCode(_editable) {
477
477
  */
478
478
  export function isInlineCode() {
479
479
  const sel = globalThis.getSelection();
480
- if (!sel || !sel.rangeCount) return false;
480
+ if (!sel?.rangeCount) return false;
481
481
  let sc = sel.getRangeAt(0).startContainer;
482
482
  if (sc.nodeType === 3) sc = sc.parentElement;
483
483
  const code = /** @type {Element|null} */ (sc)?.closest('code');
@@ -504,7 +504,7 @@ export function isInlineCode() {
504
504
  */
505
505
  export function toggleChecklist() {
506
506
  const sel = globalThis.getSelection();
507
- if (!sel || !sel.rangeCount) return;
507
+ if (!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;
@@ -552,7 +552,7 @@ export function toggleChecklist() {
552
552
  // and convert it into a single checklist item.
553
553
  const BLOCK_TAGS = new Set(['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI']);
554
554
  let block = /** @type {Element|null} */ (container);
555
- while (block && block.parentNode && !BLOCK_TAGS.has(block.tagName)) {
555
+ while (block?.parentNode && !BLOCK_TAGS.has(block.tagName)) {
556
556
  block = /** @type {Element|null} */ (block.parentNode);
557
557
  }
558
558
  // Fallback: if no block element found (e.g. cursor directly in editable root), use the
@@ -674,7 +674,7 @@ export function toggleChecklist() {
674
674
  */
675
675
  export function isInChecklist() {
676
676
  const sel = globalThis.getSelection();
677
- if (!sel || !sel.rangeCount) return false;
677
+ if (!sel?.rangeCount) return false;
678
678
  let container = sel.getRangeAt(0).commonAncestorContainer;
679
679
  if (container.nodeType === 3) container = container.parentElement;
680
680
  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);
@@ -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.5',
145
145
  };
146
146
 
147
147
  // ---------------------------------------------------------------------------
@@ -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
 
@@ -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 || '';
@@ -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);
@@ -580,7 +580,7 @@ export class IconDialog extends BaseDialog {
580
580
  // Keep a text anchor (\u200B) after the icon so ArrowLeft/ArrowRight and
581
581
  // caret placement at end-of-line stay stable across browsers.
582
582
  let caretTextNode = iconEl.nextSibling;
583
- if (!caretTextNode || caretTextNode.nodeType !== Node.TEXT_NODE) {
583
+ if (caretTextNode?.nodeType !== Node.TEXT_NODE) {
584
584
  caretTextNode = document.createTextNode('\u200B');
585
585
  iconEl.parentNode.insertBefore(caretTextNode, iconEl.nextSibling);
586
586
  } else if (!caretTextNode.textContent) {
@@ -399,7 +399,7 @@ export class ImageCropOverlay {
399
399
  const startY = e.clientY;
400
400
  const orig = { ...this._box };
401
401
  const r = this._imgRect;
402
- const lockAR = this._arCheck && this._arCheck.checked;
402
+ const lockAR = this._arCheck?.checked;
403
403
  const aspect = orig.w / (orig.h || 1);
404
404
 
405
405
  const onMove = (me) => {
@@ -181,7 +181,7 @@ export class ImageResizer {
181
181
 
182
182
  // Skip DOM writes when position hasn't changed (common during non-scroll rAF ticks)
183
183
  const p = this._lastOverlayPos;
184
- if (p && p.l === rect.left && p.t === rect.top && p.w === rect.width && p.h === rect.height) return;
184
+ if (p?.l === rect.left && p.t === rect.top && p.w === rect.width && p.h === rect.height) return;
185
185
  this._lastOverlayPos = { l: rect.left, t: rect.top, w: rect.width, h: rect.height };
186
186
 
187
187
  const left = rect.left - containerRect.left + offsetParent.scrollLeft;
@@ -210,7 +210,7 @@ export class Mention {
210
210
 
211
211
  // Fallback: use getClientRects() on the current collapsed selection
212
212
  const sel = globalThis.getSelection();
213
- if (!sel || !sel.rangeCount) return;
213
+ if (!sel?.rangeCount) return;
214
214
  const rects = sel.getRangeAt(0).getClientRects();
215
215
  if (rects.length > 0) {
216
216
  this._caretRect = rects[rects.length - 1];
@@ -58,7 +58,7 @@ function getCellAfterVisualCol(row, visualIdx) {
58
58
  if (vIdx > visualIdx) {
59
59
  // next cell after the one that starts at / spans visualIdx
60
60
  const next = c.nextElementSibling;
61
- return (next && next.tagName === 'TD' || next && next.tagName === 'TH') ? /** @type {HTMLTableCellElement} */ (next) : null;
61
+ return (next?.tagName === 'TD' || next?.tagName === 'TH') ? /** @type {HTMLTableCellElement} */ (next) : null;
62
62
  }
63
63
  }
64
64
  return null;
@@ -388,11 +388,11 @@ export class TableTooltip {
388
388
  this._disposers = [];
389
389
  if (this._el && this._el.parentNode) this._el.remove();
390
390
  this._el = null;
391
- if (this._sizePopover && this._sizePopover.parentNode) {
391
+ if (this._sizePopover?.parentNode) {
392
392
  this._sizePopover.remove();
393
393
  }
394
394
  this._sizePopover = null;
395
- if (this._shadePopover && this._shadePopover.parentNode) {
395
+ if (this._shadePopover?.parentNode) {
396
396
  this._shadePopover.remove();
397
397
  }
398
398
  this._shadePopover = null;
@@ -604,7 +604,7 @@ export class TableTooltip {
604
604
  _getCell() {
605
605
  // Prefer the cell under the current text cursor (most intuitive for operations)
606
606
  const sel = globalThis.getSelection();
607
- if (sel && sel.rangeCount) {
607
+ if (sel?.rangeCount) {
608
608
  let container = sel.getRangeAt(0).commonAncestorContainer;
609
609
  if (container.nodeType === 3) container = container.parentElement;
610
610
  const cellFromSel = /** @type {Element} */ (container)?.closest('td, th');
@@ -659,7 +659,7 @@ export class TableTooltip {
659
659
  if (!startCell) return [];
660
660
  if (!endCell || startCell === endCell) return [startCell];
661
661
  const table = startCell.closest('table');
662
- if (!table || !table.contains(endCell)) return [startCell];
662
+ if (!table?.contains(endCell)) return [startCell];
663
663
 
664
664
  const { gridMap, cellPos } = buildGridMap(table);
665
665
  const sp = cellPos.get(startCell);
@@ -742,8 +742,8 @@ export class TableTooltip {
742
742
  for (let i = 0; i < colCount; i++) {
743
743
  const td = createElement('td', {}, ['\u00a0']);
744
744
  const ref = refCells[i];
745
- if (ref && ref.style.width) td.style.width = ref.style.width;
746
- if (ref && ref.style.minWidth) td.style.minWidth = ref.style.minWidth;
745
+ if (ref?.style.width) td.style.width = ref.style.width;
746
+ if (ref?.style.minWidth) td.style.minWidth = ref.style.minWidth;
747
747
  newRow.appendChild(td);
748
748
  }
749
749
  if (position === 'above') refRow.parentElement?.insertBefore(newRow, refRow);
@@ -399,7 +399,7 @@ export class Toolbar {
399
399
 
400
400
  const saveSelection = () => {
401
401
  const sel = globalThis.getSelection();
402
- savedRange = (sel && sel.rangeCount) ? sel.getRangeAt(0).cloneRange() : null;
402
+ savedRange = sel?.rangeCount ? sel.getRangeAt(0).cloneRange() : null;
403
403
  };
404
404
 
405
405
  const restoreSelection = () => {
@@ -543,7 +543,7 @@ export class Toolbar {
543
543
  const value = (typeof item === 'object') ? item.value : item;
544
544
  const label = (typeof item === 'object')
545
545
  ? (def.name === 'paragraphStyle'
546
- ? ((this.context.locale.toolbar.paragraphItems || {})[item.value] || item.label)
546
+ ? (this.context.locale.toolbar.paragraphItems?.[item.value] || item.label)
547
547
  : item.label)
548
548
  : item;
549
549
  const isHeader = (typeof item === 'object') && !!item.disabled;
@@ -563,7 +563,7 @@ export class Toolbar {
563
563
  let _savedRange = null;
564
564
  const dMousedown = on(select, 'mousedown', () => {
565
565
  const sel = globalThis.getSelection();
566
- _savedRange = (sel && sel.rangeCount) ? sel.getRangeAt(0).cloneRange() : null;
566
+ _savedRange = sel?.rangeCount ? sel.getRangeAt(0).cloneRange() : null;
567
567
  });
568
568
 
569
569
  const disposer = on(select, 'change', (e) => {
@@ -617,15 +617,13 @@ export class Toolbar {
617
617
  } else {
618
618
  btn.textContent = btnDef.icon || btnDef.name;
619
619
  }
620
- } else {
620
+ } else if (_SVG_MAP.has(btnDef.icon)) {
621
621
  // FontAwesome absent: use SVG fallback when available
622
- if (_SVG_MAP.has(btnDef.icon)) {
623
- btn.innerHTML = _SVG_MAP.get(btnDef.icon);
624
- } else if (_SVG_MAP.has(btnDef.name)) {
625
- btn.innerHTML = _SVG_MAP.get(btnDef.name);
626
- } else {
627
- btn.textContent = btnDef.icon || btnDef.name;
628
- }
622
+ btn.innerHTML = _SVG_MAP.get(btnDef.icon);
623
+ } else if (_SVG_MAP.has(btnDef.name)) {
624
+ btn.innerHTML = _SVG_MAP.get(btnDef.name);
625
+ } else {
626
+ btn.textContent = btnDef.icon || btnDef.name;
629
627
  }
630
628
 
631
629
  const disposer = on(btn, 'click', (event) => {