autumnnote 1.6.0 → 1.6.1
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 +348 -342
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +341 -336
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +21 -3
- package/src/js/Context.js +16 -14
- 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/module/AutoSaveRestore.js +2 -4
- 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 +1 -1
- package/src/js/module/ImageResizer.js +4 -4
- package/src/js/module/ImageTooltip.js +14 -14
- package/src/js/module/LinkDialog.js +2 -2
- 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 +8 -8
- 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
package/src/js/editing/Typing.js
CHANGED
|
@@ -14,8 +14,8 @@ import { currentRange } from '../core/range.js';
|
|
|
14
14
|
// at ~120+ events/sec during normal typing.
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
16
16
|
const _FA_PATTERN = /\bfa-/;
|
|
17
|
-
const isFAIcon = (n) => !!(n
|
|
18
|
-
const isZwsAnchor = (n) => !!(n
|
|
17
|
+
const isFAIcon = (n) => !!(n?.nodeName === 'I' && _FA_PATTERN.test(n.className || ''));
|
|
18
|
+
const isZwsAnchor = (n) => !!(n?.nodeType === Node.TEXT_NODE && (n.textContent === '\u200B' || n.textContent === ''));
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Handles special keydown behaviour inside the editor.
|
|
@@ -26,7 +26,7 @@ const isZwsAnchor = (n) => !!(n && n.nodeType === Node.TEXT_NODE && (n.textConte
|
|
|
26
26
|
*/
|
|
27
27
|
export function handleKeydown(event, editable, options = {}) {
|
|
28
28
|
const moveCaret = (setFn) => {
|
|
29
|
-
const sel =
|
|
29
|
+
const sel = globalThis.getSelection();
|
|
30
30
|
if (!sel) return false;
|
|
31
31
|
const nr = document.createRange();
|
|
32
32
|
setFn(nr);
|
|
@@ -40,8 +40,8 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
40
40
|
// Backspace key — one-press deletion of a preceding FA icon (<i> element)
|
|
41
41
|
// -------------------------------------------------------------------------
|
|
42
42
|
if (isKey(event, key.BACKSPACE)) {
|
|
43
|
-
const sel =
|
|
44
|
-
if (sel
|
|
43
|
+
const sel = globalThis.getSelection();
|
|
44
|
+
if (sel?.rangeCount > 0) {
|
|
45
45
|
const r = sel.getRangeAt(0);
|
|
46
46
|
if (r.collapsed && r.startContainer.nodeType === Node.TEXT_NODE) {
|
|
47
47
|
const textNode = /** @type {ChildNode} */ (r.startContainer);
|
|
@@ -89,7 +89,7 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
89
89
|
// ArrowLeft / ArrowRight — one-press navigation across FA icon nodes
|
|
90
90
|
// -------------------------------------------------------------------------
|
|
91
91
|
if (isKey(event, key.LEFT) || isKey(event, key.RIGHT)) {
|
|
92
|
-
const sel =
|
|
92
|
+
const sel = globalThis.getSelection();
|
|
93
93
|
if (!sel || sel.rangeCount === 0) return false;
|
|
94
94
|
|
|
95
95
|
const r = sel.getRangeAt(0);
|
|
@@ -207,7 +207,7 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
// In a pre/code block, insert spaces using configured tabSize
|
|
210
|
-
if (para
|
|
210
|
+
if (para?.nodeName.toUpperCase() === 'PRE') {
|
|
211
211
|
if (event.shiftKey) return false;
|
|
212
212
|
event.preventDefault();
|
|
213
213
|
execCommand('insertText', ' '.repeat(options.tabSize || 4));
|
|
@@ -248,18 +248,18 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
248
248
|
// and leave an orphan <i> in the new paragraph — visually an "auto-created
|
|
249
249
|
// icon". Push the cursor to just after the <i> first, then fall through so
|
|
250
250
|
// the browser fires its default Enter at a safe text boundary.
|
|
251
|
-
if (el
|
|
251
|
+
if (el?.nodeName === 'I' && /\bfa-/.test(el.className || '')) {
|
|
252
252
|
const nr = document.createRange();
|
|
253
253
|
nr.setStartAfter(el);
|
|
254
254
|
nr.collapse(true);
|
|
255
|
-
const selI =
|
|
255
|
+
const selI = globalThis.getSelection();
|
|
256
256
|
if (selI) { selI.removeAllRanges(); selI.addRange(nr); }
|
|
257
257
|
return false; // cursor is now outside <i> — let browser default handle Enter
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
// Video wrapper — Enter should create a new paragraph after the wrapper,
|
|
261
261
|
// not split the wrapper's container and produce an empty video clone.
|
|
262
|
-
const videoWrapper = el
|
|
262
|
+
const videoWrapper = el?.closest('.an-video-wrapper');
|
|
263
263
|
if (videoWrapper) {
|
|
264
264
|
event.preventDefault();
|
|
265
265
|
const p = document.createElement('p');
|
|
@@ -268,18 +268,18 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
268
268
|
const nr = document.createRange();
|
|
269
269
|
nr.setStart(p, 0);
|
|
270
270
|
nr.collapse(true);
|
|
271
|
-
const sel =
|
|
271
|
+
const sel = globalThis.getSelection();
|
|
272
272
|
sel.removeAllRanges();
|
|
273
273
|
sel.addRange(nr);
|
|
274
274
|
return true;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
// Checklist — Enter creates new item; empty item exits the list
|
|
278
|
-
const checkLi = el
|
|
278
|
+
const checkLi = el?.closest('.an-checklist li');
|
|
279
279
|
if (checkLi) {
|
|
280
280
|
event.preventDefault();
|
|
281
281
|
const ul = checkLi.closest('.an-checklist');
|
|
282
|
-
const sel =
|
|
282
|
+
const sel = globalThis.getSelection();
|
|
283
283
|
let nativeRange = sel.getRangeAt(0);
|
|
284
284
|
|
|
285
285
|
// Helper: get trimmed text content of a li, excluding the checkbox INPUT.
|
|
@@ -358,14 +358,14 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
358
358
|
const para = closestPara(range.sc, editable);
|
|
359
359
|
|
|
360
360
|
// Enter in a pre/code block: insert a literal newline instead of a new block
|
|
361
|
-
if (para
|
|
361
|
+
if (para?.nodeName.toUpperCase() === 'PRE') {
|
|
362
362
|
event.preventDefault();
|
|
363
363
|
execCommand('insertText', '\n');
|
|
364
364
|
return true;
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
// Pressing Enter at the end of a blockquote should exit it
|
|
368
|
-
if (para
|
|
368
|
+
if (para?.nodeName.toUpperCase() === 'BLOCKQUOTE') {
|
|
369
369
|
const native = range.toNativeRange();
|
|
370
370
|
native.setEnd(para, para.childNodes.length);
|
|
371
371
|
if (native.toString() === '' && range.isCollapsed()) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Activated when both `autoSave` and `autoSaveRestore` options are true.
|
|
6
6
|
* On initialize it checks localStorage for a draft that is within the
|
|
7
|
-
* `autoSaveRestoreTimeout` day
|
|
7
|
+
* `autoSaveRestoreTimeout` day globalThis. If one is found a dismissible banner
|
|
8
8
|
* is prepended to the editor container.
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -118,9 +118,7 @@ export class AutoSaveRestore {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
_removeBanner() {
|
|
121
|
-
|
|
122
|
-
this._banner.parentNode.removeChild(this._banner);
|
|
123
|
-
}
|
|
121
|
+
this._banner?.remove();
|
|
124
122
|
this._banner = null;
|
|
125
123
|
}
|
|
126
124
|
}
|
|
@@ -44,13 +44,13 @@ const _ACTIONS = {
|
|
|
44
44
|
strikethrough: (ctx) => ctx.invoke('editor.strikethrough'),
|
|
45
45
|
link: (ctx) => ctx.invoke('linkDialog.show'),
|
|
46
46
|
removeFormat: (ctx) => {
|
|
47
|
-
const editable = ctx.layoutInfo
|
|
47
|
+
const editable = ctx.layoutInfo?.editable;
|
|
48
48
|
if (!editable) return;
|
|
49
49
|
editable.focus();
|
|
50
50
|
document.execCommand('removeFormat');
|
|
51
51
|
// Also strip inline style attributes which execCommand('removeFormat') misses
|
|
52
|
-
const sel =
|
|
53
|
-
if (sel
|
|
52
|
+
const sel = globalThis.getSelection();
|
|
53
|
+
if (sel?.rangeCount > 0 && !sel.getRangeAt(0).collapsed) {
|
|
54
54
|
const range = sel.getRangeAt(0);
|
|
55
55
|
const ancestor = range.commonAncestorContainer;
|
|
56
56
|
const root = /** @type {Element|null} */ (ancestor.nodeType === 1 ? ancestor : ancestor.parentElement);
|
|
@@ -115,16 +115,16 @@ export class BubbleToolbar {
|
|
|
115
115
|
const d6 = this.context.on('contextMenu:hide', () => {
|
|
116
116
|
this._contextMenuOpen = false;
|
|
117
117
|
});
|
|
118
|
-
const d7 = on(
|
|
119
|
-
const d8 = on(
|
|
118
|
+
const d7 = on(globalThis, 'scroll', () => this._hide(), { passive: true });
|
|
119
|
+
const d8 = on(globalThis, 'resize', () => this._hide(), { passive: true });
|
|
120
120
|
this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8);
|
|
121
121
|
return this;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
destroy() {
|
|
125
|
-
|
|
125
|
+
this._el?.remove();
|
|
126
126
|
this._el = null;
|
|
127
|
-
|
|
127
|
+
this._picker?.remove();
|
|
128
128
|
this._picker = null;
|
|
129
129
|
this._disposers.forEach((d) => d());
|
|
130
130
|
this._disposers = [];
|
|
@@ -259,8 +259,8 @@ export class BubbleToolbar {
|
|
|
259
259
|
|
|
260
260
|
_openColorPicker(type, anchorBtn) {
|
|
261
261
|
// Save current selection before the picker might shift focus
|
|
262
|
-
const sel =
|
|
263
|
-
if (sel
|
|
262
|
+
const sel = globalThis.getSelection();
|
|
263
|
+
if (sel?.rangeCount > 0) {
|
|
264
264
|
this._savedRange = sel.getRangeAt(0).cloneRange();
|
|
265
265
|
}
|
|
266
266
|
|
|
@@ -272,8 +272,8 @@ export class BubbleToolbar {
|
|
|
272
272
|
const noColorBtn = pickerAny._noColorBtn;
|
|
273
273
|
if (type === 'hiliteColor') {
|
|
274
274
|
if (!palette.contains(noColorBtn)) palette.appendChild(noColorBtn);
|
|
275
|
-
} else {
|
|
276
|
-
|
|
275
|
+
} else if (palette.contains(noColorBtn)) {
|
|
276
|
+
noColorBtn.remove();
|
|
277
277
|
}
|
|
278
278
|
|
|
279
279
|
// Seed the custom color input
|
|
@@ -292,7 +292,7 @@ export class BubbleToolbar {
|
|
|
292
292
|
// Align horizontally with the clicked button, clamped to viewport
|
|
293
293
|
const btnRect = anchorBtn.getBoundingClientRect();
|
|
294
294
|
let left = btnRect.left;
|
|
295
|
-
left = Math.max(8, Math.min(left,
|
|
295
|
+
left = Math.max(8, Math.min(left, globalThis.innerWidth - pw - 8));
|
|
296
296
|
|
|
297
297
|
this._picker.style.left = `${left}px`;
|
|
298
298
|
this._picker.style.top = `${top}px`;
|
|
@@ -305,11 +305,11 @@ export class BubbleToolbar {
|
|
|
305
305
|
|
|
306
306
|
/** Restore the saved selection, apply execCommand, update the color strip, then close the picker. */
|
|
307
307
|
_applyColor(type, color) {
|
|
308
|
-
const editable = this.context.layoutInfo
|
|
308
|
+
const editable = this.context.layoutInfo?.editable;
|
|
309
309
|
if (!editable || !this._savedRange) return;
|
|
310
310
|
|
|
311
311
|
editable.focus();
|
|
312
|
-
const sel =
|
|
312
|
+
const sel = globalThis.getSelection();
|
|
313
313
|
sel.removeAllRanges();
|
|
314
314
|
try { sel.addRange(this._savedRange.cloneRange()); } catch (_) { return; }
|
|
315
315
|
|
|
@@ -322,8 +322,8 @@ export class BubbleToolbar {
|
|
|
322
322
|
|
|
323
323
|
// Update the color strip on the corresponding button
|
|
324
324
|
const name = type === 'hiliteColor' ? 'hiliteColor' : 'foreColor';
|
|
325
|
-
const btn = this._el
|
|
326
|
-
const strip = btn
|
|
325
|
+
const btn = this._el?.querySelector(`[data-name="${name}"]`);
|
|
326
|
+
const strip = btn?.querySelector('.an-bubble-color-strip');
|
|
327
327
|
if (strip) /** @type {HTMLElement} */ (strip).style.background = color === 'transparent' ? 'transparent' : color;
|
|
328
328
|
|
|
329
329
|
this._closeColorPicker();
|
|
@@ -350,7 +350,7 @@ export class BubbleToolbar {
|
|
|
350
350
|
let left = rect.left + rect.width / 2 - bw / 2;
|
|
351
351
|
let top = rect.top - bh - gap;
|
|
352
352
|
|
|
353
|
-
left = Math.max(8, Math.min(left,
|
|
353
|
+
left = Math.max(8, Math.min(left, globalThis.innerWidth - bw - 8));
|
|
354
354
|
|
|
355
355
|
if (top < 8) {
|
|
356
356
|
top = rect.bottom + gap;
|
|
@@ -365,7 +365,7 @@ export class BubbleToolbar {
|
|
|
365
365
|
const overlapsVertically = top < ttRect.bottom + gap && top + bh > ttRect.top - gap;
|
|
366
366
|
if (overlapsVertically) {
|
|
367
367
|
top = rect.bottom + gap;
|
|
368
|
-
if (top + bh >
|
|
368
|
+
if (top + bh > globalThis.innerHeight - 8) top = ttRect.bottom + gap;
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
|
|
@@ -396,19 +396,19 @@ export class BubbleToolbar {
|
|
|
396
396
|
/** Read the current selection's color and update the color-strip indicators. */
|
|
397
397
|
_syncColorStrips() {
|
|
398
398
|
if (!this._el) return;
|
|
399
|
-
const sel =
|
|
399
|
+
const sel = globalThis.getSelection();
|
|
400
400
|
if (!sel || !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;
|
|
404
|
-
const cs =
|
|
404
|
+
const cs = globalThis.getComputedStyle(/** @type {Element} */ (node));
|
|
405
405
|
|
|
406
406
|
const foreBtn = this._el.querySelector('[data-name="foreColor"]');
|
|
407
|
-
const foreStrip = foreBtn
|
|
407
|
+
const foreStrip = foreBtn?.querySelector('.an-bubble-color-strip');
|
|
408
408
|
if (foreStrip) /** @type {HTMLElement} */ (foreStrip).style.background = cs.color || '#000000';
|
|
409
409
|
|
|
410
410
|
const hiliteBtn = this._el.querySelector('[data-name="hiliteColor"]');
|
|
411
|
-
const hiliteStrip = hiliteBtn
|
|
411
|
+
const hiliteStrip = hiliteBtn?.querySelector('.an-bubble-color-strip');
|
|
412
412
|
if (hiliteStrip) {
|
|
413
413
|
const bg = cs.backgroundColor;
|
|
414
414
|
/** @type {HTMLElement} */ (hiliteStrip).style.background = (!bg || bg === 'rgba(0, 0, 0, 0)' || bg === 'transparent') ? 'transparent' : bg;
|
|
@@ -426,7 +426,7 @@ export class BubbleToolbar {
|
|
|
426
426
|
// Keep toolbar visible while color picker is open
|
|
427
427
|
if (this._picker && this._picker.style.display !== 'none') return;
|
|
428
428
|
|
|
429
|
-
const sel =
|
|
429
|
+
const sel = globalThis.getSelection();
|
|
430
430
|
if (!sel || sel.isCollapsed || !sel.rangeCount) {
|
|
431
431
|
this._hide();
|
|
432
432
|
return;
|
|
@@ -458,8 +458,8 @@ export class BubbleToolbar {
|
|
|
458
458
|
_onMousedown(e) {
|
|
459
459
|
// Hide when clicking outside both the editable, the bubble toolbar, and the color picker
|
|
460
460
|
if (!this._visible) return;
|
|
461
|
-
if (this._el
|
|
462
|
-
if (this._picker
|
|
461
|
+
if (this._el?.contains(e.target)) return;
|
|
462
|
+
if (this._picker?.contains(e.target)) return;
|
|
463
463
|
const editable = this.context.layoutInfo.editable;
|
|
464
464
|
if (editable.contains(e.target)) return;
|
|
465
465
|
this._hide();
|
package/src/js/module/Buttons.js
CHANGED
|
@@ -101,7 +101,7 @@ export const underlineBtn = btn('underline', 'underline', 'Underline (Ctrl+U)',
|
|
|
101
101
|
// also check for a <u> ancestor in the DOM using startContainer for
|
|
102
102
|
// consistent behaviour across both collapsed and range selections.
|
|
103
103
|
if (document.queryCommandState('underline')) return true;
|
|
104
|
-
const sel =
|
|
104
|
+
const sel = globalThis.getSelection();
|
|
105
105
|
if (!sel || !sel.rangeCount) return false;
|
|
106
106
|
let sc = sel.getRangeAt(0).startContainer;
|
|
107
107
|
if (sc.nodeType === 3) sc = sc.parentElement;
|
|
@@ -179,7 +179,7 @@ export const fontSizeBtn = {
|
|
|
179
179
|
action: (ctx, value) => Style.fontSize(value, ctx.layoutInfo.editable),
|
|
180
180
|
getValue: (ctx) => {
|
|
181
181
|
try {
|
|
182
|
-
const sel =
|
|
182
|
+
const sel = globalThis.getSelection();
|
|
183
183
|
if (sel && sel.rangeCount) {
|
|
184
184
|
let el = /** @type {Element|null} */ (sel.getRangeAt(0).startContainer);
|
|
185
185
|
if (el && el.nodeType === 3) el = el.parentElement;
|
|
@@ -188,7 +188,7 @@ export const fontSizeBtn = {
|
|
|
188
188
|
if (size) return size;
|
|
189
189
|
}
|
|
190
190
|
// Fallback: read the base font size from the editable element itself
|
|
191
|
-
const editable = ctx
|
|
191
|
+
const editable = ctx?.layoutInfo?.editable;
|
|
192
192
|
if (editable) return editable.style.fontSize || '';
|
|
193
193
|
return '';
|
|
194
194
|
} catch { return ''; }
|
|
@@ -280,7 +280,7 @@ export const lineHeightBtn = {
|
|
|
280
280
|
action: (_ctx, value) => Style.lineHeight(value),
|
|
281
281
|
getValue: () => {
|
|
282
282
|
try {
|
|
283
|
-
const sel =
|
|
283
|
+
const sel = globalThis.getSelection();
|
|
284
284
|
if (!sel || !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);
|
|
@@ -139,7 +139,7 @@ export class Clipboard {
|
|
|
139
139
|
// Unwrap — replace el with its children
|
|
140
140
|
const parent = el.parentNode;
|
|
141
141
|
while (el.firstChild) parent.insertBefore(el.firstChild, el);
|
|
142
|
-
|
|
142
|
+
el.remove();
|
|
143
143
|
}
|
|
144
144
|
// Strip class and all data-* attributes from every remaining element
|
|
145
145
|
doc.querySelectorAll('*').forEach((el) => {
|
|
@@ -180,7 +180,7 @@ export class Clipboard {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
_onPaste(event) {
|
|
183
|
-
const clipboardData = event.clipboardData || /** @type {any} */ (
|
|
183
|
+
const clipboardData = event.clipboardData || /** @type {any} */ (globalThis).clipboardData;
|
|
184
184
|
if (!clipboardData) return;
|
|
185
185
|
|
|
186
186
|
// Consume and reset the one-shot plain-paste flag
|
|
@@ -247,7 +247,6 @@ export class Clipboard {
|
|
|
247
247
|
if (this.options.pasteStripAttributes) html = this._stripAttributes(html);
|
|
248
248
|
execCommand('insertHTML', html);
|
|
249
249
|
this.context.invoke('editor.afterCommand');
|
|
250
|
-
return;
|
|
251
250
|
}
|
|
252
251
|
|
|
253
252
|
// Otherwise let the browser handle paste natively
|
|
@@ -300,11 +299,11 @@ export class Clipboard {
|
|
|
300
299
|
}
|
|
301
300
|
|
|
302
301
|
// C2: Reject image formats that browsers cannot decode/display.
|
|
303
|
-
const UNSUPPORTED = ['image/tiff', 'image/x-tiff', 'image/bmp', 'image/x-bmp', 'image/x-ms-bmp'];
|
|
302
|
+
const UNSUPPORTED = new Set(['image/tiff', 'image/x-tiff', 'image/bmp', 'image/x-bmp', 'image/x-ms-bmp']);
|
|
304
303
|
const maxBytes = (this.options.maxImageSize || 5) * 1024 * 1024;
|
|
305
304
|
files.forEach((file) => {
|
|
306
305
|
if (!file || !file.type.startsWith('image/')) return;
|
|
307
|
-
if (UNSUPPORTED.
|
|
306
|
+
if (UNSUPPORTED.has(file.type)) {
|
|
308
307
|
const message = `Image format "${file.type}" is not supported for display in web browsers. Please convert to PNG, JPEG, or WebP first.`;
|
|
309
308
|
this.context.triggerEvent('imageError', { file, message });
|
|
310
309
|
console.warn('[AutumnNote]', message);
|
|
@@ -351,10 +350,10 @@ export class Clipboard {
|
|
|
351
350
|
*/
|
|
352
351
|
_dataUrlToBlob(dataUrl) {
|
|
353
352
|
const [header, b64] = dataUrl.split(',');
|
|
354
|
-
const mime =
|
|
353
|
+
const mime = /:(.*?);/.exec(header)?.[1] ?? 'image/png';
|
|
355
354
|
const binary = atob(b64);
|
|
356
355
|
const arr = new Uint8Array(binary.length);
|
|
357
|
-
for (let i = 0; i < binary.length; i++) arr[i] = binary.
|
|
356
|
+
for (let i = 0; i < binary.length; i++) arr[i] = binary.codePointAt(i);
|
|
358
357
|
return new Blob([arr], { type: mime });
|
|
359
358
|
}
|
|
360
359
|
|
|
@@ -438,7 +437,7 @@ export class Clipboard {
|
|
|
438
437
|
}
|
|
439
438
|
}
|
|
440
439
|
if (!range) return;
|
|
441
|
-
const sel =
|
|
440
|
+
const sel = globalThis.getSelection();
|
|
442
441
|
if (sel) {
|
|
443
442
|
sel.removeAllRanges();
|
|
444
443
|
sel.addRange(range);
|
|
@@ -456,10 +455,10 @@ export class Clipboard {
|
|
|
456
455
|
*/
|
|
457
456
|
_escapeHTML(str) {
|
|
458
457
|
return str
|
|
459
|
-
.
|
|
460
|
-
.
|
|
461
|
-
.
|
|
462
|
-
.
|
|
463
|
-
.
|
|
458
|
+
.replaceAll('&', '&')
|
|
459
|
+
.replaceAll('<', '<')
|
|
460
|
+
.replaceAll('>', '>')
|
|
461
|
+
.replaceAll('"', '"')
|
|
462
|
+
.replaceAll("'", ''');
|
|
464
463
|
}
|
|
465
464
|
}
|
|
@@ -68,7 +68,7 @@ export class CodeTooltip {
|
|
|
68
68
|
this._clearTimers();
|
|
69
69
|
this._disposers.forEach((d) => d());
|
|
70
70
|
this._disposers = [];
|
|
71
|
-
|
|
71
|
+
this._el?.remove();
|
|
72
72
|
this._el = null;
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -226,7 +226,7 @@ export class CodeTooltip {
|
|
|
226
226
|
let left = rect.left + (rect.width - tipW) / 2;
|
|
227
227
|
|
|
228
228
|
if (top < margin) top = rect.bottom + margin;
|
|
229
|
-
if (left + tipW >
|
|
229
|
+
if (left + tipW > globalThis.innerWidth - margin) left = globalThis.innerWidth - tipW - margin;
|
|
230
230
|
if (left < margin) left = margin;
|
|
231
231
|
|
|
232
232
|
// Tooltip uses position:fixed, so viewport coordinates are used directly.
|
|
@@ -241,7 +241,7 @@ export class CodeTooltip {
|
|
|
241
241
|
_syncWrapBtn() {
|
|
242
242
|
if (!this._activePre || !this._wrapBtn) return;
|
|
243
243
|
const wrapped = (this._activePre.style.whiteSpace || '').includes('pre-wrap')
|
|
244
|
-
||
|
|
244
|
+
|| globalThis.getComputedStyle(this._activePre).whiteSpace === 'pre-wrap';
|
|
245
245
|
this._wrapBtn.classList.toggle('active', wrapped);
|
|
246
246
|
this._wrapBtn.title = wrapped
|
|
247
247
|
? this.context.locale.tooltips.code.disableWordWrap
|
|
@@ -251,7 +251,7 @@ export class CodeTooltip {
|
|
|
251
251
|
_syncLangSelect() {
|
|
252
252
|
if (!this._activePre || !this._langSelect) return;
|
|
253
253
|
const codeEl = this._activePre.querySelector('code');
|
|
254
|
-
const fromAttr = this._activePre.
|
|
254
|
+
const fromAttr = this._activePre.dataset.language || '';
|
|
255
255
|
const fromClass = codeEl ? (_LANG_CLASS_RE.exec(codeEl.className) || [])[1] || '' : '';
|
|
256
256
|
this._langSelect.value = fromAttr || fromClass || '';
|
|
257
257
|
}
|
|
@@ -274,7 +274,7 @@ export class CodeTooltip {
|
|
|
274
274
|
document.body.appendChild(ta);
|
|
275
275
|
ta.select();
|
|
276
276
|
try { document.execCommand('copy'); this._flashCopied(); } catch (_) {}
|
|
277
|
-
|
|
277
|
+
ta.remove();
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
|
|
@@ -310,8 +310,7 @@ export class CodeTooltip {
|
|
|
310
310
|
applyLanguage(pre, lang) {
|
|
311
311
|
if (!pre || !lang) return;
|
|
312
312
|
// Temporarily set activePre so _onLangChange can target it
|
|
313
|
-
const savedPre
|
|
314
|
-
const savedSelect = this._langSelect ? this._langSelect.value : '';
|
|
313
|
+
const savedPre = this._activePre;
|
|
315
314
|
this._activePre = pre;
|
|
316
315
|
if (this._langSelect) this._langSelect.value = lang;
|
|
317
316
|
this._onLangChange();
|
|
@@ -320,14 +319,13 @@ export class CodeTooltip {
|
|
|
320
319
|
this._activePre = savedPre || pre;
|
|
321
320
|
// Don't restore savedPre if it was null — keep `pre` as activePre so that
|
|
322
321
|
// the tooltip select is correct the first time the user hovers over it.
|
|
323
|
-
void savedSelect;
|
|
324
322
|
}
|
|
325
323
|
|
|
326
324
|
_onLangChange() {
|
|
327
325
|
const pre = this._activePre;
|
|
328
326
|
if (!pre) return;
|
|
329
327
|
const lang = this._langSelect.value;
|
|
330
|
-
const _w = /** @type {any} */ (
|
|
328
|
+
const _w = /** @type {any} */ (globalThis);
|
|
331
329
|
|
|
332
330
|
// Ensure a <code> child exists (Prism targets <pre><code class="language-xxx">)
|
|
333
331
|
let codeEl = pre.querySelector('code');
|
|
@@ -342,9 +340,9 @@ export class CodeTooltip {
|
|
|
342
340
|
// Mirror language class on <pre> so Prism CSS theme targets it (pre[class*='language-'])
|
|
343
341
|
pre.className = lang ? `language-${lang}` : '';
|
|
344
342
|
if (lang) {
|
|
345
|
-
pre.
|
|
343
|
+
pre.dataset.language = lang;
|
|
346
344
|
} else {
|
|
347
|
-
pre.
|
|
345
|
+
delete pre.dataset.language;
|
|
348
346
|
}
|
|
349
347
|
|
|
350
348
|
// Trigger Prism if available.
|
|
@@ -357,7 +355,7 @@ export class CodeTooltip {
|
|
|
357
355
|
};
|
|
358
356
|
|
|
359
357
|
if (lang) {
|
|
360
|
-
if (
|
|
358
|
+
if (_w.Prism !== undefined) {
|
|
361
359
|
// Grammar already loaded — highlight immediately
|
|
362
360
|
if (_w.Prism.languages[lang]) {
|
|
363
361
|
applyPrism();
|
|
@@ -387,7 +385,7 @@ export class CodeTooltip {
|
|
|
387
385
|
* Called once at initialize time. Fire-and-forget; errors are silent.
|
|
388
386
|
*/
|
|
389
387
|
_ensurePrism() {
|
|
390
|
-
const _w = /** @type {any} */ (
|
|
388
|
+
const _w = /** @type {any} */ (globalThis);
|
|
391
389
|
if (!this.context.options.codeHighlight || _w.Prism) return;
|
|
392
390
|
const cdn = this.context.options.codeHighlightCDN;
|
|
393
391
|
const themeHref = `${cdn}/themes/prism-tomorrow.min.css`;
|
|
@@ -422,14 +420,14 @@ export class CodeTooltip {
|
|
|
422
420
|
* @param {Function} cb – called once the grammar is ready
|
|
423
421
|
*/
|
|
424
422
|
_loadPrismComponent(lang, cb) {
|
|
425
|
-
const _w = /** @type {any} */ (
|
|
423
|
+
const _w = /** @type {any} */ (globalThis);
|
|
426
424
|
const cdn = this.context.options.codeHighlightCDN;
|
|
427
425
|
const src = `${cdn}/components/prism-${lang}.min.js`;
|
|
428
426
|
// Avoid loading the same component twice
|
|
429
427
|
if (document.querySelector(`script[src="${src}"]`)) {
|
|
430
428
|
// Already in DOM — might still be loading; poll briefly then call cb
|
|
431
429
|
const poll = setInterval(() => {
|
|
432
|
-
if (_w.Prism
|
|
430
|
+
if (_w.Prism?.languages[lang]) {
|
|
433
431
|
clearInterval(poll);
|
|
434
432
|
cb();
|
|
435
433
|
}
|
|
@@ -466,7 +464,7 @@ export class CodeTooltip {
|
|
|
466
464
|
const pre = this._activePre;
|
|
467
465
|
if (!pre) return;
|
|
468
466
|
this._hide();
|
|
469
|
-
|
|
467
|
+
pre.remove();
|
|
470
468
|
this.context.invoke('editor.afterCommand');
|
|
471
469
|
}
|
|
472
470
|
}
|
|
@@ -26,9 +26,7 @@ export class Codeview {
|
|
|
26
26
|
destroy() {
|
|
27
27
|
this._disposers.forEach((d) => d());
|
|
28
28
|
this._disposers = [];
|
|
29
|
-
|
|
30
|
-
this._textarea.parentNode.removeChild(this._textarea);
|
|
31
|
-
}
|
|
29
|
+
this._textarea?.remove();
|
|
32
30
|
this._textarea = null;
|
|
33
31
|
}
|
|
34
32
|
|
|
@@ -74,7 +72,7 @@ export class Codeview {
|
|
|
74
72
|
const { editable } = this.context.layoutInfo;
|
|
75
73
|
// Sanitise the HTML typed in the textarea before applying (allow iframes for video embeds)
|
|
76
74
|
editable.innerHTML = sanitiseHTML(this._textarea.value, { allowIframes: true });
|
|
77
|
-
this._textarea.
|
|
75
|
+
this._textarea.remove();
|
|
78
76
|
this._textarea = null;
|
|
79
77
|
editable.style.display = '';
|
|
80
78
|
this._active = false;
|
|
@@ -108,7 +106,7 @@ export class Codeview {
|
|
|
108
106
|
.map((line) => {
|
|
109
107
|
const stripped = line.trim();
|
|
110
108
|
if (!stripped) return '';
|
|
111
|
-
if (
|
|
109
|
+
if (stripped.startsWith('</')) indent = Math.max(0, indent - 1);
|
|
112
110
|
const out = ' '.repeat(indent) + stripped;
|
|
113
111
|
if (
|
|
114
112
|
/^<[^/!][^>]*[^/]>/.test(stripped) &&
|