autumnnote 1.5.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 +10 -8
- package/dist/autumnnote.css +324 -4
- package/dist/autumnnote.es.js +943 -526
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +935 -519
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +21 -3
- package/src/js/Context.js +23 -16
- package/src/js/core/detectLang.js +98 -0
- package/src/js/core/dom.js +67 -11
- 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 +32 -31
- package/src/js/core/range.js +8 -8
- package/src/js/editing/History.js +10 -10
- package/src/js/editing/Style.js +44 -44
- package/src/js/editing/Table.js +5 -7
- package/src/js/editing/Typing.js +19 -19
- package/src/js/i18n/en.js +2 -0
- package/src/js/i18n/vi.js +2 -0
- package/src/js/index.js +3 -2
- package/src/js/module/AutoSaveRestore.js +2 -4
- package/src/js/module/BubbleToolbar.js +51 -34
- package/src/js/module/Buttons.js +20 -18
- package/src/js/module/Clipboard.js +16 -17
- package/src/js/module/CodeTooltip.js +48 -24
- package/src/js/module/Codeview.js +5 -7
- package/src/js/module/ContextMenu.js +37 -37
- package/src/js/module/Editor.js +77 -26
- package/src/js/module/EmojiDialog.js +24 -18
- package/src/js/module/FindReplace.js +93 -66
- package/src/js/module/Fullscreen.js +1 -1
- package/src/js/module/IconDialog.js +39 -35
- package/src/js/module/ImageCropOverlay.js +13 -13
- package/src/js/module/ImageDialog.js +19 -13
- package/src/js/module/ImageResizer.js +5 -5
- package/src/js/module/ImageTooltip.js +20 -16
- package/src/js/module/LinkDialog.js +20 -14
- package/src/js/module/LinkTooltip.js +15 -12
- package/src/js/module/MarkdownShortcuts.js +11 -14
- package/src/js/module/Mention.js +11 -13
- package/src/js/module/Placeholder.js +1 -1
- package/src/js/module/ShortcutsDialog.js +2 -4
- package/src/js/module/Statusbar.js +5 -7
- package/src/js/module/TableTooltip.js +196 -36
- package/src/js/module/Toolbar.js +35 -34
- package/src/js/module/VideoDialog.js +16 -10
- package/src/js/module/VideoResizer.js +7 -9
- package/src/js/module/VideoTooltip.js +15 -10
- package/src/js/renderer.js +5 -3
- package/src/js/settings.js +53 -36
- package/src/styles/autumnnote.scss +332 -7
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* single / replace-all replacement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createElement, on, trapFocus } from '../core/dom.js';
|
|
10
|
+
import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
|
|
11
11
|
|
|
12
12
|
export class FindReplace {
|
|
13
13
|
/** @param {import('../Context.js').Context} context */
|
|
@@ -61,7 +61,7 @@ export class FindReplace {
|
|
|
61
61
|
this._disposers.forEach((d) => d());
|
|
62
62
|
this._disposers = [];
|
|
63
63
|
if (this._dialog && this._dialog.parentNode) {
|
|
64
|
-
this._dialog.
|
|
64
|
+
this._dialog.remove();
|
|
65
65
|
}
|
|
66
66
|
this._dialog = null;
|
|
67
67
|
}
|
|
@@ -120,8 +120,8 @@ export class FindReplace {
|
|
|
120
120
|
const title = this._dialog.querySelector('.an-dialog-title');
|
|
121
121
|
|
|
122
122
|
const isReplace = this._mode === 'replace';
|
|
123
|
-
if (replaceRow) replaceRow.style.display = isReplace ? '' : 'none';
|
|
124
|
-
if (replaceActions) replaceActions.style.display = isReplace ? '' : 'none';
|
|
123
|
+
if (replaceRow) /** @type {HTMLElement} */ (replaceRow).style.display = isReplace ? '' : 'none';
|
|
124
|
+
if (replaceActions) /** @type {HTMLElement} */ (replaceActions).style.display = isReplace ? '' : 'none';
|
|
125
125
|
if (title) title.textContent = isReplace ? this.context.locale.findReplace.findReplaceTitle : this.context.locale.findReplace.findTitle;
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -137,107 +137,133 @@ export class FindReplace {
|
|
|
137
137
|
'aria-modal': 'true',
|
|
138
138
|
'aria-label': L.findReplaceTitle,
|
|
139
139
|
});
|
|
140
|
-
const box = createElement('div', { class: 'an-dialog-box' });
|
|
140
|
+
const box = createElement('div', { class: 'an-dialog-box an-fr-box' });
|
|
141
141
|
|
|
142
|
-
// ----
|
|
143
|
-
const
|
|
142
|
+
// ---- Header: [icon][title] [×] ----
|
|
143
|
+
const header = createElement('div', { class: 'an-fr-header' });
|
|
144
|
+
const titleGroup = createElement('div', { class: 'an-dialog-title-group' });
|
|
145
|
+
const iconEl = createElement('span', { class: 'an-dialog-icon an-dialog-icon--sm' });
|
|
146
|
+
iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`;
|
|
144
147
|
const title = createElement('h3', { class: 'an-dialog-title' });
|
|
145
148
|
title.textContent = L.findTitle;
|
|
149
|
+
titleGroup.append(iconEl, title);
|
|
146
150
|
const closeBtn = createElement('button', {
|
|
147
151
|
type: 'button',
|
|
148
152
|
class: 'an-icon-close',
|
|
149
|
-
|
|
153
|
+
title: L.close,
|
|
154
|
+
'aria-label': L.close,
|
|
150
155
|
});
|
|
151
|
-
closeBtn.
|
|
156
|
+
closeBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
152
157
|
this._closeBtn = closeBtn;
|
|
153
|
-
|
|
154
|
-
box.appendChild(
|
|
158
|
+
header.append(titleGroup, closeBtn);
|
|
159
|
+
box.appendChild(header);
|
|
155
160
|
|
|
156
|
-
// ----
|
|
157
|
-
const
|
|
158
|
-
const findInput = createElement('input', {
|
|
161
|
+
// ---- Search bar: [input] [Aa] [↑] [↓] counter ----
|
|
162
|
+
const searchBar = createElement('div', { class: 'an-fr-search-bar' });
|
|
163
|
+
const findInput = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
159
164
|
type: 'text',
|
|
160
|
-
class: 'an-input',
|
|
165
|
+
class: 'an-input an-fr-input',
|
|
161
166
|
placeholder: L.findPlaceholder,
|
|
162
167
|
'aria-label': L.searchAriaLabel,
|
|
163
|
-
});
|
|
168
|
+
}));
|
|
164
169
|
this._findInput = findInput;
|
|
165
|
-
findRow.appendChild(findInput);
|
|
166
|
-
box.appendChild(findRow);
|
|
167
170
|
|
|
168
|
-
//
|
|
169
|
-
const
|
|
170
|
-
const caseLabel = createElement('label', { class: 'an-label an-label-inline' });
|
|
171
|
-
const caseCheckbox = createElement('input', {
|
|
171
|
+
// Case-sensitive toggle (visual button; hidden checkbox kept for state)
|
|
172
|
+
const caseCheckbox = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
172
173
|
type: 'checkbox',
|
|
174
|
+
style: 'display:none',
|
|
175
|
+
'aria-hidden': 'true',
|
|
176
|
+
}));
|
|
177
|
+
this._caseCheckbox = caseCheckbox;
|
|
178
|
+
const caseBtn = createElement('button', {
|
|
179
|
+
type: 'button',
|
|
180
|
+
class: 'an-fr-icon-btn',
|
|
181
|
+
title: 'Case sensitive',
|
|
173
182
|
'aria-label': 'Case sensitive',
|
|
174
183
|
});
|
|
175
|
-
|
|
176
|
-
|
|
184
|
+
caseBtn.textContent = 'Aa';
|
|
185
|
+
|
|
186
|
+
const prevBtn = createElement('button', {
|
|
187
|
+
type: 'button',
|
|
188
|
+
class: 'an-fr-icon-btn',
|
|
189
|
+
title: 'Previous (Shift+Enter)',
|
|
190
|
+
'aria-label': 'Previous',
|
|
191
|
+
});
|
|
192
|
+
prevBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>`;
|
|
193
|
+
|
|
194
|
+
const nextBtn = createElement('button', {
|
|
195
|
+
type: 'button',
|
|
196
|
+
class: 'an-fr-icon-btn',
|
|
197
|
+
title: 'Next (Enter)',
|
|
198
|
+
'aria-label': 'Next',
|
|
199
|
+
});
|
|
200
|
+
nextBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`;
|
|
201
|
+
|
|
177
202
|
const counter = createElement('span', { class: 'an-fr-counter' });
|
|
178
203
|
this._counterEl = counter;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const prevBtn = createElement('button', { type: 'button', class: 'an-btn' });
|
|
185
|
-
prevBtn.textContent = L.prevBtn;
|
|
186
|
-
const nextBtn = createElement('button', { type: 'button', class: 'an-btn an-btn-primary' });
|
|
187
|
-
nextBtn.textContent = L.nextBtn;
|
|
188
|
-
findActions.append(prevBtn, nextBtn);
|
|
189
|
-
box.appendChild(findActions);
|
|
190
|
-
|
|
191
|
-
// ---- Replace row (hidden by default) ----
|
|
204
|
+
|
|
205
|
+
searchBar.append(findInput, caseCheckbox, caseBtn, prevBtn, nextBtn, counter);
|
|
206
|
+
box.appendChild(searchBar);
|
|
207
|
+
|
|
208
|
+
// ---- Replace row: [input] [Replace] [All] (hidden by default) ----
|
|
192
209
|
const replaceRow = createElement('div', { class: 'an-fr-replace-row' });
|
|
193
210
|
replaceRow.style.display = 'none';
|
|
194
|
-
const replaceInput = createElement('input', {
|
|
211
|
+
const replaceInput = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
195
212
|
type: 'text',
|
|
196
|
-
class: 'an-input',
|
|
213
|
+
class: 'an-input an-fr-input',
|
|
197
214
|
placeholder: L.replacePlaceholder,
|
|
198
215
|
'aria-label': L.replaceAriaLabel,
|
|
199
|
-
});
|
|
216
|
+
}));
|
|
200
217
|
this._replaceInput = replaceInput;
|
|
201
|
-
|
|
218
|
+
const replaceBtn = createElement('button', { type: 'button', class: 'an-btn an-fr-replace-btn' });
|
|
219
|
+
replaceBtn.textContent = L.replaceBtn;
|
|
220
|
+
const replaceAllBtn = createElement('button', { type: 'button', class: 'an-btn an-btn-primary an-fr-replace-btn' });
|
|
221
|
+
replaceAllBtn.textContent = L.replaceAllBtn;
|
|
222
|
+
replaceRow.append(replaceInput, replaceBtn, replaceAllBtn);
|
|
202
223
|
box.appendChild(replaceRow);
|
|
203
224
|
|
|
204
|
-
//
|
|
205
|
-
const replaceActions = createElement('div', { class: 'an-
|
|
225
|
+
// Compat div — _updateMode toggles this; kept empty so existing logic works
|
|
226
|
+
const replaceActions = createElement('div', { class: 'an-fr-replace-actions' });
|
|
206
227
|
replaceActions.style.display = 'none';
|
|
207
|
-
const replaceBtn = createElement('button', { type: 'button', class: 'an-btn' });
|
|
208
|
-
replaceBtn.textContent = L.replaceBtn;
|
|
209
|
-
const replaceAllBtn = createElement('button', { type: 'button', class: 'an-btn an-btn-primary' });
|
|
210
|
-
replaceAllBtn.textContent = L.replaceAllBtn;
|
|
211
|
-
replaceActions.append(replaceBtn, replaceAllBtn);
|
|
212
228
|
box.appendChild(replaceActions);
|
|
213
229
|
|
|
214
230
|
overlay.appendChild(box);
|
|
231
|
+
makeDraggable(header, box);
|
|
215
232
|
|
|
216
233
|
// ---- Event bindings ----
|
|
217
234
|
const d1 = on(closeBtn, 'click', () => this._close());
|
|
218
235
|
const d2 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
|
|
219
236
|
const d3 = on(findInput, 'input', () => this._onSearch());
|
|
220
|
-
const d4 = on(
|
|
237
|
+
const d4 = on(caseBtn, 'click', () => {
|
|
238
|
+
this._caseSensitive = !this._caseSensitive;
|
|
239
|
+
caseCheckbox.checked = this._caseSensitive;
|
|
240
|
+
caseBtn.classList.toggle('an-fr-icon-btn--active', this._caseSensitive);
|
|
241
|
+
this._onSearch();
|
|
242
|
+
});
|
|
243
|
+
// Hidden checkbox change handler keeps backward-compat with programmatic toggles
|
|
244
|
+
const d5 = on(caseCheckbox, 'change', () => {
|
|
221
245
|
this._caseSensitive = caseCheckbox.checked;
|
|
246
|
+
caseBtn.classList.toggle('an-fr-icon-btn--active', this._caseSensitive);
|
|
222
247
|
this._onSearch();
|
|
223
248
|
});
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
const
|
|
229
|
-
|
|
249
|
+
const d6 = on(nextBtn, 'click', () => this._next());
|
|
250
|
+
const d7 = on(prevBtn, 'click', () => this._prev());
|
|
251
|
+
const d8 = on(replaceBtn, 'click', () => this._replace());
|
|
252
|
+
const d9 = on(replaceAllBtn, 'click', () => this._replaceAll());
|
|
253
|
+
const d10 = on(findInput, 'keydown', (e) => {
|
|
254
|
+
const ke = /** @type {KeyboardEvent} */ (e);
|
|
255
|
+
if (ke.key === 'Enter') {
|
|
230
256
|
e.preventDefault();
|
|
231
|
-
|
|
257
|
+
ke.shiftKey ? this._prev() : this._next();
|
|
232
258
|
}
|
|
233
259
|
});
|
|
234
|
-
const
|
|
235
|
-
if (e.key === 'Enter') {
|
|
260
|
+
const d11 = on(replaceInput, 'keydown', (e) => {
|
|
261
|
+
if (/** @type {KeyboardEvent} */ (e).key === 'Enter') {
|
|
236
262
|
e.preventDefault();
|
|
237
263
|
this._replace();
|
|
238
264
|
}
|
|
239
265
|
});
|
|
240
|
-
this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10);
|
|
266
|
+
this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11);
|
|
241
267
|
|
|
242
268
|
return overlay;
|
|
243
269
|
}
|
|
@@ -326,13 +352,14 @@ export class FindReplace {
|
|
|
326
352
|
// Cap results to prevent blocking the main thread on very large documents
|
|
327
353
|
const MAX_RESULTS = 500;
|
|
328
354
|
const walker = document.createTreeWalker(root, 0x4 /* NodeFilter.SHOW_TEXT */);
|
|
329
|
-
let node;
|
|
330
|
-
while (
|
|
355
|
+
let node = walker.nextNode();
|
|
356
|
+
while (node && results.length < MAX_RESULTS) {
|
|
331
357
|
re.lastIndex = 0;
|
|
332
358
|
let m;
|
|
333
|
-
while ((m = re.exec(node.textContent)) !== null && results.length < MAX_RESULTS) {
|
|
334
|
-
results.push({ node, start: m.index, end: m.index + m[0].length });
|
|
359
|
+
while ((m = re.exec(/** @type {Text} */ (node).textContent)) !== null && results.length < MAX_RESULTS) {
|
|
360
|
+
results.push({ node: /** @type {Text} */ (node), start: m.index, end: m.index + m[0].length });
|
|
335
361
|
}
|
|
362
|
+
node = walker.nextNode();
|
|
336
363
|
}
|
|
337
364
|
return results;
|
|
338
365
|
}
|
|
@@ -382,7 +409,7 @@ export class FindReplace {
|
|
|
382
409
|
const parent = match.mark.parentNode;
|
|
383
410
|
const textNode = document.createTextNode(replacement);
|
|
384
411
|
parent.insertBefore(textNode, match.mark);
|
|
385
|
-
|
|
412
|
+
match.mark.remove();
|
|
386
413
|
parent.normalize();
|
|
387
414
|
this.context.invoke('editor.afterCommand');
|
|
388
415
|
const savedIndex = this._currentIndex;
|
|
@@ -402,7 +429,7 @@ export class FindReplace {
|
|
|
402
429
|
if (!mark || !mark.parentNode) return;
|
|
403
430
|
const textNode = document.createTextNode(replacement);
|
|
404
431
|
mark.parentNode.insertBefore(textNode, mark);
|
|
405
|
-
mark.
|
|
432
|
+
mark.remove();
|
|
406
433
|
});
|
|
407
434
|
|
|
408
435
|
if (this.context.layoutInfo.editable) {
|
|
@@ -430,7 +457,7 @@ export class FindReplace {
|
|
|
430
457
|
const parent = mark.parentNode;
|
|
431
458
|
if (!parent) return;
|
|
432
459
|
while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
|
|
433
|
-
|
|
460
|
+
mark.remove();
|
|
434
461
|
});
|
|
435
462
|
// Merge adjacent text nodes after unwrapping
|
|
436
463
|
editable.normalize();
|
|
@@ -21,7 +21,7 @@ export class Fullscreen {
|
|
|
21
21
|
initialize() {
|
|
22
22
|
// Press Escape to exit fullscreen
|
|
23
23
|
const d = on(document, 'keydown', (event) => {
|
|
24
|
-
if (this._active && isKey(event, key.ESCAPE)) {
|
|
24
|
+
if (this._active && isKey(/** @type {KeyboardEvent} */ (event), key.ESCAPE)) {
|
|
25
25
|
this.deactivate();
|
|
26
26
|
}
|
|
27
27
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* IconDialog.js - Browse and insert FontAwesome Free icons
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { createElement, on, trapFocus } from '../core/dom.js';
|
|
5
|
+
import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
|
|
6
6
|
import { withSavedRange } from '../core/range.js';
|
|
7
7
|
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
@@ -281,7 +281,7 @@ export class IconDialog {
|
|
|
281
281
|
// Already present (icon element or stylesheet link)?
|
|
282
282
|
if (document.getElementById('an-fontawesome-css')) return;
|
|
283
283
|
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
|
|
284
|
-
.map((l) => l.href || '').join(' ');
|
|
284
|
+
.map((l) => /** @type {HTMLLinkElement} */ (l).href || '').join(' ');
|
|
285
285
|
if (/fontawesome|font-awesome/.test(links)) return;
|
|
286
286
|
if (document.querySelector('.fa-solid, .fas, .far, .fab')) return;
|
|
287
287
|
|
|
@@ -297,9 +297,7 @@ export class IconDialog {
|
|
|
297
297
|
destroy() {
|
|
298
298
|
this._disposers.forEach((d) => d());
|
|
299
299
|
this._disposers = [];
|
|
300
|
-
|
|
301
|
-
this._dialog.parentNode.removeChild(this._dialog);
|
|
302
|
-
}
|
|
300
|
+
this._dialog?.remove();
|
|
303
301
|
this._dialog = null;
|
|
304
302
|
}
|
|
305
303
|
|
|
@@ -344,19 +342,23 @@ export class IconDialog {
|
|
|
344
342
|
|
|
345
343
|
// Title row
|
|
346
344
|
const titleRow = createElement('div', { class: 'an-icon-title-row' });
|
|
345
|
+
const titleGroup = createElement('div', { class: 'an-dialog-title-group' });
|
|
346
|
+
const iconEl = createElement('span', { class: 'an-dialog-icon an-dialog-icon--sm' });
|
|
347
|
+
iconEl.innerHTML = `<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="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>`;
|
|
347
348
|
const title = createElement('h3', { class: 'an-dialog-title' });
|
|
348
349
|
title.textContent = L.title;
|
|
350
|
+
titleGroup.append(iconEl, title);
|
|
349
351
|
const closeBtn = createElement('button', { type: 'button', class: 'an-icon-close', 'aria-label': L.close });
|
|
350
352
|
closeBtn.innerHTML = '×';
|
|
351
|
-
titleRow.append(
|
|
353
|
+
titleRow.append(titleGroup, closeBtn);
|
|
352
354
|
|
|
353
355
|
// Search
|
|
354
|
-
const searchInput = createElement('input', {
|
|
356
|
+
const searchInput = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
355
357
|
type: 'search',
|
|
356
358
|
class: 'an-input an-icon-search',
|
|
357
359
|
placeholder: L.searchPlaceholder,
|
|
358
360
|
autocomplete: 'off',
|
|
359
|
-
});
|
|
361
|
+
}));
|
|
360
362
|
this._searchInput = searchInput;
|
|
361
363
|
|
|
362
364
|
// Category tabs
|
|
@@ -366,7 +368,7 @@ export class IconDialog {
|
|
|
366
368
|
catBar.appendChild(allTab);
|
|
367
369
|
ICON_CATEGORIES.forEach(({ id, label }) => {
|
|
368
370
|
const tab = createElement('button', { type: 'button', class: 'an-icon-cat', 'data-cat': id });
|
|
369
|
-
tab.textContent =
|
|
371
|
+
tab.textContent = L.categories?.[id] || label;
|
|
370
372
|
catBar.appendChild(tab);
|
|
371
373
|
});
|
|
372
374
|
this._catBar = catBar;
|
|
@@ -388,7 +390,7 @@ export class IconDialog {
|
|
|
388
390
|
|
|
389
391
|
const styleLabel = createElement('label', { class: 'an-label' });
|
|
390
392
|
styleLabel.textContent = L.style;
|
|
391
|
-
const styleSelect = createElement('select', { class: 'an-input an-icon-option-select' });
|
|
393
|
+
const styleSelect = /** @type {HTMLSelectElement} */ (createElement('select', { class: 'an-input an-icon-option-select' }));
|
|
392
394
|
[['fa-solid', 'Solid'], ['fa-regular', 'Regular'], ['fa-light', 'Light (Pro)']].forEach(([v, t]) => {
|
|
393
395
|
const opt = createElement('option', { value: v });
|
|
394
396
|
opt.textContent = t;
|
|
@@ -399,9 +401,9 @@ export class IconDialog {
|
|
|
399
401
|
|
|
400
402
|
const sizeLabel = createElement('label', { class: 'an-label' });
|
|
401
403
|
sizeLabel.textContent = L.size;
|
|
402
|
-
const sizeSelect = createElement('select', { class: 'an-input an-icon-option-select' });
|
|
404
|
+
const sizeSelect = /** @type {HTMLSelectElement} */ (createElement('select', { class: 'an-input an-icon-option-select' }));
|
|
403
405
|
[['', 'Inherit'], ['0.75em', '0.75em'], ['1em', '1em'], ['1.25em', '1.25em'], ['1.5em', '1.5em'], ['2em', '2em'], ['3em', '3em']].forEach(([v, t]) => {
|
|
404
|
-
const opt = createElement('option', { value: v });
|
|
406
|
+
const opt = /** @type {HTMLOptionElement} */ (createElement('option', { value: v }));
|
|
405
407
|
if (v === '1em') opt.selected = true;
|
|
406
408
|
opt.textContent = t;
|
|
407
409
|
sizeSelect.appendChild(opt);
|
|
@@ -410,11 +412,11 @@ export class IconDialog {
|
|
|
410
412
|
|
|
411
413
|
const colorLabel = createElement('label', { class: 'an-label' });
|
|
412
414
|
colorLabel.textContent = L.color;
|
|
413
|
-
const colorInput = createElement('input', { type: 'color', class: 'an-icon-color', value: '#000000' });
|
|
415
|
+
const colorInput = /** @type {HTMLInputElement} */ (createElement('input', { type: 'color', class: 'an-icon-color', value: '#000000' }));
|
|
414
416
|
this._colorInput = colorInput;
|
|
415
417
|
|
|
416
418
|
const useColorLabel = createElement('label', { class: 'an-label an-label-inline an-icon-use-color' });
|
|
417
|
-
const useColorCb = createElement('input', { type: 'checkbox', checked: '' });
|
|
419
|
+
const useColorCb = /** @type {HTMLInputElement} */ (createElement('input', { type: 'checkbox', checked: '' }));
|
|
418
420
|
this._useColorCb = useColorCb;
|
|
419
421
|
useColorLabel.append(useColorCb, document.createTextNode(L.useColor));
|
|
420
422
|
|
|
@@ -438,6 +440,7 @@ export class IconDialog {
|
|
|
438
440
|
|
|
439
441
|
box.append(titleRow, searchInput, catBar, grid, optRow, preview, btnRow);
|
|
440
442
|
overlay.appendChild(box);
|
|
443
|
+
makeDraggable(titleRow, box);
|
|
441
444
|
|
|
442
445
|
// -------------------------------------------------------------------------
|
|
443
446
|
// Events
|
|
@@ -449,7 +452,7 @@ export class IconDialog {
|
|
|
449
452
|
const d4 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
|
|
450
453
|
const d5 = on(searchInput, 'input', () => this._filterIcons(searchInput.value, this._activeCat));
|
|
451
454
|
const d6 = on(catBar, 'click', (e) => {
|
|
452
|
-
const tab = e.target
|
|
455
|
+
const tab = /** @type {HTMLElement} */ (/** @type {Element} */ (e.target)?.closest('[data-cat]'));
|
|
453
456
|
if (tab) {
|
|
454
457
|
this._activeCat = tab.dataset.cat;
|
|
455
458
|
this._updateCatTabs();
|
|
@@ -457,7 +460,7 @@ export class IconDialog {
|
|
|
457
460
|
}
|
|
458
461
|
});
|
|
459
462
|
const d7 = on(grid, 'click', (e) => {
|
|
460
|
-
const cell = e.target
|
|
463
|
+
const cell = /** @type {HTMLElement} */ (/** @type {Element} */ (e.target)?.closest('.an-icon-cell'));
|
|
461
464
|
if (cell) this._selectIcon(cell.dataset.name);
|
|
462
465
|
});
|
|
463
466
|
const d8 = on(styleSelect, 'change', () => this._updatePreview(this._selectedIcon));
|
|
@@ -476,7 +479,7 @@ export class IconDialog {
|
|
|
476
479
|
|
|
477
480
|
_updateCatTabs() {
|
|
478
481
|
this._catBar.querySelectorAll('.an-icon-cat').forEach((tab) => {
|
|
479
|
-
tab.classList.toggle('active', tab.dataset.cat === this._activeCat);
|
|
482
|
+
tab.classList.toggle('active', /** @type {HTMLElement} */ (tab).dataset.cat === this._activeCat);
|
|
480
483
|
});
|
|
481
484
|
}
|
|
482
485
|
|
|
@@ -484,12 +487,13 @@ export class IconDialog {
|
|
|
484
487
|
const q = (query || '').trim().toLowerCase();
|
|
485
488
|
let visibleCount = 0;
|
|
486
489
|
this._grid.querySelectorAll('.an-icon-cell').forEach((cell) => {
|
|
487
|
-
const
|
|
488
|
-
const
|
|
490
|
+
const hCell = /** @type {HTMLElement} */ (cell);
|
|
491
|
+
const name = hCell.dataset.name;
|
|
492
|
+
const cellCat = hCell.dataset.cat;
|
|
489
493
|
const matchesCat = !cat || cat === 'all' || cellCat === cat;
|
|
490
494
|
const matchesQuery = !q || name.includes(q);
|
|
491
495
|
const visible = matchesCat && matchesQuery;
|
|
492
|
-
|
|
496
|
+
hCell.style.display = visible ? '' : 'none';
|
|
493
497
|
if (visible) visibleCount++;
|
|
494
498
|
});
|
|
495
499
|
// Show empty state if needed
|
|
@@ -499,14 +503,14 @@ export class IconDialog {
|
|
|
499
503
|
empty.textContent = 'No icons found';
|
|
500
504
|
this._grid.appendChild(empty);
|
|
501
505
|
}
|
|
502
|
-
empty.style.display = visibleCount > 0 ? 'none' : '';
|
|
506
|
+
/** @type {HTMLElement} */ (empty).style.display = visibleCount > 0 ? 'none' : '';
|
|
503
507
|
}
|
|
504
508
|
|
|
505
509
|
_selectIcon(name) {
|
|
506
510
|
this._selectedIcon = name;
|
|
507
511
|
// Highlight selected cell
|
|
508
512
|
this._grid.querySelectorAll('.an-icon-cell').forEach((cell) => {
|
|
509
|
-
cell.classList.toggle('active', cell.dataset.name === name);
|
|
513
|
+
cell.classList.toggle('active', /** @type {HTMLElement} */ (cell).dataset.name === name);
|
|
510
514
|
});
|
|
511
515
|
// Enable insert button
|
|
512
516
|
this._insertBtn.removeAttribute('disabled');
|
|
@@ -520,10 +524,10 @@ export class IconDialog {
|
|
|
520
524
|
this._preview.innerHTML = '<span class="an-icon-preview-hint">Select an icon</span>';
|
|
521
525
|
return;
|
|
522
526
|
}
|
|
523
|
-
const cls =
|
|
524
|
-
const size =
|
|
525
|
-
const useColor = this._useColorCb
|
|
526
|
-
const color =
|
|
527
|
+
const cls = this._styleSelect?.value || 'fa-solid';
|
|
528
|
+
const size = this._sizeSelect?.value || '1em';
|
|
529
|
+
const useColor = this._useColorCb?.checked ?? false;
|
|
530
|
+
const color = useColor ? (this._colorInput?.value ?? '') : '';
|
|
527
531
|
const styleAttr = [
|
|
528
532
|
size ? `font-size:${size}` : '',
|
|
529
533
|
color ? `color:${color}` : '',
|
|
@@ -543,10 +547,10 @@ export class IconDialog {
|
|
|
543
547
|
_onInsert() {
|
|
544
548
|
if (!this._selectedIcon) return;
|
|
545
549
|
|
|
546
|
-
const cls =
|
|
547
|
-
const size =
|
|
548
|
-
const useColor = this._useColorCb
|
|
549
|
-
const color =
|
|
550
|
+
const cls = this._styleSelect?.value || 'fa-solid';
|
|
551
|
+
const size = this._sizeSelect?.value || '';
|
|
552
|
+
const useColor = this._useColorCb?.checked ?? false;
|
|
553
|
+
const color = useColor ? (this._colorInput?.value ?? '') : '';
|
|
550
554
|
const styleParts = [
|
|
551
555
|
size ? `font-size:${size}` : '',
|
|
552
556
|
color ? `color:${color}` : '',
|
|
@@ -566,8 +570,8 @@ export class IconDialog {
|
|
|
566
570
|
if (savedRange) savedRange.select();
|
|
567
571
|
|
|
568
572
|
// 2. Get the now-live range from the selection
|
|
569
|
-
const sel =
|
|
570
|
-
let range = sel
|
|
573
|
+
const sel = globalThis.getSelection();
|
|
574
|
+
let range = (sel?.rangeCount ?? 0) > 0 ? sel.getRangeAt(0) : null;
|
|
571
575
|
if (!range) {
|
|
572
576
|
range = document.createRange();
|
|
573
577
|
range.selectNodeContents(editable);
|
|
@@ -581,8 +585,8 @@ export class IconDialog {
|
|
|
581
585
|
// deleteContents() can drift the range endpoint outside the cell in some
|
|
582
586
|
// browsers. Save the cell reference first and re-anchor after deletion.
|
|
583
587
|
const _sc = range.startContainer;
|
|
584
|
-
const _tdAnchor = (_sc.nodeType === 1 ? _sc : _sc.parentElement)
|
|
585
|
-
?.closest
|
|
588
|
+
const _tdAnchor = /** @type {Element|null} */ (_sc.nodeType === 1 ? _sc : _sc.parentElement)
|
|
589
|
+
?.closest('td, th');
|
|
586
590
|
range.deleteContents();
|
|
587
591
|
if (_tdAnchor && _tdAnchor.isConnected && !_tdAnchor.contains(range.startContainer)) {
|
|
588
592
|
range.setStart(_tdAnchor, 0);
|
|
@@ -633,7 +637,7 @@ export class IconDialog {
|
|
|
633
637
|
if (this._dialog) {
|
|
634
638
|
this._dialog.style.display = 'flex';
|
|
635
639
|
this._removeTrap = trapFocus(this._dialog, () => this._close());
|
|
636
|
-
setTimeout(() => this._searchInput
|
|
640
|
+
setTimeout(() => this._searchInput?.focus(), 50);
|
|
637
641
|
}
|
|
638
642
|
}
|
|
639
643
|
|
|
@@ -66,7 +66,7 @@ function drawCropToCanvas(img, naturalRect, renderW, renderH) {
|
|
|
66
66
|
if (
|
|
67
67
|
img.src.startsWith('data:') ||
|
|
68
68
|
img.src.startsWith('blob:') ||
|
|
69
|
-
img.src.startsWith(
|
|
69
|
+
img.src.startsWith(globalThis.location.origin)
|
|
70
70
|
) {
|
|
71
71
|
tryDraw(img);
|
|
72
72
|
return;
|
|
@@ -230,7 +230,7 @@ export class ImageCropOverlay {
|
|
|
230
230
|
on(h, 'touchstart', (e) => {
|
|
231
231
|
e.preventDefault();
|
|
232
232
|
e.stopPropagation();
|
|
233
|
-
this._startHandleDrag({ clientX:
|
|
233
|
+
const te = /** @type {TouchEvent} */ (e); this._startHandleDrag({ clientX: te.touches[0].clientX, clientY: te.touches[0].clientY }, id);
|
|
234
234
|
}, { passive: false }),
|
|
235
235
|
);
|
|
236
236
|
this._handles[id] = h;
|
|
@@ -240,18 +240,18 @@ export class ImageCropOverlay {
|
|
|
240
240
|
/* ---- crop move drag ---- */
|
|
241
241
|
this._disposers.push(
|
|
242
242
|
on(cropBox, 'mousedown', (e) => {
|
|
243
|
-
if (e.target.classList.contains('an-crop-handle')) return;
|
|
243
|
+
if (/** @type {Element} */ (e.target).classList.contains('an-crop-handle')) return;
|
|
244
244
|
if (e.target !== cropBox && e.target !== grid) return;
|
|
245
245
|
e.preventDefault();
|
|
246
246
|
e.stopPropagation();
|
|
247
247
|
this._startBoxMove(e);
|
|
248
248
|
}),
|
|
249
249
|
on(cropBox, 'touchstart', (e) => {
|
|
250
|
-
if (e.target.classList.contains('an-crop-handle')) return;
|
|
250
|
+
if (/** @type {Element} */ (e.target).classList.contains('an-crop-handle')) return;
|
|
251
251
|
if (e.target !== cropBox && e.target !== grid) return;
|
|
252
252
|
e.preventDefault();
|
|
253
253
|
e.stopPropagation();
|
|
254
|
-
this._startBoxMove({ clientX:
|
|
254
|
+
const te2 = /** @type {TouchEvent} */ (e); this._startBoxMove({ clientX: te2.touches[0].clientX, clientY: te2.touches[0].clientY });
|
|
255
255
|
}, { passive: false }),
|
|
256
256
|
);
|
|
257
257
|
|
|
@@ -340,8 +340,8 @@ export class ImageCropOverlay {
|
|
|
340
340
|
this._cropBox.style.height = `${h}px`;
|
|
341
341
|
|
|
342
342
|
// Scrim cutout via clip-path polygon (punches a transparent hole)
|
|
343
|
-
const vw =
|
|
344
|
-
const vh =
|
|
343
|
+
const vw = globalThis.innerWidth;
|
|
344
|
+
const vh = globalThis.innerHeight;
|
|
345
345
|
// Outer rect → inner rectangle (counterclockwise) = hole
|
|
346
346
|
this._scrim.style.clipPath = [
|
|
347
347
|
`polygon(`,
|
|
@@ -367,7 +367,7 @@ export class ImageCropOverlay {
|
|
|
367
367
|
// Position toolbar below crop box (or above if near bottom)
|
|
368
368
|
const margin = 8;
|
|
369
369
|
let tbTop = y + h + margin;
|
|
370
|
-
if (tbTop + 40 >
|
|
370
|
+
if (tbTop + 40 > globalThis.innerHeight - margin) tbTop = y - 40 - margin;
|
|
371
371
|
this._toolbar.style.left = `${x}px`;
|
|
372
372
|
this._toolbar.style.top = `${tbTop}px`;
|
|
373
373
|
}
|
|
@@ -444,7 +444,7 @@ export class ImageCropOverlay {
|
|
|
444
444
|
_attachDocDrag(onMove) {
|
|
445
445
|
const onTouchMove = (e) => {
|
|
446
446
|
e.preventDefault();
|
|
447
|
-
onMove({ clientX:
|
|
447
|
+
const te3 = /** @type {TouchEvent} */ (e); onMove(/** @type {MouseEvent} */ (/** @type {unknown} */ ({ clientX: te3.touches[0].clientX, clientY: te3.touches[0].clientY })));
|
|
448
448
|
};
|
|
449
449
|
const cleanup = () => {
|
|
450
450
|
document.removeEventListener('mousemove', onMove);
|
|
@@ -500,7 +500,7 @@ export class ImageCropOverlay {
|
|
|
500
500
|
const outW = w;
|
|
501
501
|
const outH = h;
|
|
502
502
|
|
|
503
|
-
const naturalRect = { x: natX, y: natY, width: natW, height: natH };
|
|
503
|
+
const naturalRect = /** @type {DOMRect} */ (/** @type {unknown} */ ({ x: natX, y: natY, width: natW, height: natH }));
|
|
504
504
|
const canvas = await drawCropToCanvas(img, naturalRect, outW, outH);
|
|
505
505
|
|
|
506
506
|
if (!canvas) {
|
|
@@ -514,7 +514,7 @@ export class ImageCropOverlay {
|
|
|
514
514
|
}
|
|
515
515
|
|
|
516
516
|
// Determine output format: preserve JPEG, fall back to PNG
|
|
517
|
-
const fmt =
|
|
517
|
+
const fmt = /^data:image\/(jpe?g)/i.exec(img.src) ? 'image/jpeg' : 'image/png';
|
|
518
518
|
const quality = fmt === 'image/jpeg' ? 0.92 : undefined;
|
|
519
519
|
const newSrc = canvas.toDataURL(fmt, quality);
|
|
520
520
|
|
|
@@ -551,7 +551,7 @@ export class ImageCropOverlay {
|
|
|
551
551
|
].join(';');
|
|
552
552
|
banner.textContent = msg;
|
|
553
553
|
document.body.appendChild(banner);
|
|
554
|
-
setTimeout(() => {
|
|
554
|
+
setTimeout(() => { banner.remove(); }, 4000);
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
/**
|
|
@@ -563,7 +563,7 @@ export class ImageCropOverlay {
|
|
|
563
563
|
this._disposers = [];
|
|
564
564
|
|
|
565
565
|
[this._scrim, this._cropBox, this._toolbar].forEach((el) => {
|
|
566
|
-
|
|
566
|
+
el?.remove();
|
|
567
567
|
});
|
|
568
568
|
|
|
569
569
|
this._scrim = null;
|