autumnnote 2.1.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 (65) hide show
  1. package/README.md +72 -5
  2. package/dist/autumnnote.cjs +20 -20
  3. package/dist/autumnnote.es.js +385 -619
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.min.js +20 -20
  6. package/dist/autumnnote.umd.js +20 -20
  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,250 +0,0 @@
1
- // LinkTooltip.js - Hover tooltip for links inside the editor
2
- // Displays a small action bar with: visit link, edit link, unlink
3
- import { createElement, on } from '../core/dom.js';
4
-
5
- const ICONS = {
6
- open: `<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"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>`,
7
- edit: `<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"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>`,
8
- unlink: `<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"><path d="M18.84 12.25l1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.006 5.006 0 0 0-6.95 0l-1.72 1.71"/><path d="M5.17 11.75l-1.71 1.71a5.004 5.004 0 0 0 .12 7.07 5.006 5.006 0 0 0 6.95 0l1.71-1.71"/><line x1="8" y1="2" x2="8" y2="5"/><line x1="2" y1="8" x2="5" y2="8"/><line x1="16" y1="19" x2="16" y2="22"/><line x1="19" y1="16" x2="22" y2="16"/></svg>`,
9
- copy: `<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="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`,
10
- };
11
-
12
- /** Delay in ms before showing / after hiding to avoid flicker */
13
- const SHOW_DELAY = 120;
14
- const HIDE_DELAY = 200;
15
-
16
- export class LinkTooltip {
17
- /** @param {import('../Context.js').Context} context */
18
- constructor(context) {
19
- this.context = context;
20
- this._el = null;
21
- this._activeAnchor = null;
22
- this._showTimer = null;
23
- this._hideTimer = null;
24
- this._disposers = [];
25
- }
26
-
27
- initialize() {
28
- this._el = this._buildTooltip();
29
- document.body.appendChild(this._el);
30
-
31
- const editable = this.context.layoutInfo.editable;
32
-
33
- this._disposers.push(
34
- // Detect when pointer enters a link
35
- on(editable, 'mouseover', (e) => {
36
- const anchor = /** @type {Element} */ (e.target)?.closest('a[href]');
37
- if (anchor && editable.contains(anchor)) {
38
- this._scheduleShow(anchor);
39
- }
40
- }),
41
- // Detect when pointer leaves the editable area entirely
42
- on(editable, 'mouseout', (e) => {
43
- const to = /** @type {Node|null} */ (/** @type {MouseEvent} */ (e).relatedTarget);
44
- if (!to || (!editable.contains(to) && !this._el.contains(to))) {
45
- this._scheduleHide();
46
- }
47
- }),
48
- // Hide when the page scrolls or resizes — the tooltip position becomes stale
49
- on(globalThis, 'scroll', () => this._hide(), { passive: true }),
50
- on(globalThis, 'resize', () => this._hide(), { passive: true }),
51
- );
52
-
53
- return this;
54
- }
55
-
56
- destroy() {
57
- this._clearTimers();
58
- this._disposers.forEach((d) => d());
59
- this._disposers = [];
60
- if (this._el?.parentNode) this._el.remove();
61
- this._el = null;
62
- }
63
-
64
- // ---------------------------------------------------------------------------
65
- // Build
66
- // ---------------------------------------------------------------------------
67
-
68
- _buildTooltip() {
69
- const L = this.context.locale.tooltips.link;
70
- const el = createElement('div', { class: 'an-link-tooltip', role: 'toolbar', 'aria-label': L.ariaLabel });
71
- el.style.display = 'none';
72
-
73
- // URL preview text (truncated)
74
- this._urlLabel = createElement('span', { class: 'an-link-tooltip-url' });
75
- el.appendChild(this._urlLabel);
76
-
77
- // Separator
78
- el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
79
-
80
- // Action buttons
81
- this._openBtn = this._makeBtn(ICONS.open, L.openLink, () => this._openLink());
82
- this._copyBtn = this._makeBtn(ICONS.copy, L.copyUrl, () => this._copyLink());
83
- this._editBtn = this._makeBtn(ICONS.edit, L.editLink, () => this._editLink());
84
- this._unlinkBtn = this._makeBtn(ICONS.unlink, L.removeLink, () => this._unlink());
85
-
86
- el.appendChild(this._openBtn);
87
- el.appendChild(this._copyBtn);
88
- el.appendChild(this._editBtn);
89
- el.appendChild(this._unlinkBtn);
90
-
91
- // Keep tooltip alive while hovering it
92
- this._disposers.push(
93
- on(el, 'mouseenter', () => this._clearTimers()),
94
- on(el, 'mouseleave', () => this._scheduleHide()),
95
- );
96
-
97
- return el;
98
- }
99
-
100
- _makeBtn(icon, title, handler) {
101
- const btn = createElement('button', { type: 'button', class: 'an-link-tooltip-btn', title });
102
- btn.innerHTML = icon;
103
- this._disposers.push(on(btn, 'click', (e) => { e.preventDefault(); e.stopPropagation(); handler(); }));
104
- return btn;
105
- }
106
-
107
- // ---------------------------------------------------------------------------
108
- // Show / Hide logic
109
- // ---------------------------------------------------------------------------
110
-
111
- _scheduleShow(anchor) {
112
- if (this._activeAnchor === anchor && this._el.style.display !== 'none') return;
113
- clearTimeout(this._hideTimer);
114
- this._hideTimer = null;
115
- clearTimeout(this._showTimer);
116
- this._showTimer = setTimeout(() => {
117
- this._activeAnchor = anchor;
118
- this._show(anchor);
119
- }, SHOW_DELAY);
120
- }
121
-
122
- _scheduleHide() {
123
- clearTimeout(this._showTimer);
124
- this._showTimer = null;
125
- if (this._hideTimer) return;
126
- this._hideTimer = setTimeout(() => {
127
- this._hide();
128
- }, HIDE_DELAY);
129
- }
130
-
131
- _show(anchor) {
132
- const isReadOnly = this.context.layoutInfo.container.classList.contains('an-disabled');
133
- const url = anchor.getAttribute('href') || '';
134
- this._urlLabel.textContent = this._truncateUrl(url);
135
- this._urlLabel.title = url;
136
- // In read-only mode keep only open + copy; hide edit and unlink
137
- this._editBtn.style.display = isReadOnly ? 'none' : '';
138
- this._unlinkBtn.style.display = isReadOnly ? 'none' : '';
139
- this._el.style.display = 'flex';
140
- this._positionNear(anchor);
141
- }
142
-
143
- _hide() {
144
- this._el.style.display = 'none';
145
- this._activeAnchor = null;
146
- this._clearTimers();
147
- }
148
-
149
- _clearTimers() {
150
- clearTimeout(this._showTimer);
151
- clearTimeout(this._hideTimer);
152
- this._showTimer = null;
153
- this._hideTimer = null;
154
- }
155
-
156
- _positionNear(anchor) {
157
- const rect = anchor.getBoundingClientRect();
158
- const tipW = this._el.offsetWidth || 260;
159
- const tipH = this._el.offsetHeight || 34;
160
- const margin = 6;
161
-
162
- // Prefer below the link; if no room, put above
163
- let top = rect.bottom + margin;
164
- let left = rect.left;
165
-
166
- if (top + tipH > globalThis.innerHeight - margin) {
167
- top = rect.top - tipH - margin;
168
- }
169
- if (left + tipW > globalThis.innerWidth - margin) {
170
- left = globalThis.innerWidth - tipW - margin;
171
- }
172
- if (left < margin) left = margin;
173
-
174
- this._el.style.top = `${top}px`;
175
- this._el.style.left = `${left}px`;
176
- }
177
-
178
- _truncateUrl(url) {
179
- try {
180
- const u = new URL(url);
181
- const display = u.host + (u.pathname === '/' ? '' : u.pathname);
182
- return display.length > 48 ? display.slice(0, 48) + '…' : display;
183
- } catch {
184
- return url.length > 48 ? url.slice(0, 48) + '…' : url;
185
- }
186
- }
187
-
188
- // ---------------------------------------------------------------------------
189
- // Actions
190
- // ---------------------------------------------------------------------------
191
-
192
- _openLink() {
193
- const url = this._activeAnchor?.getAttribute('href');
194
- if (url) globalThis.open(url, '_blank', 'noopener,noreferrer');
195
- this._hide();
196
- }
197
-
198
- _copyLink() {
199
- const url = this._activeAnchor?.getAttribute('href');
200
- if (url) {
201
- navigator.clipboard.writeText(url).catch(() => {
202
- // Fallback for environments where clipboard API is unavailable
203
- const ta = document.createElement('textarea');
204
- ta.value = url;
205
- ta.style.position = 'fixed';
206
- ta.style.opacity = '0';
207
- document.body.appendChild(ta);
208
- ta.select();
209
- document.execCommand('copy');
210
- ta.remove();
211
- });
212
- }
213
- // Brief visual feedback
214
- if (this._copyBtn) {
215
- this._copyBtn.classList.add('an-link-tooltip-btn--copied');
216
- setTimeout(() => this._copyBtn?.classList.remove('an-link-tooltip-btn--copied'), 1000);
217
- }
218
- }
219
-
220
- _editLink() {
221
- const anchor = this._activeAnchor;
222
- if (!anchor) return;
223
- this._hide();
224
-
225
- // Place cursor inside the anchor, then open the link dialog (which pre-fills from selection)
226
- const sel = globalThis.getSelection();
227
- const range = document.createRange();
228
- range.selectNodeContents(anchor);
229
- sel.removeAllRanges();
230
- sel.addRange(range);
231
-
232
- this.context.invoke('linkDialog.show');
233
- }
234
-
235
- _unlink() {
236
- const anchor = this._activeAnchor;
237
- if (!anchor) return;
238
- this._hide();
239
-
240
- // Select the anchor text, then remove the link
241
- const sel = globalThis.getSelection();
242
- const range = document.createRange();
243
- range.selectNode(anchor);
244
- sel.removeAllRanges();
245
- sel.addRange(range);
246
-
247
- document.execCommand('unlink');
248
- this.context.invoke('editor.afterCommand');
249
- }
250
- }
@@ -1,250 +0,0 @@
1
- /**
2
- * MarkdownShortcuts.js — Convert Markdown-style syntax typed directly in the
3
- * editor into rich HTML elements (input rules / auto-format).
4
- *
5
- * Activated when `markdownShortcuts: true` (the default).
6
- *
7
- * Block rules — triggered by Space or Enter at the start of a line:
8
- * #[#[#]]· → H1 / H2 / H3
9
- * >· → blockquote
10
- * -· or *· → unordered list item
11
- * 1.· → ordered list item
12
- * [ ]· → checklist item
13
- * --- → horizontal rule (on Enter)
14
- * ``` → code block (on Enter)
15
- *
16
- * Inline rules — triggered when the closing marker is typed:
17
- * **text** → <strong>
18
- * *text* → <em>
19
- * ~~text~~ → <s>
20
- * `code` → <code>
21
- */
22
-
23
- import { on } from '../core/dom.js';
24
-
25
- export class MarkdownShortcuts {
26
- /** @param {import('../Context.js').Context} context */
27
- constructor(context) {
28
- this.context = context;
29
- this.options = context.options;
30
- this._disposers = [];
31
- }
32
-
33
- initialize() {
34
- if (!this.options.markdownShortcuts) return this;
35
- const editable = this.context.layoutInfo.editable;
36
- const d1 = on(editable, 'keydown', /** @param {KeyboardEvent} e */ (e) => this._onKeydown(e));
37
- const d2 = on(editable, 'input', () => this._onInput());
38
- this._disposers.push(d1, d2);
39
- return this;
40
- }
41
-
42
- destroy() {
43
- this._disposers.forEach((d) => d());
44
- this._disposers = [];
45
- }
46
-
47
- // ---------------------------------------------------------------------------
48
- // Block rules (Space / Enter)
49
- // ---------------------------------------------------------------------------
50
-
51
- _onKeydown(e) {
52
- if (e.key === ' ') {
53
- if (this._applyBlockRule()) e.preventDefault();
54
- } else if (e.key === 'Enter') {
55
- if (this._applyEnterRule()) e.preventDefault();
56
- }
57
- }
58
-
59
- /**
60
- * Returns the plain text of the line the cursor is on, up to the cursor.
61
- * @returns {{ text: string, range: Range, lineNode: Node } | null}
62
- */
63
- _getLineContext() {
64
- const sel = globalThis.getSelection();
65
- if (!sel?.rangeCount) return null;
66
- const range = sel.getRangeAt(0);
67
- if (!range.collapsed) return null;
68
-
69
- const editable = this.context.layoutInfo.editable;
70
- if (!editable.contains(range.startContainer)) return null;
71
-
72
- // Walk up to find the block-level ancestor inside the editable
73
- let node = range.startContainer;
74
- while (node && node !== editable && !this._isBlock(node)) {
75
- node = node.parentNode;
76
- }
77
- if (!node || node === editable) node = range.startContainer;
78
-
79
- // Collect all text content before the cursor within that block
80
- const tmpRange = document.createRange();
81
- tmpRange.setStart(node, 0);
82
- tmpRange.setEnd(range.startContainer, range.startOffset);
83
- const text = tmpRange.toString();
84
-
85
- return { text, range, lineNode: node };
86
- }
87
-
88
- _isBlock(node) {
89
- if (node.nodeType !== Node.ELEMENT_NODE) return false;
90
- const display = globalThis.getComputedStyle(node).display;
91
- return display === 'block' || display === 'list-item' || display === 'table-cell';
92
- }
93
-
94
- /** Applies block rule on Space key. Returns true if a rule fired. */
95
- _applyBlockRule() {
96
- const ctx = this._getLineContext();
97
- if (!ctx) return false;
98
- const { text } = ctx;
99
-
100
- const blockPatterns = [
101
- { re: /^(#{1,3})$/, handler: (m) => this._convertToHeading(m[1].length) },
102
- { re: /^>$/, handler: () => this._convertToBlockquote() },
103
- { re: /^[-*]$/, handler: () => this._convertToList('ul') },
104
- { re: /^1\.$/, handler: () => this._convertToList('ol') },
105
- { re: /^\[ \]$/, handler: () => this._convertToChecklist() },
106
- ];
107
-
108
- for (const { re, handler } of blockPatterns) {
109
- const m = re.exec(text);
110
- if (m) {
111
- handler(m);
112
- return true;
113
- }
114
- }
115
- return false;
116
- }
117
-
118
- /** Applies block rule on Enter key (---, ```). Returns true if a rule fired. */
119
- _applyEnterRule() {
120
- const ctx = this._getLineContext();
121
- if (!ctx) return false;
122
- const { text } = ctx;
123
-
124
- if (/^-{3,}$/.test(text)) {
125
- this._convertToHr();
126
- return true;
127
- }
128
- if (/^`{3}/.test(text)) {
129
- this._convertToCodeBlock();
130
- return true;
131
- }
132
- return false;
133
- }
134
-
135
- // ---------------------------------------------------------------------------
136
- // Block converters
137
- // ---------------------------------------------------------------------------
138
-
139
- _selectLineAndDelete() {
140
- const sel = globalThis.getSelection();
141
- if (!sel?.rangeCount) return;
142
- const range = sel.getRangeAt(0);
143
- // Select from the start of the block to the cursor and delete
144
- const startRange = document.createRange();
145
- startRange.setStart(range.startContainer.parentNode || range.startContainer, 0);
146
- startRange.setEnd(range.startContainer, range.startOffset);
147
- startRange.deleteContents();
148
- }
149
-
150
- _convertToHeading(level) {
151
- this._selectLineAndDelete();
152
- document.execCommand('formatBlock', false, `h${level}`);
153
- this.context.triggerEvent('change', this.context.getHTML());
154
- }
155
-
156
- _convertToBlockquote() {
157
- this._selectLineAndDelete();
158
- document.execCommand('formatBlock', false, 'blockquote');
159
- this.context.triggerEvent('change', this.context.getHTML());
160
- }
161
-
162
- _convertToList(type) {
163
- this._selectLineAndDelete();
164
- document.execCommand(type === 'ul' ? 'insertUnorderedList' : 'insertOrderedList');
165
- this.context.triggerEvent('change', this.context.getHTML());
166
- }
167
-
168
- _convertToChecklist() {
169
- this._selectLineAndDelete();
170
- this.context.invoke('editor.toggleChecklist');
171
- this.context.triggerEvent('change', this.context.getHTML());
172
- }
173
-
174
- _convertToHr() {
175
- this._selectLineAndDelete();
176
- this.context.invoke('editor.insertHr');
177
- this.context.triggerEvent('change', this.context.getHTML());
178
- }
179
-
180
- _convertToCodeBlock() {
181
- this._selectLineAndDelete();
182
- document.execCommand('formatBlock', false, 'pre');
183
- this.context.triggerEvent('change', this.context.getHTML());
184
- }
185
-
186
- // ---------------------------------------------------------------------------
187
- // Inline rules (input event)
188
- // ---------------------------------------------------------------------------
189
-
190
- _onInput() {
191
- const sel = globalThis.getSelection();
192
- if (!sel?.rangeCount) return;
193
- const range = sel.getRangeAt(0);
194
- if (!range.collapsed) return;
195
-
196
- const editable = this.context.layoutInfo.editable;
197
- if (!editable.contains(range.startContainer)) return;
198
-
199
- const node = range.startContainer;
200
- if (node.nodeType !== Node.TEXT_NODE) return;
201
-
202
- const text = node.textContent;
203
- const offset = range.startOffset;
204
-
205
- const inlineRules = [
206
- // **bold**
207
- { re: /\*\*(.+?)\*\*$/, tag: 'strong' },
208
- // *italic* (not part of **)
209
- { re: /(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)$/, tag: 'em' },
210
- // ~~strike~~
211
- { re: /~~(.+?)~~$/, tag: 's' },
212
- // `code`
213
- { re: /`([^`]+)`$/, tag: 'code' },
214
- ];
215
-
216
- const upToCursor = text.slice(0, offset);
217
- for (const { re, tag } of inlineRules) {
218
- const m = re.exec(upToCursor);
219
- if (!m) continue;
220
-
221
- const matchStart = upToCursor.length - m[0].length;
222
- const matchEnd = offset;
223
- const innerText = m[1];
224
-
225
- // Replace matched text with formatted element
226
- const before = text.slice(0, matchStart);
227
- const after = text.slice(matchEnd);
228
-
229
- const el = document.createElement(tag);
230
- el.textContent = innerText;
231
-
232
- // Rebuild the text node and insert the element
233
- const beforeNode = document.createTextNode(before);
234
- const afterNode = document.createTextNode('​' + after);
235
-
236
- /** @type {ChildNode} */ (node).before(beforeNode, el, afterNode);
237
- /** @type {ChildNode} */ (node).remove();
238
-
239
- // Place cursor after the element (after the ZWS)
240
- const newRange = document.createRange();
241
- newRange.setStart(afterNode, 1);
242
- newRange.collapse(true);
243
- sel.removeAllRanges();
244
- sel.addRange(newRange);
245
-
246
- this.context.triggerEvent('change', this.context.getHTML());
247
- break;
248
- }
249
- }
250
- }