autumnnote 1.6.1 → 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.
@@ -3,41 +3,12 @@
3
3
  * Inspired by Summernote's LinkDialog — rewritten without jQuery
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 } from '../core/dom.js';
7
+ import { BaseDialog } from './BaseDialog.js';
8
8
 
9
- export class LinkDialog {
10
- /**
11
- * @param {import('../Context.js').Context} context
12
- */
13
- constructor(context) {
14
- this.context = context;
15
- this.options = context.options;
16
- /** @type {HTMLElement|null} */
17
- this._dialog = null;
18
- this._disposers = [];
19
- this._savedRange = null;
20
- }
21
-
22
- // ---------------------------------------------------------------------------
23
- // Lifecycle
24
- // ---------------------------------------------------------------------------
25
-
26
- initialize() {
27
- this._dialog = this._buildDialog();
28
- document.body.appendChild(this._dialog);
29
- return this;
30
- }
31
-
32
- destroy() {
33
- this._disposers.forEach((d) => d());
34
- this._disposers = [];
35
- if (this._dialog && this._dialog.parentNode) {
36
- this._dialog.remove();
37
- }
38
- this._dialog = null;
39
- }
9
+ const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>`;
40
10
 
11
+ export class LinkDialog extends BaseDialog {
41
12
  // ---------------------------------------------------------------------------
42
13
  // Public API
43
14
  // ---------------------------------------------------------------------------
@@ -47,9 +18,7 @@ export class LinkDialog {
47
18
  * Pre-fills with the currently selected link if present.
48
19
  */
49
20
  show() {
50
- withSavedRange((range) => {
51
- this._savedRange = range;
52
- });
21
+ this._saveRange();
53
22
  this._prefill();
54
23
  this._open();
55
24
  }
@@ -60,16 +29,7 @@ export class LinkDialog {
60
29
 
61
30
  _buildDialog() {
62
31
  const L = this.context.locale.linkDialog;
63
- const overlay = createElement('div', { class: 'an-dialog-overlay', role: 'dialog', 'aria-modal': 'true', 'aria-label': L.ariaLabel });
64
- const box = createElement('div', { class: 'an-dialog-box' });
65
-
66
- const header = createElement('div', { class: 'an-dialog-header' });
67
- const iconEl = createElement('span', { class: 'an-dialog-icon' });
68
- iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>`;
69
- const title = createElement('h3', { class: 'an-dialog-title' });
70
- title.textContent = L.title;
71
- header.appendChild(iconEl);
72
- header.appendChild(title);
32
+ const { overlay, box } = this._buildDialogShell(L.ariaLabel, ICON_SVG, L.title);
73
33
 
74
34
  // URL field
75
35
  const urlLabel = createElement('label', { class: 'an-label' });
@@ -83,6 +43,7 @@ export class LinkDialog {
83
43
  autocomplete: 'off',
84
44
  }));
85
45
  this._urlInput = urlInput;
46
+ this._firstInput = urlInput;
86
47
 
87
48
  // Text field
88
49
  const textLabel = createElement('label', { class: 'an-label' });
@@ -108,26 +69,13 @@ export class LinkDialog {
108
69
  tabLabel.appendChild(tabCheckbox);
109
70
  tabLabel.appendChild(document.createTextNode(' ' + L.openInNewTab));
110
71
 
111
- // Buttons
112
- const btnRow = createElement('div', { class: 'an-dialog-actions' });
113
- const insertBtn = createElement('button', { type: 'button', class: 'an-btn an-btn-primary' });
114
- insertBtn.textContent = L.insertBtn;
115
- const cancelBtn = createElement('button', { type: 'button', class: 'an-btn' });
116
- cancelBtn.textContent = L.cancelBtn;
117
- btnRow.appendChild(insertBtn);
118
- btnRow.appendChild(cancelBtn);
119
-
120
- box.append(header, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
121
- overlay.appendChild(box);
122
- makeDraggable(header, box);
123
-
124
- // Events
125
- const d1 = on(insertBtn, 'click', () => this._onInsert());
126
- const d2 = on(cancelBtn, 'click', () => this._close());
127
- const d3 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
72
+ const btnRow = this._buildButtonRow(L.insertBtn, L.cancelBtn, () => this._onInsert());
73
+
74
+ box.append(urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
75
+
128
76
  const d4 = on(urlInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
129
77
  const d5 = on(textInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
130
- this._disposers.push(d1, d2, d3, d4, d5);
78
+ this._disposers.push(d4, d5);
131
79
 
132
80
  return overlay;
133
81
  }
@@ -194,18 +142,4 @@ export class LinkDialog {
194
142
  this.context.invoke('editor.insertLink', url, text, newTab);
195
143
  this._close();
196
144
  }
197
-
198
- _open() {
199
- if (this._dialog) {
200
- this._dialog.style.display = 'flex';
201
- this._removeTrap = trapFocus(this._dialog, () => this._close());
202
- setTimeout(() => this._urlInput && this._urlInput.focus(), 50);
203
- }
204
- }
205
-
206
- _close() {
207
- if (this._dialog) this._dialog.style.display = 'none';
208
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
209
- this._savedRange = null;
210
- }
211
145
  }
@@ -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
  }
@@ -7,47 +7,18 @@
7
7
  * • Direct video URLs → <video> element (.mp4 / .webm / .ogg)
8
8
  */
9
9
 
10
- import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
11
- import { withSavedRange } from '../core/range.js';
10
+ import { createElement, on } from '../core/dom.js';
11
+ import { BaseDialog } from './BaseDialog.js';
12
12
 
13
- export class VideoDialog {
14
- /** @param {import('../Context.js').Context} context */
15
- constructor(context) {
16
- this.context = context;
17
- this.options = context.options;
18
- /** @type {HTMLElement|null} */
19
- this._dialog = null;
20
- this._disposers = [];
21
- this._savedRange = null;
22
- }
23
-
24
- // ---------------------------------------------------------------------------
25
- // Lifecycle
26
- // ---------------------------------------------------------------------------
27
-
28
- initialize() {
29
- this._dialog = this._buildDialog();
30
- document.body.appendChild(this._dialog);
31
- return this;
32
- }
33
-
34
- destroy() {
35
- this._disposers.forEach((d) => d());
36
- this._disposers = [];
37
- if (this._dialog && this._dialog.parentNode) {
38
- this._dialog.remove();
39
- }
40
- this._dialog = null;
41
- }
13
+ const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg>`;
42
14
 
15
+ export class VideoDialog extends BaseDialog {
43
16
  // ---------------------------------------------------------------------------
44
17
  // Public API
45
18
  // ---------------------------------------------------------------------------
46
19
 
47
20
  show() {
48
- withSavedRange((range) => {
49
- this._savedRange = range;
50
- });
21
+ this._saveRange();
51
22
  this._urlInput.value = '';
52
23
  this._widthInput.value = '560';
53
24
  this._hintEl.textContent = '';
@@ -60,21 +31,7 @@ export class VideoDialog {
60
31
 
61
32
  _buildDialog() {
62
33
  const L = this.context.locale.videoDialog;
63
- const overlay = createElement('div', {
64
- class: 'an-dialog-overlay',
65
- role: 'dialog',
66
- 'aria-modal': 'true',
67
- 'aria-label': L.ariaLabel,
68
- });
69
- const box = createElement('div', { class: 'an-dialog-box' });
70
-
71
- const header = createElement('div', { class: 'an-dialog-header' });
72
- const iconEl = createElement('span', { class: 'an-dialog-icon' });
73
- iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg>`;
74
- const title = createElement('h3', { class: 'an-dialog-title' });
75
- title.textContent = L.title;
76
- header.appendChild(iconEl);
77
- header.appendChild(title);
34
+ const { overlay, box } = this._buildDialogShell(L.ariaLabel, ICON_SVG, L.title);
78
35
 
79
36
  // URL input
80
37
  const urlLabel = createElement('label', { class: 'an-label' });
@@ -86,6 +43,7 @@ export class VideoDialog {
86
43
  autocomplete: 'off',
87
44
  }));
88
45
  this._urlInput = urlInput;
46
+ this._firstInput = urlInput;
89
47
 
90
48
  // Hint (detected source)
91
49
  const hintEl = createElement('p', { class: 'an-dialog-hint' });
@@ -104,30 +62,16 @@ export class VideoDialog {
104
62
  }));
105
63
  this._widthInput = widthInput;
106
64
 
107
- // Buttons
108
- const btnRow = createElement('div', { class: 'an-dialog-actions' });
109
- const insertBtn = createElement('button', { type: 'button', class: 'an-btn an-btn-primary' });
110
- insertBtn.textContent = L.insertBtn;
111
- const cancelBtn = createElement('button', { type: 'button', class: 'an-btn' });
112
- cancelBtn.textContent = L.cancelBtn;
113
- btnRow.appendChild(insertBtn);
114
- btnRow.appendChild(cancelBtn);
115
-
116
- box.append(header, urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
117
- overlay.appendChild(box);
118
- makeDraggable(header, box);
65
+ const btnRow = this._buildButtonRow(L.insertBtn, L.cancelBtn, () => this._onInsert());
66
+ box.append(urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
119
67
 
120
68
  // Live URL hint
121
69
  const d0 = on(urlInput, 'input', () => {
122
70
  const info = this._parseVideoUrl(urlInput.value.trim());
123
71
  hintEl.textContent = info ? this.context.locale.videoDialog.detected(info.type) : (urlInput.value ? this.context.locale.videoDialog.unknownFormat : '');
124
72
  });
125
-
126
- const d1 = on(insertBtn, 'click', () => this._onInsert());
127
- const d2 = on(cancelBtn, 'click', () => this._close());
128
- const d3 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
129
73
  const d4 = on(urlInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
130
- this._disposers.push(d0, d1, d2, d3, d4);
74
+ this._disposers.push(d0, d4);
131
75
 
132
76
  return overlay;
133
77
  }
@@ -157,20 +101,6 @@ export class VideoDialog {
157
101
  this._close();
158
102
  }
159
103
 
160
- _open() {
161
- if (this._dialog) {
162
- this._dialog.style.display = 'flex';
163
- this._removeTrap = trapFocus(this._dialog, () => this._close());
164
- setTimeout(() => this._urlInput && this._urlInput.focus(), 50);
165
- }
166
- }
167
-
168
- _close() {
169
- if (this._dialog) this._dialog.style.display = 'none';
170
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
171
- this._savedRange = null;
172
- }
173
-
174
104
  // ---------------------------------------------------------------------------
175
105
  // URL parsing & HTML building
176
106
  // ---------------------------------------------------------------------------