autumnnote 1.6.2 → 1.6.3

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.2",
3
+ "version": "1.6.3",
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/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.2',
144
+ version: '1.6.3',
145
145
  };
146
146
 
147
147
  // ---------------------------------------------------------------------------
@@ -3,8 +3,8 @@
3
3
  * Click an emoji to insert it directly at the caret — no extra "Insert" step.
4
4
  */
5
5
 
6
- import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
7
- import { withSavedRange } from '../core/range.js';
6
+ import { createElement, on, makeDraggable } from '../core/dom.js';
7
+ import { BaseDialog } from './BaseDialog.js';
8
8
 
9
9
  // ---------------------------------------------------------------------------
10
10
  // Emoji catalogue
@@ -500,37 +500,18 @@ const EMOJI_LIST = [
500
500
  // Dialog class
501
501
  // ---------------------------------------------------------------------------
502
502
 
503
- export class EmojiDialog {
504
- /**
505
- * @param {import('../Context.js').Context} context
506
- */
507
- constructor(context) {
508
- this.context = context;
509
- /** @type {HTMLElement|null} */
510
- this._dialog = null;
511
- this._disposers = [];
512
- this._savedRange = null;
513
- this._activeCat = 'all';
514
- }
503
+ export class EmojiDialog extends BaseDialog {
504
+ _activeCat = 'all';
515
505
 
516
506
  // ---------------------------------------------------------------------------
517
507
  // Lifecycle
518
508
  // ---------------------------------------------------------------------------
519
509
 
520
510
  initialize() {
521
- // Dialog grid is built lazily on first show() to avoid rendering ~500 DOM nodes at load time.
511
+ // Grid is built lazily in show() to avoid ~500 DOM nodes at load time.
522
512
  return this;
523
513
  }
524
514
 
525
- destroy() {
526
- this._disposers.forEach((d) => d());
527
- this._disposers = [];
528
- if (this._dialog && this._dialog.parentNode) {
529
- this._dialog.remove();
530
- }
531
- this._dialog = null;
532
- }
533
-
534
515
  // ---------------------------------------------------------------------------
535
516
  // Public API
536
517
  // ---------------------------------------------------------------------------
@@ -540,9 +521,7 @@ export class EmojiDialog {
540
521
  this._dialog = this._buildDialog();
541
522
  document.body.appendChild(this._dialog);
542
523
  }
543
- withSavedRange((range) => {
544
- this._savedRange = range;
545
- });
524
+ this._saveRange();
546
525
  this._activeCat = 'all';
547
526
  this._searchInput.value = '';
548
527
  this._updateCatTabs();
@@ -612,6 +591,7 @@ export class EmojiDialog {
612
591
  grid.appendChild(cell);
613
592
  });
614
593
  this._grid = grid;
594
+ this._firstInput = searchInput;
615
595
 
616
596
  // Cancel only — clicking an emoji inserts immediately
617
597
  const btnRow = createElement('div', { class: 'an-dialog-actions' });
@@ -724,21 +704,4 @@ export class EmojiDialog {
724
704
  this.context.invoke('editor.afterCommand');
725
705
  }
726
706
 
727
- // ---------------------------------------------------------------------------
728
- // Open / Close
729
- // ---------------------------------------------------------------------------
730
-
731
- _open() {
732
- if (this._dialog) {
733
- this._dialog.style.display = 'flex';
734
- this._removeTrap = trapFocus(this._dialog, () => this._close());
735
- setTimeout(() => this._searchInput?.focus(), 50);
736
- }
737
- }
738
-
739
- _close() {
740
- if (this._dialog) this._dialog.style.display = 'none';
741
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
742
- this._savedRange = null;
743
- }
744
707
  }
@@ -8,14 +8,13 @@
8
8
  */
9
9
 
10
10
  import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
11
+ import { BaseDialog } from './BaseDialog.js';
11
12
 
12
- export class FindReplace {
13
+ export class FindReplace extends BaseDialog {
13
14
  /** @param {import('../Context.js').Context} context */
14
15
  constructor(context) {
15
- this.context = context;
16
+ super(context);
16
17
 
17
- /** @type {HTMLElement|null} */
18
- this._dialog = null;
19
18
  /** @type {HTMLInputElement|null} */
20
19
  this._findInput = null;
21
20
  /** @type {HTMLInputElement|null} */
@@ -39,8 +38,6 @@ export class FindReplace {
39
38
  this._lastQuery = null;
40
39
  this._lastCaseSensitive = null;
41
40
 
42
- this._disposers = [];
43
- this._removeTrap = null;
44
41
  this._focusTimer = null;
45
42
  }
46
43
 
@@ -48,22 +45,11 @@ export class FindReplace {
48
45
  // Lifecycle
49
46
  // ---------------------------------------------------------------------------
50
47
 
51
- initialize() {
52
- this._dialog = this._buildDialog();
53
- document.body.appendChild(this._dialog);
54
- return this;
55
- }
56
-
57
48
  destroy() {
58
49
  clearTimeout(this._focusTimer);
59
50
  this._focusTimer = null;
60
51
  this._clearHighlights();
61
- this._disposers.forEach((d) => d());
62
- this._disposers = [];
63
- if (this._dialog && this._dialog.parentNode) {
64
- this._dialog.remove();
65
- }
66
- this._dialog = null;
52
+ super.destroy();
67
53
  }
68
54
 
69
55
  // ---------------------------------------------------------------------------
@@ -2,8 +2,8 @@
2
2
  * IconDialog.js - Browse and insert FontAwesome Free icons
3
3
  */
4
4
 
5
- import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
6
- import { withSavedRange } from '../core/range.js';
5
+ import { createElement, on, makeDraggable } from '../core/dom.js';
6
+ import { BaseDialog } from './BaseDialog.js';
7
7
 
8
8
  // ---------------------------------------------------------------------------
9
9
  // Icon catalogue — FA 6 Free Solid slug names, grouped by category
@@ -247,19 +247,9 @@ const ICON_LIST = [
247
247
  ['puzzle-piece', 'objects'],
248
248
  ];
249
249
 
250
- export class IconDialog {
251
- /**
252
- * @param {import('../Context.js').Context} context
253
- */
254
- constructor(context) {
255
- this.context = context;
256
- /** @type {HTMLElement|null} */
257
- this._dialog = null;
258
- this._disposers = [];
259
- this._savedRange = null;
260
- this._selectedIcon = null;
261
- this._activeCat = 'all';
262
- }
250
+ export class IconDialog extends BaseDialog {
251
+ _selectedIcon = null;
252
+ _activeCat = 'all';
263
253
 
264
254
  // ---------------------------------------------------------------------------
265
255
  // Lifecycle
@@ -267,7 +257,7 @@ export class IconDialog {
267
257
 
268
258
  initialize() {
269
259
  this._ensureFontAwesome();
270
- // Dialog grid is built lazily on first show() to avoid rendering ~250 icon cells at load time.
260
+ // Grid is built lazily in show() to avoid ~250 icon cells at load time.
271
261
  return this;
272
262
  }
273
263
 
@@ -294,13 +284,6 @@ export class IconDialog {
294
284
  document.head.appendChild(link);
295
285
  }
296
286
 
297
- destroy() {
298
- this._disposers.forEach((d) => d());
299
- this._disposers = [];
300
- this._dialog?.remove();
301
- this._dialog = null;
302
- }
303
-
304
287
  // ---------------------------------------------------------------------------
305
288
  // Public API
306
289
  // ---------------------------------------------------------------------------
@@ -310,9 +293,7 @@ export class IconDialog {
310
293
  this._dialog = this._buildDialog();
311
294
  document.body.appendChild(this._dialog);
312
295
  }
313
- withSavedRange((range) => {
314
- this._savedRange = range;
315
- });
296
+ this._saveRange();
316
297
  this._selectedIcon = null;
317
298
  this._activeCat = 'all';
318
299
  this._searchInput.value = '';
@@ -360,6 +341,7 @@ export class IconDialog {
360
341
  autocomplete: 'off',
361
342
  }));
362
343
  this._searchInput = searchInput;
344
+ this._firstInput = searchInput;
363
345
 
364
346
  // Category tabs
365
347
  const catBar = createElement('div', { class: 'an-icon-cats' });
@@ -629,22 +611,8 @@ export class IconDialog {
629
611
  this.context.invoke('editor.afterCommand');
630
612
  }
631
613
 
632
- // ---------------------------------------------------------------------------
633
- // Open / Close
634
- // ---------------------------------------------------------------------------
635
-
636
- _open() {
637
- if (this._dialog) {
638
- this._dialog.style.display = 'flex';
639
- this._removeTrap = trapFocus(this._dialog, () => this._close());
640
- setTimeout(() => this._searchInput?.focus(), 50);
641
- }
642
- }
643
-
644
614
  _close() {
645
- if (this._dialog) this._dialog.style.display = 'none';
646
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
647
- this._savedRange = null;
648
615
  this._selectedIcon = null;
616
+ super._close();
649
617
  }
650
618
  }
@@ -3,7 +3,8 @@
3
3
  * Opened via the toolbar '?' button or Shift+? inside the editor.
4
4
  */
5
5
 
6
- import { createElement, on, trapFocus } from '../core/dom.js';
6
+ import { createElement, on } from '../core/dom.js';
7
+ import { BaseDialog } from './BaseDialog.js';
7
8
 
8
9
  const SHORTCUTS = [
9
10
  {
@@ -51,64 +52,37 @@ const SHORTCUTS = [
51
52
  },
52
53
  ];
53
54
 
54
- export class ShortcutsDialog {
55
- /** @param {import('../Context.js').Context} context */
56
- constructor(context) {
57
- this.context = context;
58
- this._dialog = null;
59
- this._closeBtn = null;
60
- this._disposers = [];
61
- }
62
-
63
- initialize() {
64
- this._dialog = this._buildDialog();
65
- document.body.appendChild(this._dialog);
66
- return this;
67
- }
55
+ const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M6 11h.01M10 11h.01M14 11h.01M18 11h.01M6 15h.01M18 15h.01M10 15h4"/><path d="M7 3l5 4 5-4"/></svg>`;
68
56
 
69
- destroy() {
70
- this._disposers.forEach((d) => d());
71
- this._disposers = [];
72
- this._dialog?.remove();
73
- this._dialog = null;
74
- }
57
+ export class ShortcutsDialog extends BaseDialog {
58
+ // ---------------------------------------------------------------------------
59
+ // Public API
60
+ // ---------------------------------------------------------------------------
75
61
 
76
62
  show() {
77
- if (this._dialog) {
78
- this._dialog.style.display = 'flex';
79
- this._removeTrap = trapFocus(this._dialog, () => this._close());
80
- setTimeout(() => this._closeBtn?.focus(), 50);
81
- }
63
+ this._open();
82
64
  }
83
65
 
84
- _close() {
85
- if (this._dialog) this._dialog.style.display = 'none';
86
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
87
- }
66
+ // ---------------------------------------------------------------------------
67
+ // Build dialog
68
+ // ---------------------------------------------------------------------------
88
69
 
89
70
  _buildDialog() {
90
- const overlay = createElement('div', {
91
- class: 'an-dialog-overlay',
92
- role: 'dialog',
93
- 'aria-modal': 'true',
94
- 'aria-label': this.context.locale.shortcutsDialog.ariaLabel,
95
- });
71
+ const L = this.context.locale.shortcutsDialog;
72
+ const { overlay, box } = this._buildDialogShell(L.ariaLabel, ICON_SVG, L.title);
73
+ box.classList.add('an-shortcuts-box');
96
74
 
97
- const box = createElement('div', { class: 'an-dialog-box an-shortcuts-box' });
98
-
99
- // Title row with close button
100
- const titleRow = createElement('div', { class: 'an-icon-title-row' });
101
- const title = createElement('h3', { class: 'an-dialog-title' });
102
- title.textContent = this.context.locale.shortcutsDialog.title;
75
+ // Close button pinned to the right of the header row
103
76
  const closeBtn = createElement('button', {
104
77
  type: 'button',
105
78
  class: 'an-icon-close',
106
- 'aria-label': this.context.locale.shortcutsDialog.close,
79
+ 'aria-label': L.close,
80
+ style: 'margin-left:auto',
107
81
  });
108
82
  closeBtn.textContent = '×';
109
83
  this._closeBtn = closeBtn;
110
- titleRow.append(title, closeBtn);
111
- box.appendChild(titleRow);
84
+ this._firstInput = closeBtn;
85
+ box.querySelector('.an-dialog-header').appendChild(closeBtn);
112
86
 
113
87
  const shortcuts = this.context.locale.shortcutsDialog.shortcuts || SHORTCUTS;
114
88
  shortcuts.forEach(({ category, items }) => {
@@ -129,11 +103,8 @@ export class ShortcutsDialog {
129
103
  box.appendChild(table);
130
104
  });
131
105
 
132
- overlay.appendChild(box);
133
-
134
106
  const d1 = on(closeBtn, 'click', () => this._close());
135
- const d2 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
136
- this._disposers.push(d1, d2);
107
+ this._disposers.push(d1);
137
108
 
138
109
  return overlay;
139
110
  }