autumnnote 1.6.0 → 1.6.2
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/README.md +4 -4
- package/dist/autumnnote.css +0 -2
- package/dist/autumnnote.es.js +468 -552
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +461 -546
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +21 -3
- package/src/js/Context.js +21 -16
- package/src/js/core/dom.js +9 -9
- package/src/js/core/env.js +1 -1
- package/src/js/core/func.js +1 -1
- package/src/js/core/lists.js +1 -1
- package/src/js/core/markdown.js +18 -18
- package/src/js/core/range.js +6 -6
- package/src/js/editing/History.js +4 -4
- package/src/js/editing/Style.js +32 -32
- package/src/js/editing/Table.js +2 -2
- package/src/js/editing/Typing.js +15 -15
- package/src/js/index.js +1 -1
- package/src/js/module/AutoSaveRestore.js +2 -4
- package/src/js/module/BaseDialog.js +126 -0
- package/src/js/module/BubbleToolbar.js +25 -25
- package/src/js/module/Buttons.js +4 -4
- package/src/js/module/Clipboard.js +12 -13
- package/src/js/module/CodeTooltip.js +14 -16
- package/src/js/module/Codeview.js +3 -5
- package/src/js/module/ContextMenu.js +27 -27
- package/src/js/module/Editor.js +17 -18
- package/src/js/module/EmojiDialog.js +5 -5
- package/src/js/module/FindReplace.js +8 -7
- package/src/js/module/IconDialog.js +13 -15
- package/src/js/module/ImageCropOverlay.js +7 -7
- package/src/js/module/ImageDialog.js +12 -82
- package/src/js/module/ImageResizer.js +4 -4
- package/src/js/module/ImageTooltip.js +14 -14
- package/src/js/module/LinkDialog.js +13 -79
- package/src/js/module/LinkTooltip.js +12 -12
- package/src/js/module/MarkdownShortcuts.js +11 -14
- package/src/js/module/Mention.js +8 -10
- package/src/js/module/Placeholder.js +1 -1
- package/src/js/module/ShortcutsDialog.js +2 -4
- package/src/js/module/Statusbar.js +3 -5
- package/src/js/module/TableTooltip.js +30 -32
- package/src/js/module/Toolbar.js +21 -21
- package/src/js/module/VideoDialog.js +17 -87
- package/src/js/module/VideoResizer.js +5 -7
- package/src/js/module/VideoTooltip.js +7 -7
- package/src/js/renderer.js +1 -1
- package/src/styles/autumnnote.scss +0 -3
|
@@ -3,49 +3,18 @@
|
|
|
3
3
|
* Inspired by Summernote's ImageDialog — rewritten without jQuery
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createElement, on
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
export class ImageDialog {
|
|
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
|
-
}
|
|
6
|
+
import { createElement, on } from '../core/dom.js';
|
|
7
|
+
import { BaseDialog } from './BaseDialog.js';
|
|
21
8
|
|
|
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.parentNode.removeChild(this._dialog);
|
|
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"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`;
|
|
40
10
|
|
|
11
|
+
export class ImageDialog extends BaseDialog {
|
|
41
12
|
// ---------------------------------------------------------------------------
|
|
42
13
|
// Public API
|
|
43
14
|
// ---------------------------------------------------------------------------
|
|
44
15
|
|
|
45
16
|
show() {
|
|
46
|
-
|
|
47
|
-
this._savedRange = range;
|
|
48
|
-
});
|
|
17
|
+
this._saveRange();
|
|
49
18
|
this._urlInput.value = '';
|
|
50
19
|
this._altInput.value = '';
|
|
51
20
|
if (this._fileInput) this._fileInput.value = '';
|
|
@@ -58,23 +27,9 @@ export class ImageDialog {
|
|
|
58
27
|
|
|
59
28
|
_buildDialog() {
|
|
60
29
|
const L = this.context.locale.imageDialog;
|
|
61
|
-
const overlay =
|
|
62
|
-
class: 'an-dialog-overlay',
|
|
63
|
-
role: 'dialog',
|
|
64
|
-
'aria-modal': 'true',
|
|
65
|
-
'aria-label': L.ariaLabel,
|
|
66
|
-
});
|
|
67
|
-
const box = createElement('div', { class: 'an-dialog-box' });
|
|
68
|
-
|
|
69
|
-
const header = createElement('div', { class: 'an-dialog-header' });
|
|
70
|
-
const iconEl = createElement('span', { class: 'an-dialog-icon' });
|
|
71
|
-
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"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>`;
|
|
72
|
-
const title = createElement('h3', { class: 'an-dialog-title' });
|
|
73
|
-
title.textContent = L.title;
|
|
74
|
-
header.appendChild(iconEl);
|
|
75
|
-
header.appendChild(title);
|
|
30
|
+
const { overlay, box } = this._buildDialogShell(L.ariaLabel, ICON_SVG, L.title);
|
|
76
31
|
|
|
77
|
-
// URL
|
|
32
|
+
// URL field
|
|
78
33
|
const urlLabel = createElement('label', { class: 'an-label' });
|
|
79
34
|
urlLabel.textContent = L.imageUrl;
|
|
80
35
|
const urlInput = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
@@ -84,6 +39,7 @@ export class ImageDialog {
|
|
|
84
39
|
autocomplete: 'off',
|
|
85
40
|
}));
|
|
86
41
|
this._urlInput = urlInput;
|
|
42
|
+
this._firstInput = urlInput;
|
|
87
43
|
|
|
88
44
|
// Alt text
|
|
89
45
|
const altLabel = createElement('label', { class: 'an-label' });
|
|
@@ -96,7 +52,7 @@ export class ImageDialog {
|
|
|
96
52
|
}));
|
|
97
53
|
this._altInput = altInput;
|
|
98
54
|
|
|
99
|
-
box.append(
|
|
55
|
+
box.append(urlLabel, urlInput, altLabel, altInput);
|
|
100
56
|
|
|
101
57
|
// Alignment
|
|
102
58
|
const alignLabel = createElement('label', { class: 'an-label' });
|
|
@@ -118,6 +74,7 @@ export class ImageDialog {
|
|
|
118
74
|
});
|
|
119
75
|
this._alignRow = alignRow;
|
|
120
76
|
box.append(alignLabel, alignRow);
|
|
77
|
+
|
|
121
78
|
if (this.options.allowImageUpload !== false) {
|
|
122
79
|
const fileLabel = createElement('label', { class: 'an-label' });
|
|
123
80
|
fileLabel.textContent = L.uploadLabel;
|
|
@@ -135,25 +92,12 @@ export class ImageDialog {
|
|
|
135
92
|
box.append(fileLabel, fileInput, fileHint);
|
|
136
93
|
}
|
|
137
94
|
|
|
138
|
-
|
|
139
|
-
const btnRow = createElement('div', { class: 'an-dialog-actions' });
|
|
140
|
-
const insertBtn = createElement('button', { type: 'button', class: 'an-btn an-btn-primary' });
|
|
141
|
-
insertBtn.textContent = L.insertBtn;
|
|
142
|
-
const cancelBtn = createElement('button', { type: 'button', class: 'an-btn' });
|
|
143
|
-
cancelBtn.textContent = L.cancelBtn;
|
|
144
|
-
btnRow.appendChild(insertBtn);
|
|
145
|
-
btnRow.appendChild(cancelBtn);
|
|
146
|
-
|
|
95
|
+
const btnRow = this._buildButtonRow(L.insertBtn, L.cancelBtn, () => this._onInsert());
|
|
147
96
|
box.append(btnRow);
|
|
148
|
-
overlay.appendChild(box);
|
|
149
|
-
makeDraggable(header, box);
|
|
150
97
|
|
|
151
|
-
const d1 = on(insertBtn, 'click', () => this._onInsert());
|
|
152
|
-
const d2 = on(cancelBtn, 'click', () => this._close());
|
|
153
|
-
const d3 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
|
|
154
98
|
const d4 = on(urlInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
|
|
155
99
|
const d5 = on(altInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
|
|
156
|
-
this._disposers.push(
|
|
100
|
+
this._disposers.push(d4, d5);
|
|
157
101
|
|
|
158
102
|
return overlay;
|
|
159
103
|
}
|
|
@@ -217,18 +161,4 @@ export class ImageDialog {
|
|
|
217
161
|
this.context.invoke('editor.insertImage', src, alt, align);
|
|
218
162
|
this._close();
|
|
219
163
|
}
|
|
220
|
-
|
|
221
|
-
_open() {
|
|
222
|
-
if (this._dialog) {
|
|
223
|
-
this._dialog.style.display = 'flex';
|
|
224
|
-
this._removeTrap = trapFocus(this._dialog, () => this._close());
|
|
225
|
-
setTimeout(() => this._urlInput && this._urlInput.focus(), 50);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
_close() {
|
|
230
|
-
if (this._dialog) this._dialog.style.display = 'none';
|
|
231
|
-
if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
|
|
232
|
-
this._savedRange = null;
|
|
233
|
-
}
|
|
234
164
|
}
|
|
@@ -38,7 +38,7 @@ export class ImageResizer {
|
|
|
38
38
|
|
|
39
39
|
const editable = this.context.layoutInfo.editable;
|
|
40
40
|
|
|
41
|
-
// Debounce
|
|
41
|
+
// Debounce globalThis resize — _updateOverlayPosition already has rAF gating but
|
|
42
42
|
// every call cancels + re-schedules it; a debounce reduces that churn.
|
|
43
43
|
let _resizeDebounce = null;
|
|
44
44
|
const onWindowResize = () => {
|
|
@@ -55,8 +55,8 @@ export class ImageResizer {
|
|
|
55
55
|
if (img) this._select(img);
|
|
56
56
|
}),
|
|
57
57
|
on(document, 'click', (e) => this._onDocClick(e)),
|
|
58
|
-
on(
|
|
59
|
-
on(
|
|
58
|
+
on(globalThis, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
59
|
+
on(globalThis, 'resize', onWindowResize, { passive: true }),
|
|
60
60
|
on(editable, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
61
61
|
);
|
|
62
62
|
|
|
@@ -76,7 +76,7 @@ export class ImageResizer {
|
|
|
76
76
|
}
|
|
77
77
|
this._deselect();
|
|
78
78
|
if (this._overlay && this._overlay.parentNode) {
|
|
79
|
-
this._overlay.
|
|
79
|
+
this._overlay.remove();
|
|
80
80
|
}
|
|
81
81
|
this._overlay = null;
|
|
82
82
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ImageTooltip.js - Hover tooltip for images inside the editor
|
|
1
|
+
// ImageTooltip.js - Hover tooltip for images inside the editor
|
|
2
2
|
// Displays a horizontal action bar below (or above) the selected image,
|
|
3
3
|
// similar in appearance and interaction to LinkTooltip.
|
|
4
4
|
import { createElement, on } from '../core/dom.js';
|
|
@@ -59,8 +59,8 @@ export class ImageTooltip {
|
|
|
59
59
|
}
|
|
60
60
|
}),
|
|
61
61
|
// Hide when the page scrolls or resizes — the tooltip position becomes stale
|
|
62
|
-
on(
|
|
63
|
-
on(
|
|
62
|
+
on(globalThis, 'scroll', () => this._hide(), { passive: true }),
|
|
63
|
+
on(globalThis, 'resize', () => this._hide(), { passive: true }),
|
|
64
64
|
);
|
|
65
65
|
|
|
66
66
|
return this;
|
|
@@ -70,7 +70,7 @@ export class ImageTooltip {
|
|
|
70
70
|
this._clearTimers();
|
|
71
71
|
this._disposers.forEach((d) => d());
|
|
72
72
|
this._disposers = [];
|
|
73
|
-
|
|
73
|
+
this._el?.remove();
|
|
74
74
|
this._el = null;
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -215,8 +215,8 @@ export class ImageTooltip {
|
|
|
215
215
|
let top = rect.bottom + margin;
|
|
216
216
|
let left = rect.left + (rect.width - tipW) / 2;
|
|
217
217
|
|
|
218
|
-
if (top + tipH >
|
|
219
|
-
if (left + tipW >
|
|
218
|
+
if (top + tipH > globalThis.innerHeight - margin) top = rect.top - tipH - margin;
|
|
219
|
+
if (left + tipW > globalThis.innerWidth - margin) left = globalThis.innerWidth - tipW - margin;
|
|
220
220
|
if (left < margin) left = margin;
|
|
221
221
|
|
|
222
222
|
this._el.style.top = `${top}px`;
|
|
@@ -279,8 +279,8 @@ export class ImageTooltip {
|
|
|
279
279
|
if (!img) return;
|
|
280
280
|
// Parse current rotation angle from inline transform
|
|
281
281
|
const current = img.style.transform || '';
|
|
282
|
-
const match =
|
|
283
|
-
const prev = match ? parseFloat(match[1]) : 0;
|
|
282
|
+
const match = /rotate\((-?[\d.]+)deg\)/.exec(current);
|
|
283
|
+
const prev = match ? Number.parseFloat(match[1]) : 0;
|
|
284
284
|
const next = (prev + delta + 360) % 360; // normalise to [0, 360)
|
|
285
285
|
// Preserve any other transform functions (e.g. scale), replace only rotate()
|
|
286
286
|
const cleaned = current.replace(/rotate\(-?[\d.]+deg\)/, '').trim();
|
|
@@ -298,10 +298,10 @@ export class ImageTooltip {
|
|
|
298
298
|
this._hide();
|
|
299
299
|
this.context.invoke('imageResizer.deselect');
|
|
300
300
|
const figure = img.closest('figure.an-figure');
|
|
301
|
-
if (figure
|
|
302
|
-
figure.
|
|
303
|
-
} else
|
|
304
|
-
img.
|
|
301
|
+
if (figure) {
|
|
302
|
+
figure.remove();
|
|
303
|
+
} else {
|
|
304
|
+
img.remove();
|
|
305
305
|
}
|
|
306
306
|
this.context.invoke('editor.afterCommand');
|
|
307
307
|
}
|
|
@@ -327,7 +327,7 @@ export class ImageTooltip {
|
|
|
327
327
|
this._hide();
|
|
328
328
|
const range = document.createRange();
|
|
329
329
|
range.selectNodeContents(cap);
|
|
330
|
-
const sel =
|
|
330
|
+
const sel = globalThis.getSelection();
|
|
331
331
|
if (sel) { sel.removeAllRanges(); sel.addRange(range); }
|
|
332
332
|
}
|
|
333
333
|
return;
|
|
@@ -367,7 +367,7 @@ export class ImageTooltip {
|
|
|
367
367
|
// Select caption text immediately
|
|
368
368
|
const range = document.createRange();
|
|
369
369
|
range.selectNodeContents(figcaption);
|
|
370
|
-
const sel =
|
|
370
|
+
const sel = globalThis.getSelection();
|
|
371
371
|
if (sel) { sel.removeAllRanges(); sel.addRange(range); }
|
|
372
372
|
|
|
373
373
|
this.context.invoke('editor.afterCommand');
|
|
@@ -3,41 +3,12 @@
|
|
|
3
3
|
* Inspired by Summernote's LinkDialog — rewritten without jQuery
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createElement, on
|
|
7
|
-
import {
|
|
6
|
+
import { createElement, on } from '../core/dom.js';
|
|
7
|
+
import { BaseDialog } from './BaseDialog.js';
|
|
8
8
|
|
|
9
|
-
|
|
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.parentNode.removeChild(this._dialog);
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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(
|
|
78
|
+
this._disposers.push(d4, d5);
|
|
131
79
|
|
|
132
80
|
return overlay;
|
|
133
81
|
}
|
|
@@ -138,7 +86,7 @@ export class LinkDialog {
|
|
|
138
86
|
|
|
139
87
|
_prefill() {
|
|
140
88
|
// Try to find currently selected anchor
|
|
141
|
-
const sel =
|
|
89
|
+
const sel = globalThis.getSelection();
|
|
142
90
|
let anchor = null;
|
|
143
91
|
if (sel && sel.rangeCount > 0) {
|
|
144
92
|
let node = sel.getRangeAt(0).startContainer;
|
|
@@ -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
|
}
|
|
@@ -46,8 +46,8 @@ export class LinkTooltip {
|
|
|
46
46
|
}
|
|
47
47
|
}),
|
|
48
48
|
// Hide when the page scrolls or resizes — the tooltip position becomes stale
|
|
49
|
-
on(
|
|
50
|
-
on(
|
|
49
|
+
on(globalThis, 'scroll', () => this._hide(), { passive: true }),
|
|
50
|
+
on(globalThis, 'resize', () => this._hide(), { passive: true }),
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
return this;
|
|
@@ -57,7 +57,7 @@ export class LinkTooltip {
|
|
|
57
57
|
this._clearTimers();
|
|
58
58
|
this._disposers.forEach((d) => d());
|
|
59
59
|
this._disposers = [];
|
|
60
|
-
if (this._el && this._el.parentNode) this._el.
|
|
60
|
+
if (this._el && this._el.parentNode) this._el.remove();
|
|
61
61
|
this._el = null;
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -163,11 +163,11 @@ export class LinkTooltip {
|
|
|
163
163
|
let top = rect.bottom + margin;
|
|
164
164
|
let left = rect.left;
|
|
165
165
|
|
|
166
|
-
if (top + tipH >
|
|
166
|
+
if (top + tipH > globalThis.innerHeight - margin) {
|
|
167
167
|
top = rect.top - tipH - margin;
|
|
168
168
|
}
|
|
169
|
-
if (left + tipW >
|
|
170
|
-
left =
|
|
169
|
+
if (left + tipW > globalThis.innerWidth - margin) {
|
|
170
|
+
left = globalThis.innerWidth - tipW - margin;
|
|
171
171
|
}
|
|
172
172
|
if (left < margin) left = margin;
|
|
173
173
|
|
|
@@ -190,13 +190,13 @@ export class LinkTooltip {
|
|
|
190
190
|
// ---------------------------------------------------------------------------
|
|
191
191
|
|
|
192
192
|
_openLink() {
|
|
193
|
-
const url = this._activeAnchor
|
|
194
|
-
if (url)
|
|
193
|
+
const url = this._activeAnchor?.getAttribute('href');
|
|
194
|
+
if (url) globalThis.open(url, '_blank', 'noopener,noreferrer');
|
|
195
195
|
this._hide();
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
_copyLink() {
|
|
199
|
-
const url = this._activeAnchor
|
|
199
|
+
const url = this._activeAnchor?.getAttribute('href');
|
|
200
200
|
if (url) {
|
|
201
201
|
navigator.clipboard.writeText(url).catch(() => {
|
|
202
202
|
// Fallback for environments where clipboard API is unavailable
|
|
@@ -207,7 +207,7 @@ export class LinkTooltip {
|
|
|
207
207
|
document.body.appendChild(ta);
|
|
208
208
|
ta.select();
|
|
209
209
|
document.execCommand('copy');
|
|
210
|
-
|
|
210
|
+
ta.remove();
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
213
|
// Brief visual feedback
|
|
@@ -223,7 +223,7 @@ export class LinkTooltip {
|
|
|
223
223
|
this._hide();
|
|
224
224
|
|
|
225
225
|
// Place cursor inside the anchor, then open the link dialog (which pre-fills from selection)
|
|
226
|
-
const sel =
|
|
226
|
+
const sel = globalThis.getSelection();
|
|
227
227
|
const range = document.createRange();
|
|
228
228
|
range.selectNodeContents(anchor);
|
|
229
229
|
sel.removeAllRanges();
|
|
@@ -238,7 +238,7 @@ export class LinkTooltip {
|
|
|
238
238
|
this._hide();
|
|
239
239
|
|
|
240
240
|
// Select the anchor text, then remove the link
|
|
241
|
-
const sel =
|
|
241
|
+
const sel = globalThis.getSelection();
|
|
242
242
|
const range = document.createRange();
|
|
243
243
|
range.selectNode(anchor);
|
|
244
244
|
sel.removeAllRanges();
|
|
@@ -61,8 +61,8 @@ export class MarkdownShortcuts {
|
|
|
61
61
|
* @returns {{ text: string, range: Range, lineNode: Node } | null}
|
|
62
62
|
*/
|
|
63
63
|
_getLineContext() {
|
|
64
|
-
const sel =
|
|
65
|
-
if (!sel
|
|
64
|
+
const sel = globalThis.getSelection();
|
|
65
|
+
if (!sel?.rangeCount) return null;
|
|
66
66
|
const range = sel.getRangeAt(0);
|
|
67
67
|
if (!range.collapsed) return null;
|
|
68
68
|
|
|
@@ -87,7 +87,7 @@ export class MarkdownShortcuts {
|
|
|
87
87
|
|
|
88
88
|
_isBlock(node) {
|
|
89
89
|
if (node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
90
|
-
const display =
|
|
90
|
+
const display = globalThis.getComputedStyle(node).display;
|
|
91
91
|
return display === 'block' || display === 'list-item' || display === 'table-cell';
|
|
92
92
|
}
|
|
93
93
|
|
|
@@ -106,7 +106,7 @@ export class MarkdownShortcuts {
|
|
|
106
106
|
];
|
|
107
107
|
|
|
108
108
|
for (const { re, handler } of blockPatterns) {
|
|
109
|
-
const m =
|
|
109
|
+
const m = re.exec(text);
|
|
110
110
|
if (m) {
|
|
111
111
|
handler(m);
|
|
112
112
|
return true;
|
|
@@ -137,8 +137,8 @@ export class MarkdownShortcuts {
|
|
|
137
137
|
// ---------------------------------------------------------------------------
|
|
138
138
|
|
|
139
139
|
_selectLineAndDelete() {
|
|
140
|
-
const sel =
|
|
141
|
-
if (!sel
|
|
140
|
+
const sel = globalThis.getSelection();
|
|
141
|
+
if (!sel?.rangeCount) return;
|
|
142
142
|
const range = sel.getRangeAt(0);
|
|
143
143
|
// Select from the start of the block to the cursor and delete
|
|
144
144
|
const startRange = document.createRange();
|
|
@@ -188,8 +188,8 @@ export class MarkdownShortcuts {
|
|
|
188
188
|
// ---------------------------------------------------------------------------
|
|
189
189
|
|
|
190
190
|
_onInput() {
|
|
191
|
-
const sel =
|
|
192
|
-
if (!sel
|
|
191
|
+
const sel = globalThis.getSelection();
|
|
192
|
+
if (!sel?.rangeCount) return;
|
|
193
193
|
const range = sel.getRangeAt(0);
|
|
194
194
|
if (!range.collapsed) return;
|
|
195
195
|
|
|
@@ -215,7 +215,7 @@ export class MarkdownShortcuts {
|
|
|
215
215
|
|
|
216
216
|
const upToCursor = text.slice(0, offset);
|
|
217
217
|
for (const { re, tag } of inlineRules) {
|
|
218
|
-
const m =
|
|
218
|
+
const m = re.exec(upToCursor);
|
|
219
219
|
if (!m) continue;
|
|
220
220
|
|
|
221
221
|
const matchStart = upToCursor.length - m[0].length;
|
|
@@ -233,11 +233,8 @@ export class MarkdownShortcuts {
|
|
|
233
233
|
const beforeNode = document.createTextNode(before);
|
|
234
234
|
const afterNode = document.createTextNode('' + after);
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
parent.insertBefore(el, node);
|
|
239
|
-
parent.insertBefore(afterNode, node);
|
|
240
|
-
parent.removeChild(node);
|
|
236
|
+
/** @type {ChildNode} */ (node).before(beforeNode, el, afterNode);
|
|
237
|
+
/** @type {ChildNode} */ (node).remove();
|
|
241
238
|
|
|
242
239
|
// Place cursor after the element (after the ZWS)
|
|
243
240
|
const newRange = document.createRange();
|
package/src/js/module/Mention.js
CHANGED
|
@@ -76,9 +76,7 @@ export class Mention {
|
|
|
76
76
|
|
|
77
77
|
destroy() {
|
|
78
78
|
clearTimeout(this._debounceTimer);
|
|
79
|
-
|
|
80
|
-
this._dropdown.parentNode.removeChild(this._dropdown);
|
|
81
|
-
}
|
|
79
|
+
this._dropdown?.remove();
|
|
82
80
|
this._dropdown = null;
|
|
83
81
|
this._disposers.forEach((d) => d());
|
|
84
82
|
this._disposers = [];
|
|
@@ -163,10 +161,10 @@ export class Mention {
|
|
|
163
161
|
let top = rect.bottom + 4;
|
|
164
162
|
let left = rect.left;
|
|
165
163
|
|
|
166
|
-
if (rect.bottom + ddh + 8 >
|
|
164
|
+
if (rect.bottom + ddh + 8 > globalThis.innerHeight) {
|
|
167
165
|
top = rect.top - ddh - 4;
|
|
168
166
|
}
|
|
169
|
-
left = Math.max(8, Math.min(left,
|
|
167
|
+
left = Math.max(8, Math.min(left, globalThis.innerWidth - ddw - 8));
|
|
170
168
|
|
|
171
169
|
dd.style.top = `${top}px`;
|
|
172
170
|
dd.style.left = `${left}px`;
|
|
@@ -196,7 +194,7 @@ export class Mention {
|
|
|
196
194
|
_captureCaretRect() {
|
|
197
195
|
// Prefer a range over the trigger character — it has non-zero width and
|
|
198
196
|
// getBoundingClientRect() reliably returns a valid rect.
|
|
199
|
-
if (this._triggerNode
|
|
197
|
+
if (this._triggerNode?.isConnected) {
|
|
200
198
|
try {
|
|
201
199
|
const r = document.createRange();
|
|
202
200
|
const end = Math.min(this._triggerOffset + 1, this._triggerNode.textContent.length);
|
|
@@ -211,7 +209,7 @@ export class Mention {
|
|
|
211
209
|
}
|
|
212
210
|
|
|
213
211
|
// Fallback: use getClientRects() on the current collapsed selection
|
|
214
|
-
const sel =
|
|
212
|
+
const sel = globalThis.getSelection();
|
|
215
213
|
if (!sel || !sel.rangeCount) return;
|
|
216
214
|
const rects = sel.getRangeAt(0).getClientRects();
|
|
217
215
|
if (rects.length > 0) {
|
|
@@ -224,7 +222,7 @@ export class Mention {
|
|
|
224
222
|
// ---------------------------------------------------------------------------
|
|
225
223
|
|
|
226
224
|
_getQueryAtCursor() {
|
|
227
|
-
const sel =
|
|
225
|
+
const sel = globalThis.getSelection();
|
|
228
226
|
if (!sel || !sel.rangeCount) return null;
|
|
229
227
|
const range = sel.getRangeAt(0);
|
|
230
228
|
if (!range.collapsed) return null;
|
|
@@ -305,7 +303,7 @@ export class Mention {
|
|
|
305
303
|
|
|
306
304
|
_onDocClick(e) {
|
|
307
305
|
if (!this._open) return;
|
|
308
|
-
if (this._dropdown
|
|
306
|
+
if (this._dropdown?.contains(e.target)) return;
|
|
309
307
|
this._hideDropdown();
|
|
310
308
|
}
|
|
311
309
|
|
|
@@ -325,7 +323,7 @@ export class Mention {
|
|
|
325
323
|
node.textContent = before + after;
|
|
326
324
|
|
|
327
325
|
// Move cursor to where the trigger was
|
|
328
|
-
const sel =
|
|
326
|
+
const sel = globalThis.getSelection();
|
|
329
327
|
const range = document.createRange();
|
|
330
328
|
range.setStart(node, this._triggerOffset);
|
|
331
329
|
range.collapse(true);
|
|
@@ -43,7 +43,7 @@ export class Placeholder {
|
|
|
43
43
|
// addition to regular whitespace before deciding if the editor is empty.
|
|
44
44
|
// Without this, a freshly-created checklist item or icon leaves a ZWS in
|
|
45
45
|
// the DOM that causes the placeholder to overlap real content (A-1).
|
|
46
|
-
const hasText = editable.textContent.
|
|
46
|
+
const hasText = editable.textContent.replaceAll('\u200B', '').trim().length > 0;
|
|
47
47
|
const isEmpty = !hasText &&
|
|
48
48
|
!editable.querySelector('img, table, hr, .an-video-wrapper');
|
|
49
49
|
editable.classList.toggle('an-placeholder', isEmpty && !isFocused);
|
|
@@ -69,9 +69,7 @@ export class ShortcutsDialog {
|
|
|
69
69
|
destroy() {
|
|
70
70
|
this._disposers.forEach((d) => d());
|
|
71
71
|
this._disposers = [];
|
|
72
|
-
|
|
73
|
-
this._dialog.parentNode.removeChild(this._dialog);
|
|
74
|
-
}
|
|
72
|
+
this._dialog?.remove();
|
|
75
73
|
this._dialog = null;
|
|
76
74
|
}
|
|
77
75
|
|
|
@@ -79,7 +77,7 @@ export class ShortcutsDialog {
|
|
|
79
77
|
if (this._dialog) {
|
|
80
78
|
this._dialog.style.display = 'flex';
|
|
81
79
|
this._removeTrap = trapFocus(this._dialog, () => this._close());
|
|
82
|
-
setTimeout(() => this._closeBtn
|
|
80
|
+
setTimeout(() => this._closeBtn?.focus(), 50);
|
|
83
81
|
}
|
|
84
82
|
}
|
|
85
83
|
|