autumnnote 2.0.0 → 2.2.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 (64) hide show
  1. package/README.md +72 -5
  2. package/dist/autumnnote.cjs +20 -20
  3. package/dist/autumnnote.css +1 -1
  4. package/dist/autumnnote.es.js +661 -798
  5. package/dist/autumnnote.es.js.map +1 -1
  6. package/dist/autumnnote.min.js +20 -20
  7. package/dist/autumnnote.umd.js +20 -20
  8. package/dist/autumnnote.umd.js.map +1 -1
  9. package/dist/icon-data-V0Xqv-wX.js +255 -0
  10. package/dist/icon-data-V0Xqv-wX.js.map +1 -0
  11. package/package.json +13 -4
  12. package/types/index.d.ts +8 -0
  13. package/src/js/Context.js +0 -854
  14. package/src/js/core/detectLang.js +0 -98
  15. package/src/js/core/dom.js +0 -372
  16. package/src/js/core/env.js +0 -25
  17. package/src/js/core/key.js +0 -66
  18. package/src/js/core/lists.js +0 -121
  19. package/src/js/core/markdown.js +0 -695
  20. package/src/js/core/range.js +0 -194
  21. package/src/js/core/sanitise.js +0 -231
  22. package/src/js/editing/History.js +0 -266
  23. package/src/js/editing/Style.js +0 -812
  24. package/src/js/editing/Table.js +0 -105
  25. package/src/js/editing/Typing.js +0 -397
  26. package/src/js/index.js +0 -193
  27. package/src/js/index.umd.js +0 -17
  28. package/src/js/module/AutoSaveRestore.js +0 -125
  29. package/src/js/module/BaseDialog.js +0 -133
  30. package/src/js/module/BaseMediaTooltip.js +0 -142
  31. package/src/js/module/BaseResizer.js +0 -312
  32. package/src/js/module/BubbleToolbar.js +0 -483
  33. package/src/js/module/Buttons.js +0 -399
  34. package/src/js/module/Clipboard.js +0 -579
  35. package/src/js/module/CodeTooltip.js +0 -493
  36. package/src/js/module/Codeview.js +0 -125
  37. package/src/js/module/ContextMenu.js +0 -621
  38. package/src/js/module/Editor.js +0 -747
  39. package/src/js/module/EmojiDialog.js +0 -254
  40. package/src/js/module/FindReplace.js +0 -512
  41. package/src/js/module/Fullscreen.js +0 -80
  42. package/src/js/module/IconDialog.js +0 -618
  43. package/src/js/module/ImageCropOverlay.js +0 -586
  44. package/src/js/module/ImageDialog.js +0 -193
  45. package/src/js/module/ImageResizer.js +0 -42
  46. package/src/js/module/ImageTooltip.js +0 -285
  47. package/src/js/module/LinkDialog.js +0 -145
  48. package/src/js/module/LinkTooltip.js +0 -250
  49. package/src/js/module/MarkdownShortcuts.js +0 -250
  50. package/src/js/module/Mention.js +0 -365
  51. package/src/js/module/Placeholder.js +0 -51
  52. package/src/js/module/ShortcutsDialog.js +0 -111
  53. package/src/js/module/SlashMenu.js +0 -376
  54. package/src/js/module/Statusbar.js +0 -246
  55. package/src/js/module/TableTooltip.js +0 -1521
  56. package/src/js/module/Toolbar.js +0 -750
  57. package/src/js/module/VideoDialog.js +0 -193
  58. package/src/js/module/VideoResizer.js +0 -66
  59. package/src/js/module/VideoTooltip.js +0 -248
  60. package/src/js/module/emoji-data.js +0 -496
  61. package/src/js/renderer.js +0 -120
  62. package/src/js/settings.js +0 -214
  63. package/src/styles/_variables.scss +0 -48
  64. package/src/styles/autumnnote.scss +0 -2866
@@ -1,105 +0,0 @@
1
- /**
2
- * Table.js - Table creation and manipulation utilities
3
- * Inspired by Summernote's table handling
4
- */
5
-
6
- import { createElement } from '../core/dom.js';
7
-
8
- // ---------------------------------------------------------------------------
9
- // Table creation
10
- // ---------------------------------------------------------------------------
11
-
12
- /**
13
- * Build an HTML table with the given number of columns and rows, optionally including a header row.
14
- * @param {number} cols - Number of columns in each row.
15
- * @param {number} rows - Total number of rows to create (including header when `headerRow` is true).
16
- * @param {{ headerRow?: boolean }} [opts] - Options: `headerRow` creates a `<thead>` when true.
17
- * @returns {HTMLTableElement} The constructed `<table>` element with a `<tbody>` and optional `<thead>`; each cell contains a `<br>` placeholder.
18
- */
19
- export function createTable(cols, rows, opts = {}) {
20
- const { headerRow = false } = opts;
21
- const table = createElement('table', { class: 'an-table' });
22
-
23
- if (headerRow && rows > 0) {
24
- const thead = createElement('thead');
25
- const tr = createElement('tr');
26
- for (let c = 0; c < cols; c++) {
27
- const th = createElement('th', {}, [document.createElement('br')]);
28
- tr.appendChild(th);
29
- }
30
- thead.appendChild(tr);
31
- table.appendChild(thead);
32
- }
33
-
34
- const bodyRows = headerRow ? Math.max(rows - 1, 1) : rows;
35
- const tbody = createElement('tbody');
36
- table.appendChild(tbody);
37
-
38
- for (let r = 0; r < bodyRows; r++) {
39
- const tr = createElement('tr');
40
- for (let c = 0; c < cols; c++) {
41
- const td = createElement('td', {}, [document.createElement('br')]);
42
- tr.appendChild(td);
43
- }
44
- tbody.appendChild(tr);
45
- }
46
- return /** @type {HTMLTableElement} */ (table);
47
- }
48
-
49
- /**
50
- * Insert a table at the current selection and place the caret into its first cell.
51
- * @param {number} cols - Number of columns for the new table.
52
- * @param {number} rows - Number of rows for the new table.
53
- * @param {{ headerRow?: boolean }} [opts] - Options for table creation.
54
- */
55
- export function insertTable(cols, rows, opts = {}) {
56
- if (cols <= 0 || rows <= 0) return;
57
- const table = createTable(cols, rows, opts);
58
-
59
- const sel = globalThis.getSelection();
60
- if (!sel || sel.rangeCount === 0) return;
61
- const range = sel.getRangeAt(0);
62
- try {
63
- range.deleteContents();
64
- } catch (_) {
65
- return;
66
- }
67
-
68
- // Walk up to find the nearest block-level ancestor to insert after
69
- const BLOCK = new Set(['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'LI', 'PRE']);
70
- let anchor = /** @type {Element|null} */ (range.startContainer);
71
- if (anchor?.nodeType === 3) anchor = anchor.parentElement;
72
- while (anchor && !BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentElement) {
73
- anchor = anchor.parentElement;
74
- }
75
-
76
- if (anchor && BLOCK.has(anchor.tagName?.toUpperCase()) && anchor.parentNode) {
77
- anchor.after(table);
78
- // Ensure there's a paragraph after the table for cursor landing
79
- if (!table.nextElementSibling) {
80
- const p = document.createElement('p');
81
- p.appendChild(document.createElement('br'));
82
- table.after(p);
83
- }
84
- // Remove the anchor block if it was empty (common case: cursor in blank paragraph)
85
- if (!anchor.textContent.trim() && !anchor.querySelector('img, video, table')) {
86
- anchor.remove();
87
- }
88
- } else {
89
- try {
90
- range.insertNode(table);
91
- } catch (_) {
92
- return;
93
- }
94
- }
95
-
96
- // Place cursor in the first cell
97
- const firstCell = table.querySelector('td, th');
98
- if (firstCell) {
99
- const nr = document.createRange();
100
- nr.setStart(firstCell, 0);
101
- nr.collapse(true);
102
- sel.removeAllRanges();
103
- sel.addRange(nr);
104
- }
105
- }
@@ -1,397 +0,0 @@
1
- /**
2
- * Typing.js - Keyboard typing event handling (Enter, Tab, Backspace behaviour)
3
- * Inspired by Summernote's Typing module
4
- */
5
-
6
- import { key, isKey } from '../core/key.js';
7
- import { closestPara, isLi } from '../core/dom.js';
8
- import { execCommand, outdent } from './Style.js';
9
- import { currentRange } from '../core/range.js';
10
-
11
- // ---------------------------------------------------------------------------
12
- // Module-level predicates — defined once, not re-created on every keypress.
13
- // Previously these were arrow functions inside handleKeydown() which fires
14
- // at ~120+ events/sec during normal typing.
15
- // ---------------------------------------------------------------------------
16
- const _FA_PATTERN = /\bfa-/;
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
-
20
- /**
21
- * Extracts the content from `startContainer:startOffset` to the end of `li`.
22
- * Returns an empty fragment if the range is invalid (e.g. detached node).
23
- * @param {Range} nativeRange
24
- * @param {Element} li
25
- * @returns {DocumentFragment}
26
- */
27
- function extractAfterContent(nativeRange, li) {
28
- try {
29
- const r = document.createRange();
30
- r.setStart(nativeRange.startContainer, nativeRange.startOffset);
31
- r.setEnd(li, li.childNodes.length);
32
- return r.extractContents();
33
- } catch (_) {
34
- void _;
35
- return document.createDocumentFragment();
36
- }
37
- }
38
-
39
- /**
40
- * Handles special keydown behaviour inside the editor.
41
- * @param {KeyboardEvent} event
42
- * @param {HTMLElement} editable
43
- * @param {object} options - editor options
44
- * @returns {boolean} true if the event was consumed
45
- */
46
- export function handleKeydown(event, editable, options = {}) {
47
- const moveCaret = (setFn) => {
48
- const sel = globalThis.getSelection();
49
- if (!sel) return false;
50
- const nr = document.createRange();
51
- setFn(nr);
52
- nr.collapse(true);
53
- sel.removeAllRanges();
54
- sel.addRange(nr);
55
- return true;
56
- };
57
-
58
- // -------------------------------------------------------------------------
59
- // Backspace key — one-press deletion of a preceding FA icon (<i> element)
60
- // -------------------------------------------------------------------------
61
- if (isKey(event, key.BACKSPACE)) {
62
- const sel = globalThis.getSelection();
63
- if (sel?.rangeCount > 0) {
64
- const r = sel.getRangeAt(0);
65
- if (r.collapsed && r.startContainer.nodeType === Node.TEXT_NODE) {
66
- const textNode = /** @type {ChildNode} */ (r.startContainer);
67
- // Case A: cursor at offset 0, preceding sibling is an FA icon
68
- if (r.startOffset === 0 && isFAIcon(textNode.previousSibling)) {
69
- event.preventDefault();
70
- /** @type {ChildNode} */ (textNode.previousSibling).remove();
71
- return true;
72
- }
73
-
74
- // Case B: cursor at offset 1 of a ZWS-only text node whose preceding
75
- // sibling is an FA icon. The ZWS is the invisible caret anchor inserted
76
- // by IconDialog; treat the whole Backspace as "delete icon + its anchor".
77
- if (r.startOffset === 1 && textNode.textContent === '\u200B' &&
78
- isFAIcon(textNode.previousSibling)) {
79
- event.preventDefault();
80
- const parent = textNode.parentNode;
81
- const icon = /** @type {ChildNode} */ (textNode.previousSibling);
82
- const prevNode = icon.previousSibling; // node before the icon (e.g. ZWS of prior icon)
83
- icon.remove();
84
- textNode.remove();
85
- // Explicitly restore the cursor to the node preceding the deleted icon.
86
- // Without this, the browser collapses the selection to the parent element
87
- // (not a text node), causing the next Backspace to miss Cases A/B and
88
- // requiring an extra keypress when two icons are adjacent.
89
- const nr = document.createRange();
90
- if (prevNode?.nodeType === Node.TEXT_NODE) {
91
- nr.setStart(prevNode, prevNode.textContent.length);
92
- } else if (prevNode) {
93
- nr.setStartAfter(prevNode);
94
- } else if (parent) {
95
- nr.setStart(parent, 0);
96
- }
97
- nr.collapse(true);
98
- sel.removeAllRanges();
99
- sel.addRange(nr);
100
- return true;
101
- }
102
- }
103
- }
104
- return false;
105
- }
106
-
107
- // -------------------------------------------------------------------------
108
- // ArrowLeft / ArrowRight — one-press navigation across FA icon nodes
109
- // -------------------------------------------------------------------------
110
- if (isKey(event, key.LEFT) || isKey(event, key.RIGHT)) {
111
- const sel = globalThis.getSelection();
112
- if (!sel || sel.rangeCount === 0) return false;
113
-
114
- const r = sel.getRangeAt(0);
115
- if (!r.collapsed) return false;
116
-
117
- const sc = r.startContainer;
118
- const movingLeft = isKey(event, key.LEFT);
119
-
120
- if (sc.nodeType === Node.TEXT_NODE) {
121
- const textNode = sc;
122
-
123
- if (movingLeft &&
124
- r.startOffset === 1 &&
125
- textNode.textContent === '\u200B' &&
126
- isFAIcon(textNode.previousSibling)) {
127
- event.preventDefault();
128
- return moveCaret((nr) => nr.setStartBefore(textNode.previousSibling));
129
- }
130
-
131
- if (movingLeft && r.startOffset === 0 && isFAIcon(textNode.previousSibling)) {
132
- event.preventDefault();
133
- return moveCaret((nr) => nr.setStartBefore(textNode.previousSibling));
134
- }
135
-
136
- if (movingLeft &&
137
- r.startOffset === 0 &&
138
- isZwsAnchor(textNode.previousSibling) &&
139
- isFAIcon(textNode.previousSibling.previousSibling)) {
140
- event.preventDefault();
141
- return moveCaret((nr) => nr.setStartBefore(textNode.previousSibling.previousSibling));
142
- }
143
-
144
- if (!movingLeft &&
145
- r.startOffset === textNode.textContent.length &&
146
- isFAIcon(textNode.nextSibling)) {
147
- const icon = textNode.nextSibling;
148
- const after = icon.nextSibling;
149
- event.preventDefault();
150
- if (after?.nodeType === Node.TEXT_NODE) {
151
- const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
152
- return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
153
- }
154
- return moveCaret((nr) => nr.setStartAfter(icon));
155
- }
156
-
157
- if (!movingLeft &&
158
- r.startOffset === textNode.textContent.length &&
159
- isZwsAnchor(textNode.nextSibling) &&
160
- isFAIcon(textNode.nextSibling.nextSibling)) {
161
- const icon = textNode.nextSibling.nextSibling;
162
- const after = icon.nextSibling;
163
- event.preventDefault();
164
- if (after?.nodeType === Node.TEXT_NODE) {
165
- const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
166
- return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
167
- }
168
- return moveCaret((nr) => nr.setStartAfter(icon));
169
- }
170
- }
171
-
172
- if (sc.nodeType === Node.ELEMENT_NODE) {
173
- const el = sc;
174
- if (movingLeft && r.startOffset > 0) {
175
- const prev = el.childNodes[r.startOffset - 1];
176
- if (isFAIcon(prev)) {
177
- event.preventDefault();
178
- return moveCaret((nr) => nr.setStartBefore(prev));
179
- }
180
- if (isZwsAnchor(prev) && isFAIcon(prev.previousSibling)) {
181
- event.preventDefault();
182
- return moveCaret((nr) => nr.setStartBefore(prev.previousSibling));
183
- }
184
- }
185
- if (!movingLeft && r.startOffset < el.childNodes.length) {
186
- const next = el.childNodes[r.startOffset];
187
- if (isFAIcon(next)) {
188
- const after = next.nextSibling;
189
- event.preventDefault();
190
- if (after?.nodeType === Node.TEXT_NODE) {
191
- const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
192
- return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
193
- }
194
- return moveCaret((nr) => nr.setStartAfter(next));
195
- }
196
- if (isZwsAnchor(next) && isFAIcon(next.nextSibling)) {
197
- const icon = next.nextSibling;
198
- const after = icon.nextSibling;
199
- event.preventDefault();
200
- if (after?.nodeType === Node.TEXT_NODE) {
201
- const offset = ((after.textContent || '').startsWith('\u200B')) ? 1 : 0;
202
- return moveCaret((nr) => nr.setStart(after, Math.min(offset, after.textContent.length)));
203
- }
204
- return moveCaret((nr) => nr.setStartAfter(icon));
205
- }
206
- }
207
- }
208
- }
209
-
210
- // -------------------------------------------------------------------------
211
- // Tab key — indent / outdent list items, or insert soft tab in code blocks
212
- // -------------------------------------------------------------------------
213
- if (isKey(event, key.TAB)) {
214
- const range = currentRange(editable);
215
- if (!range) return false;
216
-
217
- const para = closestPara(range.sc, editable);
218
- if (para && isLi(para)) {
219
- event.preventDefault();
220
- if (event.shiftKey) {
221
- outdent();
222
- } else {
223
- execCommand('indent');
224
- }
225
- return true;
226
- }
227
-
228
- // In a pre/code block, insert spaces using configured tabSize
229
- if (para?.nodeName.toUpperCase() === 'PRE') {
230
- if (event.shiftKey) return false;
231
- event.preventDefault();
232
- execCommand('insertText', ' '.repeat(options.tabSize || 4));
233
- return true;
234
- }
235
-
236
- // Default: insert &nbsp; * tabSize
237
- if (options.tabSize) {
238
- if (event.shiftKey) return false;
239
- event.preventDefault();
240
- execCommand('insertText', ' '.repeat(options.tabSize));
241
- return true;
242
- }
243
- }
244
-
245
- // -------------------------------------------------------------------------
246
- // Shift+Enter — insert <br> instead of opening a new block element
247
- // -------------------------------------------------------------------------
248
- if (isKey(event, key.ENTER) && event.shiftKey) {
249
- event.preventDefault();
250
- execCommand('insertLineBreak');
251
- return true;
252
- }
253
-
254
- // -------------------------------------------------------------------------
255
- // Enter key — keep consistent paragraph insertion
256
- // -------------------------------------------------------------------------
257
- if (isKey(event, key.ENTER) && !event.shiftKey) {
258
- const range = currentRange(editable);
259
- if (!range) return false;
260
-
261
- // Hoist sc/el once so all guards below can reuse them.
262
- const sc = range.sc;
263
- const el = /** @type {Element|null} */ (sc.nodeType === 3 ? sc.parentElement : sc);
264
-
265
- // Guard: if the cursor is inside a <i> FA icon element (zero text children,
266
- // rendered entirely by CSS ::before), pressing Enter would split the block
267
- // and leave an orphan <i> in the new paragraph — visually an "auto-created
268
- // icon". Push the cursor to just after the <i> first, then fall through so
269
- // the browser fires its default Enter at a safe text boundary.
270
- if (el?.nodeName === 'I' && /\bfa-/.test(el.className || '')) {
271
- const nr = document.createRange();
272
- nr.setStartAfter(el);
273
- nr.collapse(true);
274
- const selI = globalThis.getSelection();
275
- if (selI) { selI.removeAllRanges(); selI.addRange(nr); }
276
- return false; // cursor is now outside <i> — let browser default handle Enter
277
- }
278
-
279
- // Video wrapper — Enter should create a new paragraph after the wrapper,
280
- // not split the wrapper's container and produce an empty video clone.
281
- const videoWrapper = el?.closest('.an-video-wrapper');
282
- if (videoWrapper) {
283
- event.preventDefault();
284
- const p = document.createElement('p');
285
- p.innerHTML = '\u00a0';
286
- videoWrapper.parentNode.insertBefore(p, videoWrapper.nextSibling);
287
- const nr = document.createRange();
288
- nr.setStart(p, 0);
289
- nr.collapse(true);
290
- const sel = globalThis.getSelection();
291
- sel.removeAllRanges();
292
- sel.addRange(nr);
293
- return true;
294
- }
295
-
296
- // Checklist — Enter creates new item; empty item exits the list
297
- const checkLi = el?.closest('.an-checklist li');
298
- if (checkLi) {
299
- event.preventDefault();
300
- const ul = checkLi.closest('.an-checklist');
301
- const sel = globalThis.getSelection();
302
- let nativeRange = sel.getRangeAt(0);
303
-
304
- // Helper: get trimmed text content of a li, excluding the checkbox INPUT.
305
- // Strip both \u00a0 (placeholder nbsp) and \u200B (ZWS cursor anchors).
306
- const liText = (li) =>
307
- Array.from(li.childNodes)
308
- .filter((n) => !(n.nodeType === 1 && n.tagName === 'INPUT'))
309
- .map((n) => n.textContent).join('').replace(/[\u00a0\u200B]/g, ' ').trim();
310
-
311
- // 1. Check if the ENTIRE item is empty BEFORE any DOM mutation.
312
- // (Do NOT check only the "before-cursor" part — that check incorrectly
313
- // exits the list when cursor is at the start of a non-empty item.)
314
- if (!liText(checkLi)) {
315
- // Empty item — exit checklist, insert <p> after list
316
- const p = document.createElement('p');
317
- p.innerHTML = '\u00a0';
318
- ul.parentNode.insertBefore(p, ul.nextSibling);
319
- checkLi.remove();
320
- if (ul.children.length === 0) ul.remove();
321
- const nr = document.createRange();
322
- nr.setStart(p.firstChild, 0);
323
- nr.collapse(true);
324
- sel.removeAllRanges();
325
- sel.addRange(nr);
326
- return true;
327
- }
328
-
329
- // 2. If selection is not collapsed, delete the selected content first —
330
- // mirrors browser-default Enter behaviour (delete selection, then split).
331
- if (!nativeRange.collapsed) {
332
- nativeRange.deleteContents();
333
- // nativeRange is now collapsed at the deletion point; re-read it
334
- if (sel.rangeCount === 0 || !checkLi.isConnected) return true;
335
- nativeRange = sel.getRangeAt(0);
336
- }
337
-
338
- // 3. Extract everything from cursor to end of li into afterFrag.
339
- // Use startContainer/startOffset (cursor position after potential delete),
340
- // NOT endContainer/endOffset which is wrong for non-collapsed ranges.
341
- const afterFrag = extractAfterContent(nativeRange, checkLi);
342
-
343
- // 4. Build the new checklist item with the extracted "after" content.
344
- const newLi = document.createElement('li');
345
- const cb = document.createElement('input');
346
- cb.type = 'checkbox';
347
- cb.setAttribute('contenteditable', 'false');
348
- newLi.appendChild(cb);
349
-
350
- // Append extracted "after" content (if any) then insert the new item.
351
- if (afterFrag.textContent.replace(/[\u00a0\u200B]/g, '').length > 0) {
352
- newLi.appendChild(afterFrag);
353
- }
354
-
355
- // Always ensure a text node exists so the cursor has a text-level
356
- // anchor. Use \u200B (zero-width space) instead of an empty string:
357
- // Chrome does not reliably honour a Selection in an empty text node and
358
- // may normalise it to element-level, placing the caret before the
359
- // absolutely-positioned checkbox. \u200B is stripped by getHTML().
360
- let cursorNode = newLi.childNodes[1]; // first child after checkbox
361
- if (cursorNode?.nodeType !== Node.TEXT_NODE) {
362
- cursorNode = document.createTextNode('\u200B');
363
- newLi.appendChild(cursorNode);
364
- }
365
- checkLi.after(newLi);
366
-
367
- const nr = document.createRange();
368
- nr.setStart(cursorNode, 0);
369
- nr.collapse(true);
370
- sel.removeAllRanges();
371
- sel.addRange(nr);
372
- return true;
373
- }
374
-
375
- const para = closestPara(range.sc, editable);
376
-
377
- // Enter in a pre/code block: insert a literal newline instead of a new block
378
- if (para?.nodeName.toUpperCase() === 'PRE') {
379
- event.preventDefault();
380
- execCommand('insertText', '\n');
381
- return true;
382
- }
383
-
384
- // Pressing Enter at the end of a blockquote should exit it
385
- if (para?.nodeName.toUpperCase() === 'BLOCKQUOTE') {
386
- const native = range.toNativeRange();
387
- native.setEnd(para, para.childNodes.length);
388
- if (native.toString() === '' && range.isCollapsed()) {
389
- event.preventDefault();
390
- execCommand('formatBlock', '<p>');
391
- return true;
392
- }
393
- }
394
- }
395
-
396
- return false;
397
- }
package/src/js/index.js DELETED
@@ -1,193 +0,0 @@
1
- /**
2
- * index.js - Public entry point for AutumnNote
3
- *
4
- * Usage (module):
5
- * import AutumnNote from 'autumnnote';
6
- * const editor = AutumnNote.create('#my-editor', { placeholder: 'Start typing…' });
7
- *
8
- * Usage (UMD / script tag):
9
- * const editor = AutumnNote.create('#my-editor');
10
- */
11
-
12
- // @ts-ignore
13
- import '../styles/autumnnote.scss';
14
- import { Context, _customModules, _globalPlugins } from './Context.js';
15
- import { registerButton, buttons } from './module/Buttons.js';
16
- import { defaultOptions } from './settings.js';
17
- import { registerLocale } from './i18n/index.js';
18
-
19
- // Snapshot of factory defaults taken at module-load time (before any setDefaults() calls)
20
- const _originalDefaults = { ...defaultOptions };
21
-
22
- // Re-export for tree-shaking / module consumers
23
- export { Context } from './Context.js';
24
- export { defaultOptions } from './settings.js';
25
- export * from './core/dom.js';
26
- export * from './core/range.js';
27
- export * from './core/func.js';
28
- export * from './core/key.js';
29
- export * from './core/lists.js';
30
- export * from './core/env.js';
31
- export * from './core/sanitise.js';
32
- export * from './module/Buttons.js';
33
- export { locales, resolveLocale, registerLocale } from './i18n/index.js';
34
-
35
- // ---------------------------------------------------------------------------
36
- // Main factory
37
- // ---------------------------------------------------------------------------
38
-
39
- /** @type {WeakMap<Element, Context>} */
40
- const instances = new WeakMap();
41
-
42
- const AutumnNote = {
43
- /**
44
- * Creates (or returns existing) editor instance on one or more elements.
45
- *
46
- * @param {string|Element|NodeList|Element[]} selector
47
- * @param {import('./settings.js').AsnOptions} [options]
48
- * @returns {Context|Context[]} single Context or array of Contexts
49
- */
50
- create(selector, options = {}) {
51
- const elements = resolveElements(selector);
52
- const ctxs = elements.map((el) => {
53
- if (instances.has(el)) return instances.get(el);
54
- const ctx = new Context(/** @type {HTMLElement} */ (el), options);
55
- ctx.initialize();
56
- instances.set(el, ctx);
57
- return ctx;
58
- });
59
- return ctxs.length === 1 ? ctxs[0] : ctxs;
60
- },
61
-
62
- /**
63
- * Destroys the editor(s) on the given selector.
64
- * @param {string|Element|NodeList|Element[]} selector
65
- */
66
- destroy(selector) {
67
- resolveElements(selector).forEach((el) => {
68
- const ctx = instances.get(el);
69
- if (ctx) {
70
- ctx.destroy();
71
- instances.delete(el);
72
- }
73
- });
74
- },
75
-
76
- /**
77
- * Returns the Context instance for a given element (or null).
78
- * @param {string|Element} selector
79
- * @returns {Context|null}
80
- */
81
- getInstance(selector) {
82
- const el = typeof selector === 'string' ? document.querySelector(selector) : selector;
83
- return el ? instances.get(el) || null : null;
84
- },
85
-
86
- /** Returns a shallow copy of the default options (read-only snapshot). */
87
- get defaults() { return { ...defaultOptions }; },
88
-
89
- /** Merges properties into the global defaults, applied to all future instances. */
90
- setDefaults(overrides) { Object.assign(defaultOptions, overrides); },
91
-
92
- /** Restores global defaults to their original factory values. */
93
- resetDefaults() {
94
- Object.keys(defaultOptions).forEach((k) => delete defaultOptions[k]);
95
- Object.assign(defaultOptions, _originalDefaults);
96
- },
97
-
98
- /**
99
- * Registers a custom module to be included in every new editor instance.
100
- * @param {string} name - unique module key used for ctx.invoke() calls
101
- * @param {Function} ModuleClass - class with initialize() and optional destroy()
102
- */
103
- registerModule(name, ModuleClass) { _customModules.set(name, ModuleClass); },
104
-
105
- /**
106
- * Installs a plugin globally — applied to every future editor instance.
107
- * Plugin `buttons` are registered to the global button registry immediately
108
- * so they are available when Toolbar initialises inside create().
109
- * Plugin `install()` is called after all built-in modules have initialised.
110
- * @param {object} plugin - { name, version?, buttons?, install?, uninstall? }
111
- * @param {object} [options] - Forwarded to plugin.install(context, options)
112
- * @returns {typeof AutumnNote}
113
- */
114
- use(plugin, options = {}) {
115
- if (!plugin || typeof plugin.name !== 'string') {
116
- throw new TypeError('[AutumnNote] AutumnNote.use: plugin must have a string `name` property.');
117
- }
118
- if (_globalPlugins.has(plugin.name)) {
119
- console.warn(`[AutumnNote] Plugin "${plugin.name}" already registered globally. Skipping.`);
120
- return this;
121
- }
122
- if (Array.isArray(plugin.buttons)) {
123
- plugin.buttons.forEach((b) => registerButton(b));
124
- }
125
- _globalPlugins.set(plugin.name, { plugin, options });
126
- return this;
127
- },
128
-
129
- /**
130
- * Returns true if a plugin with the given name has been registered globally.
131
- * @param {string} name
132
- * @returns {boolean}
133
- */
134
- hasPlugin(name) { return _globalPlugins.has(name); },
135
-
136
- /**
137
- * Registers a single button definition in the global button registry.
138
- * After create(), call ctx.invoke('toolbar.rebuild') to render new buttons.
139
- * @param {object} btnDef - ButtonDef-compatible object with a `name` string
140
- * @returns {typeof AutumnNote}
141
- */
142
- registerButton(btnDef) { registerButton(btnDef); return this; },
143
-
144
- /**
145
- * Registers a locale so `lang: '<code>'` can select it.
146
- * Only English ships in the ESM bundle — import others from
147
- * `autumnnote/i18n/<code>` and register them here.
148
- * @param {string} code
149
- * @param {object} locale
150
- */
151
- registerLocale(code, locale) { registerLocale(code, locale); return this; },
152
-
153
- /** Registers a slash-menu command for future editor instances. */
154
- registerSlashCommand(command) {
155
- if (!command?.id || typeof command.run !== 'function') {
156
- throw new TypeError('[AutumnNote] Slash command requires an id and run(context) function.');
157
- }
158
- const commands = defaultOptions.slashCommands;
159
- const index = commands.findIndex((item) => item.id === command.id);
160
- if (index >= 0) commands[index] = command;
161
- else commands.push(command);
162
- return this;
163
- },
164
-
165
- /** All pre-built button definitions — accessible in every module format including UMD/CJS. */
166
- buttons,
167
-
168
- /** Library version */
169
- version: '2.0.0',
170
- };
171
-
172
- // ---------------------------------------------------------------------------
173
- // Helper
174
- // ---------------------------------------------------------------------------
175
-
176
- /**
177
- * @param {string|Element|NodeList|Element[]} selector
178
- * @returns {Element[]}
179
- */
180
- function resolveElements(selector) {
181
- if (typeof selector === 'string') {
182
- return Array.from(document.querySelectorAll(selector));
183
- }
184
- if (selector instanceof Element) {
185
- return [selector];
186
- }
187
- if (selector instanceof NodeList || Array.isArray(selector)) {
188
- return /** @type {Element[]} */ (Array.from(selector));
189
- }
190
- return [];
191
- }
192
-
193
- export default AutumnNote;