autumnnote 2.1.0 → 2.3.0

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.
Files changed (65) hide show
  1. package/README.md +74 -6
  2. package/dist/autumnnote.cjs +27 -22
  3. package/dist/autumnnote.es.js +474 -591
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.min.js +27 -22
  6. package/dist/autumnnote.umd.js +27 -22
  7. package/dist/autumnnote.umd.js.map +1 -1
  8. package/dist/icon-data-V0Xqv-wX.js +255 -0
  9. package/dist/icon-data-V0Xqv-wX.js.map +1 -0
  10. package/package.json +12 -3
  11. package/types/index.d.ts +8 -0
  12. package/src/js/Context.js +0 -854
  13. package/src/js/core/detectLang.js +0 -98
  14. package/src/js/core/dom.js +0 -372
  15. package/src/js/core/env.js +0 -38
  16. package/src/js/core/key.js +0 -66
  17. package/src/js/core/lists.js +0 -121
  18. package/src/js/core/markdown.js +0 -695
  19. package/src/js/core/range.js +0 -194
  20. package/src/js/core/sanitise.js +0 -304
  21. package/src/js/editing/History.js +0 -266
  22. package/src/js/editing/Style.js +0 -812
  23. package/src/js/editing/Table.js +0 -105
  24. package/src/js/editing/Typing.js +0 -397
  25. package/src/js/index.js +0 -193
  26. package/src/js/index.umd.js +0 -17
  27. package/src/js/module/AutoSaveRestore.js +0 -125
  28. package/src/js/module/BaseDialog.js +0 -133
  29. package/src/js/module/BaseMediaTooltip.js +0 -142
  30. package/src/js/module/BaseResizer.js +0 -322
  31. package/src/js/module/BubbleToolbar.js +0 -483
  32. package/src/js/module/Buttons.js +0 -399
  33. package/src/js/module/Clipboard.js +0 -579
  34. package/src/js/module/CodeTooltip.js +0 -493
  35. package/src/js/module/Codeview.js +0 -125
  36. package/src/js/module/ContextMenu.js +0 -621
  37. package/src/js/module/Editor.js +0 -747
  38. package/src/js/module/EmojiDialog.js +0 -254
  39. package/src/js/module/FindReplace.js +0 -512
  40. package/src/js/module/Fullscreen.js +0 -80
  41. package/src/js/module/IconDialog.js +0 -618
  42. package/src/js/module/ImageCropOverlay.js +0 -586
  43. package/src/js/module/ImageDialog.js +0 -193
  44. package/src/js/module/ImageResizer.js +0 -42
  45. package/src/js/module/ImageTooltip.js +0 -285
  46. package/src/js/module/LinkDialog.js +0 -145
  47. package/src/js/module/LinkTooltip.js +0 -250
  48. package/src/js/module/MarkdownShortcuts.js +0 -250
  49. package/src/js/module/Mention.js +0 -365
  50. package/src/js/module/Placeholder.js +0 -51
  51. package/src/js/module/ShortcutsDialog.js +0 -111
  52. package/src/js/module/SlashMenu.js +0 -376
  53. package/src/js/module/Statusbar.js +0 -246
  54. package/src/js/module/TableTooltip.js +0 -1392
  55. package/src/js/module/Toolbar.js +0 -855
  56. package/src/js/module/VideoDialog.js +0 -193
  57. package/src/js/module/VideoResizer.js +0 -66
  58. package/src/js/module/VideoTooltip.js +0 -248
  59. package/src/js/module/emoji-data.js +0 -496
  60. package/src/js/module/table-grid.js +0 -102
  61. package/src/js/module/table-icons.js +0 -33
  62. package/src/js/renderer.js +0 -120
  63. package/src/js/settings.js +0 -214
  64. package/src/styles/_variables.scss +0 -48
  65. package/src/styles/autumnnote.scss +0 -2877
@@ -1,512 +0,0 @@
1
- /**
2
- * FindReplace.js - Find & Replace dialog
3
- *
4
- * Opens via Ctrl+F (find only) or Ctrl+H (find + replace).
5
- * Uses TreeWalker to locate text matches and wraps them with <mark> elements
6
- * for highlighting. Supports case-sensitive search, Prev/Next navigation and
7
- * single / replace-all replacement.
8
- */
9
-
10
- import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
11
- import { BaseDialog } from './BaseDialog.js';
12
-
13
- export class FindReplace extends BaseDialog {
14
- /** @param {import('../Context.js').Context} context */
15
- constructor(context) {
16
- super(context);
17
-
18
- /** @type {HTMLInputElement|null} */
19
- this._findInput = null;
20
- /** @type {HTMLInputElement|null} */
21
- this._replaceInput = null;
22
- /** @type {HTMLInputElement|null} */
23
- this._caseCheckbox = null;
24
- /** @type {HTMLElement|null} */
25
- this._counterEl = null;
26
- /** @type {HTMLElement|null} */
27
- this._closeBtn = null;
28
-
29
- /** Live matches — each entry is { mark: HTMLElement } after highlighting */
30
- this._matches = [];
31
- this._currentIndex = -1;
32
- this._caseSensitive = false;
33
- this._useRegex = false;
34
- this._wholeWord = false;
35
- /** @type {'find'|'replace'} */
36
- this._mode = 'find';
37
-
38
- /** Cached compiled regex — reused when query and case-sensitivity are unchanged */
39
- this._queryRegex = null;
40
- this._lastQuery = null;
41
- this._lastCaseSensitive = null;
42
- this._lastUseRegex = null;
43
- this._lastWholeWord = null;
44
-
45
- this._focusTimer = null;
46
- }
47
-
48
- // ---------------------------------------------------------------------------
49
- // Lifecycle
50
- // ---------------------------------------------------------------------------
51
-
52
- destroy() {
53
- clearTimeout(this._focusTimer);
54
- this._focusTimer = null;
55
- this._clearHighlights();
56
- super.destroy();
57
- }
58
-
59
- // ---------------------------------------------------------------------------
60
- // Public API
61
- // ---------------------------------------------------------------------------
62
-
63
- /**
64
- * Opens the dialog in 'find' or 'replace' mode.
65
- * @param {'find'|'replace'} [mode='find']
66
- */
67
- show(mode = 'find') {
68
- this._mode = mode;
69
- this._updateMode();
70
- this._open();
71
- // Pre-select whatever was previously typed so the user can retype immediately
72
- clearTimeout(this._focusTimer);
73
- this._focusTimer = setTimeout(() => {
74
- if (this._findInput) {
75
- this._findInput.select();
76
- this._findInput.focus();
77
- }
78
- }, 50);
79
- }
80
-
81
- // ---------------------------------------------------------------------------
82
- // Open / close
83
- // ---------------------------------------------------------------------------
84
-
85
- _open() {
86
- if (!this._dialog) return;
87
- // If already visible, just focus the search input — don't re-trap.
88
- if (this._dialog.style.display === 'flex') {
89
- if (this._findInput) this._findInput.focus();
90
- return;
91
- }
92
- this._dialog.style.display = 'flex';
93
- // Release any previous trap before installing a new one.
94
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
95
- this._removeTrap = trapFocus(this._dialog, () => this._close());
96
- }
97
-
98
- _close() {
99
- this._clearHighlights();
100
- if (this._dialog) this._dialog.style.display = 'none';
101
- if (this._removeTrap) { this._removeTrap(); this._removeTrap = null; }
102
- // Return focus to the editor
103
- this.context.invoke('editor.focus');
104
- }
105
-
106
- _updateMode() {
107
- if (!this._dialog) return;
108
- const replaceRow = this._dialog.querySelector('.an-fr-replace-row');
109
- const replaceActions = this._dialog.querySelector('.an-fr-replace-actions');
110
- const title = this._dialog.querySelector('.an-dialog-title');
111
-
112
- const isReplace = this._mode === 'replace';
113
- if (replaceRow) /** @type {HTMLElement} */ (replaceRow).style.display = isReplace ? '' : 'none';
114
- if (replaceActions) /** @type {HTMLElement} */ (replaceActions).style.display = isReplace ? '' : 'none';
115
- if (title) title.textContent = isReplace ? this.context.locale.findReplace.findReplaceTitle : this.context.locale.findReplace.findTitle;
116
- }
117
-
118
- // ---------------------------------------------------------------------------
119
- // Build dialog
120
- // ---------------------------------------------------------------------------
121
-
122
- _buildDialog() {
123
- const L = this.context.locale.findReplace;
124
- const overlay = createElement('div', {
125
- class: 'an-dialog-overlay an-fr-dialog',
126
- role: 'dialog',
127
- 'aria-modal': 'true',
128
- 'aria-label': L.findReplaceTitle,
129
- });
130
- const box = createElement('div', { class: 'an-dialog-box an-fr-box' });
131
-
132
- // ---- Header: [icon][title] [×] ----
133
- const header = createElement('div', { class: 'an-fr-header' });
134
- const titleGroup = createElement('div', { class: 'an-dialog-title-group' });
135
- const iconEl = createElement('span', { class: 'an-dialog-icon an-dialog-icon--sm' });
136
- 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>`;
137
- const title = createElement('h3', { class: 'an-dialog-title' });
138
- title.textContent = L.findTitle;
139
- titleGroup.append(iconEl, title);
140
- const closeBtn = createElement('button', {
141
- type: 'button',
142
- class: 'an-icon-close',
143
- title: L.close,
144
- 'aria-label': L.close,
145
- });
146
- 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>`;
147
- this._closeBtn = closeBtn;
148
- header.append(titleGroup, closeBtn);
149
- box.appendChild(header);
150
-
151
- // ---- Search bar: [input] [Aa] [↑] [↓] counter ----
152
- const searchBar = createElement('div', { class: 'an-fr-search-bar' });
153
- const findInput = /** @type {HTMLInputElement} */ (createElement('input', {
154
- type: 'text',
155
- class: 'an-input an-fr-input',
156
- placeholder: L.findPlaceholder,
157
- 'aria-label': L.searchAriaLabel,
158
- }));
159
- this._findInput = findInput;
160
-
161
- // Case-sensitive toggle (visual button; hidden checkbox kept for state)
162
- const caseCheckbox = /** @type {HTMLInputElement} */ (createElement('input', {
163
- type: 'checkbox',
164
- style: 'display:none',
165
- 'aria-hidden': 'true',
166
- }));
167
- this._caseCheckbox = caseCheckbox;
168
- const caseBtn = createElement('button', {
169
- type: 'button',
170
- class: 'an-fr-icon-btn',
171
- title: 'Case sensitive',
172
- 'aria-label': 'Case sensitive',
173
- });
174
- caseBtn.textContent = 'Aa';
175
-
176
- const regexBtn = createElement('button', {
177
- type: 'button',
178
- class: 'an-fr-icon-btn',
179
- title: L.useRegex,
180
- 'aria-label': L.useRegex,
181
- });
182
- regexBtn.textContent = '.*';
183
-
184
- const wholeWordBtn = createElement('button', {
185
- type: 'button',
186
- class: 'an-fr-icon-btn',
187
- title: L.wholeWord,
188
- 'aria-label': L.wholeWord,
189
- });
190
- wholeWordBtn.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" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="7" width="18" height="10" rx="2"/><line x1="7" y1="21" x2="7" y2="17"/><line x1="17" y1="21" x2="17" y2="17"/></svg>`;
191
-
192
- const prevBtn = createElement('button', {
193
- type: 'button',
194
- class: 'an-fr-icon-btn',
195
- title: 'Previous (Shift+Enter)',
196
- 'aria-label': 'Previous',
197
- });
198
- 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>`;
199
-
200
- const nextBtn = createElement('button', {
201
- type: 'button',
202
- class: 'an-fr-icon-btn',
203
- title: 'Next (Enter)',
204
- 'aria-label': 'Next',
205
- });
206
- 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>`;
207
-
208
- const counter = createElement('span', { class: 'an-fr-counter' });
209
- this._counterEl = counter;
210
-
211
- searchBar.append(findInput, caseCheckbox, caseBtn, regexBtn, wholeWordBtn, prevBtn, nextBtn, counter);
212
- box.appendChild(searchBar);
213
-
214
- // ---- Replace row: [input] [Replace] [All] (hidden by default) ----
215
- const replaceRow = createElement('div', { class: 'an-fr-replace-row' });
216
- replaceRow.style.display = 'none';
217
- const replaceInput = /** @type {HTMLInputElement} */ (createElement('input', {
218
- type: 'text',
219
- class: 'an-input an-fr-input',
220
- placeholder: L.replacePlaceholder,
221
- 'aria-label': L.replaceAriaLabel,
222
- }));
223
- this._replaceInput = replaceInput;
224
- const replaceBtn = createElement('button', { type: 'button', class: 'an-btn an-fr-replace-btn' });
225
- replaceBtn.textContent = L.replaceBtn;
226
- const replaceAllBtn = createElement('button', { type: 'button', class: 'an-btn an-btn-primary an-fr-replace-btn' });
227
- replaceAllBtn.textContent = L.replaceAllBtn;
228
- replaceRow.append(replaceInput, replaceBtn, replaceAllBtn);
229
- box.appendChild(replaceRow);
230
-
231
- // Compat div — _updateMode toggles this; kept empty so existing logic works
232
- const replaceActions = createElement('div', { class: 'an-fr-replace-actions' });
233
- replaceActions.style.display = 'none';
234
- box.appendChild(replaceActions);
235
-
236
- overlay.appendChild(box);
237
- makeDraggable(header, box);
238
-
239
- // ---- Event bindings ----
240
- const d1 = on(closeBtn, 'click', () => this._close());
241
- const d2 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
242
- const d3 = on(findInput, 'input', () => this._onSearch());
243
- const d4 = on(caseBtn, 'click', () => {
244
- this._caseSensitive = !this._caseSensitive;
245
- caseCheckbox.checked = this._caseSensitive;
246
- caseBtn.classList.toggle('an-fr-icon-btn--active', this._caseSensitive);
247
- this._onSearch();
248
- });
249
- // Hidden checkbox change handler keeps backward-compat with programmatic toggles
250
- const d5 = on(caseCheckbox, 'change', () => {
251
- this._caseSensitive = caseCheckbox.checked;
252
- caseBtn.classList.toggle('an-fr-icon-btn--active', this._caseSensitive);
253
- this._onSearch();
254
- });
255
- const d6 = on(nextBtn, 'click', () => this._next());
256
- const d7 = on(prevBtn, 'click', () => this._prev());
257
- const d8 = on(replaceBtn, 'click', () => this._replace());
258
- const d9 = on(replaceAllBtn, 'click', () => this._replaceAll());
259
- const d10 = on(findInput, 'keydown', (e) => {
260
- const ke = /** @type {KeyboardEvent} */ (e);
261
- if (ke.key === 'Enter') {
262
- e.preventDefault();
263
- ke.shiftKey ? this._prev() : this._next();
264
- }
265
- });
266
- const d11 = on(replaceInput, 'keydown', (e) => {
267
- if (/** @type {KeyboardEvent} */ (e).key === 'Enter') {
268
- e.preventDefault();
269
- this._replace();
270
- }
271
- });
272
- const dRegex = on(regexBtn, 'click', () => {
273
- this._useRegex = !this._useRegex;
274
- regexBtn.classList.toggle('an-fr-icon-btn--active', this._useRegex);
275
- this._queryRegex = null; // force recompile
276
- this._lastQuery = null;
277
- this._onSearch();
278
- });
279
- const dWholeWord = on(wholeWordBtn, 'click', () => {
280
- this._wholeWord = !this._wholeWord;
281
- wholeWordBtn.classList.toggle('an-fr-icon-btn--active', this._wholeWord);
282
- this._queryRegex = null;
283
- this._lastQuery = null;
284
- this._onSearch();
285
- });
286
- this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, dRegex, dWholeWord);
287
-
288
- return overlay;
289
- }
290
-
291
- // ---------------------------------------------------------------------------
292
- // Search logic
293
- // ---------------------------------------------------------------------------
294
-
295
- _onSearch() {
296
- this._clearHighlights();
297
- const query = this._findInput ? this._findInput.value : '';
298
- if (!query) {
299
- this._updateCounter();
300
- return;
301
- }
302
- this._findAndHighlight(query);
303
- this._updateCounter();
304
- }
305
-
306
- /**
307
- * Finds all occurrences of `query` in the editable area text nodes,
308
- * then wraps each match in a <mark class="an-highlight"> element.
309
- * Iterates text nodes in reverse so earlier offsets remain valid.
310
- * @param {string} query
311
- */
312
- _findAndHighlight(query) {
313
- const editable = this.context.layoutInfo.editable;
314
- if (!editable) return;
315
-
316
- // Collect raw matches: { node, start, end }
317
- const rawMatches = this._findRawMatches(query, editable);
318
- if (rawMatches.length === 0) return;
319
-
320
- // Wrap matches in reverse order so earlier text offsets stay valid when
321
- // later sections of the same text node are split by surroundContents().
322
- // Use push() instead of unshift() to avoid O(n²) shifting on every insert;
323
- // reverse() at the end restores forward document order in O(n).
324
- for (let i = rawMatches.length - 1; i >= 0; i--) {
325
- const { node, start, end } = rawMatches[i];
326
- try {
327
- const range = document.createRange();
328
- range.setStart(node, start);
329
- range.setEnd(node, end);
330
- const mark = document.createElement('mark');
331
- mark.className = 'an-highlight';
332
- range.surroundContents(mark);
333
- this._matches.push({ mark });
334
- } catch (_) {
335
- void _; // surroundContents fails when the range crosses element boundaries
336
- this._matches.push({ mark: null });
337
- }
338
- }
339
- this._matches.reverse(); // O(n) — restore forward document order
340
-
341
- // Drop entries where wrapping failed so the counter and navigation are accurate
342
- this._matches = this._matches.filter((m) => m.mark);
343
- this._currentIndex = 0;
344
- if (this._matches.length === 0) return;
345
-
346
- // Highlight the first (current) match
347
- if (this._matches[0]?.mark) {
348
- this._matches[0].mark.className = 'an-highlight an-highlight-current';
349
- this._matches[0].mark.scrollIntoView({ block: 'center', behavior: 'smooth' });
350
- }
351
- }
352
-
353
- /**
354
- * Walks all text nodes under `root` and returns positional match descriptors.
355
- * @param {string} query
356
- * @param {HTMLElement} root
357
- * @returns {{ node: Text, start: number, end: number }[]}
358
- */
359
- _findRawMatches(query, root) {
360
- const results = [];
361
- // Reuse compiled regex when query, case-sensitivity, regex mode, and whole-word haven't changed
362
- if (this._lastQuery !== query || this._lastCaseSensitive !== this._caseSensitive || this._lastUseRegex !== this._useRegex || this._lastWholeWord !== this._wholeWord) {
363
- const flags = this._caseSensitive ? 'g' : 'gi';
364
- try {
365
- let pattern = this._useRegex
366
- ? query
367
- : query.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
368
- if (this._wholeWord) pattern = `\\b${pattern}\\b`;
369
- this._queryRegex = new RegExp(pattern, flags);
370
- } catch (_) {
371
- this._queryRegex = null;
372
- }
373
- this._lastQuery = query;
374
- this._lastCaseSensitive = this._caseSensitive;
375
- this._lastUseRegex = this._useRegex;
376
- this._lastWholeWord = this._wholeWord;
377
- }
378
- if (!this._queryRegex) return results;
379
- const re = this._queryRegex;
380
-
381
- // Cap results to prevent blocking the main thread on very large documents
382
- const MAX_RESULTS = 500;
383
- const walker = document.createTreeWalker(root, 0x4 /* NodeFilter.SHOW_TEXT */);
384
- let node = walker.nextNode();
385
- while (node && results.length < MAX_RESULTS) {
386
- re.lastIndex = 0;
387
- let m;
388
- while ((m = re.exec(/** @type {Text} */ (node).textContent)) !== null && results.length < MAX_RESULTS) {
389
- results.push({ node: /** @type {Text} */ (node), start: m.index, end: m.index + m[0].length });
390
- }
391
- node = walker.nextNode();
392
- }
393
- return results;
394
- }
395
-
396
- // ---------------------------------------------------------------------------
397
- // Navigation
398
- // ---------------------------------------------------------------------------
399
-
400
- _next() {
401
- if (this._matches.length === 0) return;
402
- this._currentIndex = (this._currentIndex + 1) % this._matches.length;
403
- this._scrollToMatch(this._currentIndex);
404
- this._updateCounter();
405
- }
406
-
407
- _prev() {
408
- if (this._matches.length === 0) return;
409
- this._currentIndex = (this._currentIndex - 1 + this._matches.length) % this._matches.length;
410
- this._scrollToMatch(this._currentIndex);
411
- this._updateCounter();
412
- }
413
-
414
- _scrollToMatch(index) {
415
- const match = this._matches[index];
416
- if (!match?.mark) return;
417
- // Update CSS classes
418
- this._matches.forEach((m, i) => {
419
- if (m.mark) {
420
- m.mark.className = i === index
421
- ? 'an-highlight an-highlight-current'
422
- : 'an-highlight';
423
- }
424
- });
425
- match.mark.scrollIntoView({ block: 'center', behavior: 'smooth' });
426
- }
427
-
428
- // ---------------------------------------------------------------------------
429
- // Replace
430
- // ---------------------------------------------------------------------------
431
-
432
- _replace() {
433
- if (this._matches.length === 0 || this._currentIndex < 0) return;
434
- const match = this._matches[this._currentIndex];
435
- if (!match?.mark?.parentNode) return;
436
-
437
- const replacement = this._replaceInput ? this._replaceInput.value : '';
438
- const parent = match.mark.parentNode;
439
- const textNode = document.createTextNode(replacement);
440
- parent.insertBefore(textNode, match.mark);
441
- match.mark.remove();
442
- parent.normalize();
443
- this.context.invoke('editor.afterCommand');
444
- const savedIndex = this._currentIndex;
445
- this._onSearch();
446
- if (this._matches.length > 0) {
447
- this._currentIndex = Math.min(savedIndex, this._matches.length - 1);
448
- this._scrollToMatch(this._currentIndex);
449
- this._updateCounter();
450
- }
451
- }
452
-
453
- _replaceAll() {
454
- if (this._matches.length === 0) return;
455
- const replacement = this._replaceInput ? this._replaceInput.value : '';
456
-
457
- this._matches.forEach(({ mark }) => {
458
- if (!mark?.parentNode) return;
459
- const textNode = document.createTextNode(replacement);
460
- mark.parentNode.insertBefore(textNode, mark);
461
- mark.remove();
462
- });
463
-
464
- if (this.context.layoutInfo.editable) {
465
- this.context.layoutInfo.editable.normalize();
466
- }
467
- this._matches = [];
468
- this._currentIndex = -1;
469
- this.context.invoke('editor.afterCommand');
470
- this._onSearch();
471
- }
472
-
473
- // ---------------------------------------------------------------------------
474
- // Highlight management
475
- // ---------------------------------------------------------------------------
476
-
477
- /**
478
- * Removes all <mark class="an-highlight"> elements from the editable area,
479
- * restoring the original text nodes via normalization.
480
- */
481
- _clearHighlights() {
482
- const editable = this.context.layoutInfo.editable;
483
- if (!editable) return;
484
-
485
- editable.querySelectorAll('mark.an-highlight').forEach((mark) => {
486
- const parent = mark.parentNode;
487
- if (!parent) return;
488
- while (mark.firstChild) parent.insertBefore(mark.firstChild, mark);
489
- mark.remove();
490
- });
491
- // Merge adjacent text nodes after unwrapping
492
- editable.normalize();
493
-
494
- this._matches = [];
495
- this._currentIndex = -1;
496
- }
497
-
498
- // ---------------------------------------------------------------------------
499
- // Counter display
500
- // ---------------------------------------------------------------------------
501
-
502
- _updateCounter() {
503
- if (!this._counterEl) return;
504
- const total = this._matches.length;
505
- if (total === 0) {
506
- const query = this._findInput ? this._findInput.value : '';
507
- this._counterEl.textContent = query ? this.context.locale.findReplace.noResults : '';
508
- } else {
509
- this._counterEl.textContent = `${this._currentIndex + 1} / ${total}`;
510
- }
511
- }
512
- }
@@ -1,80 +0,0 @@
1
- /**
2
- * Fullscreen.js - Toggles the editor in/out of fullscreen mode
3
- * Inspired by Summernote's Fullscreen module
4
- */
5
-
6
- import { on } from '../core/dom.js';
7
- import { key, isKey } from '../core/key.js';
8
-
9
- export class Fullscreen {
10
- /**
11
- * @param {import('../Context.js').Context} context
12
- */
13
- constructor(context) {
14
- this.context = context;
15
- this._active = false;
16
- this._disposers = [];
17
- /** @type {string} cached editable height before fullscreen */
18
- this._prevHeight = '';
19
- }
20
-
21
- initialize() {
22
- // Press Escape to exit fullscreen
23
- const d = on(document, 'keydown', (event) => {
24
- if (this._active && isKey(/** @type {KeyboardEvent} */ (event), key.ESCAPE)) {
25
- this.deactivate();
26
- }
27
- });
28
- this._disposers.push(d);
29
- return this;
30
- }
31
-
32
- destroy() {
33
- this._disposers.forEach((d) => d());
34
- this._disposers = [];
35
- if (this._active) this.deactivate();
36
- }
37
-
38
- // ---------------------------------------------------------------------------
39
- // Public API
40
- // ---------------------------------------------------------------------------
41
-
42
- toggle() {
43
- if (this._active) {
44
- this.deactivate();
45
- } else {
46
- this.activate();
47
- }
48
- }
49
-
50
- isActive() {
51
- return this._active;
52
- }
53
-
54
- activate() {
55
- if (this._active) return;
56
- const container = this.context.layoutInfo.container;
57
- // Save the container's explicit height (set by the resize handle) so it
58
- // can be restored when exiting fullscreen.
59
- this._prevHeight = container.style.height;
60
-
61
- container.classList.add('an-fullscreen');
62
- // Clear any resize-imposed height so the fullscreen CSS (inset:0) takes over.
63
- container.style.height = '';
64
- document.body.style.overflow = 'hidden';
65
- this._active = true;
66
- this.context.invoke('toolbar.refresh');
67
- }
68
-
69
- deactivate() {
70
- if (!this._active) return;
71
- const container = this.context.layoutInfo.container;
72
-
73
- container.classList.remove('an-fullscreen');
74
- // Restore whatever explicit height the user had set via the resize handle.
75
- container.style.height = this._prevHeight;
76
- document.body.style.overflow = '';
77
- this._active = false;
78
- this.context.invoke('toolbar.refresh');
79
- }
80
- }