autumnnote 1.6.2 → 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/dist/autumnnote.es.js +76 -187
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +76 -187
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/core/detectLang.js +1 -1
- package/src/js/core/dom.js +3 -5
- package/src/js/editing/Style.js +10 -10
- package/src/js/editing/Typing.js +5 -5
- package/src/js/index.js +1 -1
- package/src/js/module/BaseDialog.js +1 -1
- package/src/js/module/BubbleToolbar.js +2 -2
- package/src/js/module/Buttons.js +8 -8
- package/src/js/module/Editor.js +4 -4
- package/src/js/module/EmojiDialog.js +7 -44
- package/src/js/module/FindReplace.js +4 -18
- package/src/js/module/IconDialog.js +10 -42
- package/src/js/module/ImageCropOverlay.js +1 -1
- package/src/js/module/ImageResizer.js +1 -1
- package/src/js/module/Mention.js +1 -1
- package/src/js/module/ShortcutsDialog.js +20 -49
- package/src/js/module/TableTooltip.js +7 -7
- package/src/js/module/Toolbar.js +9 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autumnnote",
|
|
3
|
-
"version": "1.6.
|
|
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
|
|
16
|
+
if (!code?.trim()) return null;
|
|
17
17
|
const s = code.trim();
|
|
18
18
|
|
|
19
19
|
// ── TypeScript ─────────────────────────────────────────────────────────────
|
package/src/js/core/dom.js
CHANGED
|
@@ -305,11 +305,9 @@ export function trapFocus(container, onEscape) {
|
|
|
305
305
|
e.preventDefault();
|
|
306
306
|
/** @type {HTMLElement} */ (last).focus();
|
|
307
307
|
}
|
|
308
|
-
} else {
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
package/src/js/editing/Style.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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'));
|
package/src/js/editing/Typing.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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 (
|
|
344
|
+
if (cursorNode?.nodeType !== Node.TEXT_NODE) {
|
|
345
345
|
cursorNode = document.createTextNode('\u200B');
|
|
346
346
|
newLi.appendChild(cursorNode);
|
|
347
347
|
}
|
|
348
|
-
checkLi.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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;
|
package/src/js/module/Buttons.js
CHANGED
|
@@ -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
|
|
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 !!(
|
|
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
|
|
183
|
+
if (sel?.rangeCount) {
|
|
184
184
|
let el = /** @type {Element|null} */ (sel.getRangeAt(0).startContainer);
|
|
185
|
-
if (el
|
|
186
|
-
while (el
|
|
187
|
-
const size =
|
|
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
|
|
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
|
|
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 || '';
|
package/src/js/module/Editor.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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);
|
|
@@ -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,
|
|
7
|
-
import {
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
6
|
-
import {
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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' });
|
|
@@ -598,7 +580,7 @@ export class IconDialog {
|
|
|
598
580
|
// Keep a text anchor (\u200B) after the icon so ArrowLeft/ArrowRight and
|
|
599
581
|
// caret placement at end-of-line stay stable across browsers.
|
|
600
582
|
let caretTextNode = iconEl.nextSibling;
|
|
601
|
-
if (
|
|
583
|
+
if (caretTextNode?.nodeType !== Node.TEXT_NODE) {
|
|
602
584
|
caretTextNode = document.createTextNode('\u200B');
|
|
603
585
|
iconEl.parentNode.insertBefore(caretTextNode, iconEl.nextSibling);
|
|
604
586
|
} else if (!caretTextNode.textContent) {
|
|
@@ -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
|
}
|
|
@@ -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
|
|
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
|
|
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;
|
package/src/js/module/Mention.js
CHANGED
|
@@ -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
|
|
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];
|