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,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
- }