autumnnote 1.15.0 → 2.0.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 (51) hide show
  1. package/README.md +53 -2
  2. package/dist/autumnnote.cjs +21 -21
  3. package/dist/autumnnote.css +1 -1
  4. package/dist/autumnnote.es.js +1470 -5884
  5. package/dist/autumnnote.es.js.map +1 -1
  6. package/dist/autumnnote.min.js +61 -0
  7. package/dist/autumnnote.umd.js +21 -21
  8. package/dist/autumnnote.umd.js.map +1 -1
  9. package/dist/emoji-data-BDQX5kh-.js +2331 -0
  10. package/dist/emoji-data-BDQX5kh-.js.map +1 -0
  11. package/package.json +9 -5
  12. package/src/js/Context.js +216 -17
  13. package/src/js/editing/History.js +38 -6
  14. package/src/js/i18n/all.js +27 -0
  15. package/src/js/i18n/de.js +15 -0
  16. package/src/js/i18n/en.js +15 -0
  17. package/src/js/i18n/es.js +15 -0
  18. package/src/js/i18n/fr.js +15 -0
  19. package/src/js/i18n/index.js +45 -19
  20. package/src/js/i18n/ja.js +15 -0
  21. package/src/js/i18n/ko.js +15 -0
  22. package/src/js/i18n/vi.js +15 -0
  23. package/src/js/i18n/zh.js +15 -0
  24. package/src/js/index.js +24 -2
  25. package/src/js/index.umd.js +5 -0
  26. package/src/js/module/BaseMediaTooltip.js +142 -0
  27. package/src/js/module/BaseResizer.js +312 -0
  28. package/src/js/module/Clipboard.js +20 -6
  29. package/src/js/module/Editor.js +15 -1
  30. package/src/js/module/EmojiDialog.js +41 -494
  31. package/src/js/module/ImageDialog.js +34 -6
  32. package/src/js/module/ImageResizer.js +23 -241
  33. package/src/js/module/ImageTooltip.js +16 -111
  34. package/src/js/module/MarkdownShortcuts.js +2 -2
  35. package/src/js/module/SlashMenu.js +376 -0
  36. package/src/js/module/Statusbar.js +3 -9
  37. package/src/js/module/VideoResizer.js +38 -239
  38. package/src/js/module/VideoTooltip.js +27 -114
  39. package/src/js/module/emoji-data.js +496 -0
  40. package/src/js/settings.js +27 -0
  41. package/src/styles/autumnnote.scss +49 -0
  42. package/types/i18n/all.d.ts +14 -0
  43. package/types/i18n/de.d.ts +4 -0
  44. package/types/i18n/en.d.ts +4 -0
  45. package/types/i18n/es.d.ts +4 -0
  46. package/types/i18n/fr.d.ts +4 -0
  47. package/types/i18n/ja.d.ts +4 -0
  48. package/types/i18n/ko.d.ts +4 -0
  49. package/types/i18n/vi.d.ts +4 -0
  50. package/types/i18n/zh.d.ts +4 -0
  51. package/types/index.d.ts +113 -4
@@ -1,84 +1,34 @@
1
1
  // ImageResizer.js - Interactive resize handles for selected images in the editor
2
- import { on } from '../core/dom.js';
2
+ import { BaseResizer } from './BaseResizer.js';
3
3
 
4
- /**
5
- * Eight handle positions. 'pos' is used as CSS class suffix and
6
- * to determine which axis/direction is being dragged.
7
- *
8
- * Pos length 2 → corner handle → maintain aspect ratio.
9
- * Pos length 1 → edge handle → single-axis resize.
10
- */
11
- const HANDLE_DEFS = [
12
- { pos: 'nw', cursor: 'nw-resize' },
13
- { pos: 'n', cursor: 'n-resize' },
14
- { pos: 'ne', cursor: 'ne-resize' },
15
- { pos: 'e', cursor: 'e-resize' },
16
- { pos: 'se', cursor: 'se-resize' },
17
- { pos: 's', cursor: 's-resize' },
18
- { pos: 'sw', cursor: 'sw-resize' },
19
- { pos: 'w', cursor: 'w-resize' },
20
- ];
4
+ export class ImageResizer extends BaseResizer {
5
+ get _overlayClass() { return 'an-image-resizer'; }
6
+ get _selectedClass() { return 'an-image-selected'; }
21
7
 
22
- export class ImageResizer {
23
- /** @param {import('../Context.js').Context} context */
24
- constructor(context) {
25
- this.context = context;
26
- /** @type {HTMLImageElement|null} */
27
- this._activeImg = null;
28
- this._overlay = null;
29
- this._disposers = [];
30
- this._positionRaf = null;
8
+ /** @param {EventTarget|null} el @returns {HTMLImageElement|null} */
9
+ _findTarget(el) {
10
+ return /** @type {HTMLImageElement|null} */ (
11
+ el instanceof Element ? el.closest('img') : null
12
+ );
31
13
  }
32
14
 
33
- initialize() {
34
- this._overlay = this._buildOverlay();
35
- const container = this.context.layoutInfo.editable.closest('.an-container') || document.body;
36
- container.appendChild(this._overlay);
37
- this._container = container;
38
-
39
- const editable = this.context.layoutInfo.editable;
40
-
41
- // Debounce globalThis resize — _updateOverlayPosition already has rAF gating but
42
- // every call cancels + re-schedules it; a debounce reduces that churn.
43
- let _resizeDebounce = null;
44
- const onWindowResize = () => {
45
- clearTimeout(_resizeDebounce);
46
- _resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
15
+ /** @param {HTMLImageElement} img */
16
+ _getStartSize(img) {
17
+ return {
18
+ w: img.offsetWidth || img.naturalWidth || 100,
19
+ h: img.offsetHeight || img.naturalHeight || 100,
47
20
  };
21
+ }
48
22
 
49
- this._disposers.push(
50
- on(editable, 'click', (e) => this._onEditorClick(e)),
51
- // Also select on right-click so the highlight shows before the context menu
52
- on(editable, 'contextmenu', (e) => {
53
- if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
54
- const img = /** @type {Element} */ (e.target)?.closest('img');
55
- if (img) this._select(img);
56
- }),
57
- on(document, 'click', (e) => this._onDocClick(e)),
58
- on(globalThis, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
59
- on(globalThis, 'resize', onWindowResize, { passive: true }),
60
- on(editable, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
61
- );
62
-
63
- return this;
23
+ _getMinSize() {
24
+ const min = this.context.options?.minImageSize ?? 20;
25
+ return { minW: min, minH: min };
64
26
  }
65
27
 
66
- destroy() {
67
- this._disposers.forEach((d) => d());
68
- this._disposers = [];
69
- if (this._dragDisposers) {
70
- this._dragDisposers.forEach((d) => d());
71
- this._dragDisposers = null;
72
- }
73
- if (this._positionRaf) {
74
- cancelAnimationFrame(this._positionRaf);
75
- this._positionRaf = null;
76
- }
77
- this._deselect();
78
- if (this._overlay?.parentNode) {
79
- this._overlay.remove();
80
- }
81
- this._overlay = null;
28
+ /** @param {HTMLImageElement} img @param {number} w @param {number} h */
29
+ _applySize(img, w, h) {
30
+ img.style.width = `${w}px`;
31
+ img.style.height = `${h}px`;
82
32
  }
83
33
 
84
34
  // ---------------------------------------------------------------------------
@@ -87,174 +37,6 @@ export class ImageResizer {
87
37
 
88
38
  /** @returns {HTMLImageElement|null} */
89
39
  getActiveImage() {
90
- return this._activeImg;
91
- }
92
-
93
- /** Re-sync overlay position (call after external size changes). */
94
- updateOverlay() {
95
- this._updateOverlayPosition();
96
- }
97
-
98
- /** Programmatically clear the current image selection. */
99
- deselect() {
100
- this._deselect();
101
- }
102
-
103
- // ---------------------------------------------------------------------------
104
- // Internal
105
- // ---------------------------------------------------------------------------
106
-
107
- _buildOverlay() {
108
- const overlay = document.createElement('div');
109
- overlay.className = 'an-image-resizer';
110
- overlay.style.display = 'none';
111
-
112
- HANDLE_DEFS.forEach(({ pos }) => {
113
- const h = document.createElement('div');
114
- h.className = `an-resize-handle an-resize-${pos}`;
115
- h.dataset.handle = pos;
116
- // Attach handle listeners here so they're torn down in destroy() via _disposers
117
- this._disposers.push(
118
- on(h, 'mousedown', (e) => {
119
- e.preventDefault();
120
- e.stopPropagation();
121
- this._startResize(e, pos);
122
- }),
123
- );
124
- overlay.appendChild(h);
125
- });
126
-
127
- return overlay;
128
- }
129
-
130
- _onEditorClick(e) {
131
- if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
132
- const img = e.target.closest('img');
133
- if (img) {
134
- // Prevent browser from deselecting the current text selection / opening native resize
135
- e.preventDefault();
136
- this._select(img);
137
- }
138
- }
139
-
140
- _onDocClick(e) {
141
- if (!this._activeImg) return;
142
- if (e.target === this._activeImg) return;
143
- if (this._overlay?.contains(e.target)) return;
144
- // Don't deselect while interacting with the context menu
145
- if (e.target.closest('.an-contextmenu')) return;
146
- this._deselect();
147
- }
148
-
149
- _select(img) {
150
- if (this._activeImg && this._activeImg !== img) {
151
- this._activeImg.classList.remove('an-image-selected');
152
- }
153
- this._activeImg = img;
154
- this._lastOverlayPos = null; // invalidate position cache on new selection
155
- img.classList.add('an-image-selected');
156
- this._updateOverlayPosition();
157
- this._overlay.style.display = 'block';
158
- }
159
-
160
- _deselect() {
161
- if (this._activeImg) {
162
- this._activeImg.classList.remove('an-image-selected');
163
- this._activeImg = null;
164
- }
165
- if (this._overlay) this._overlay.style.display = 'none';
166
- }
167
-
168
- _updateOverlayPosition() {
169
- if (this._positionRaf) cancelAnimationFrame(this._positionRaf);
170
- this._positionRaf = requestAnimationFrame(() => {
171
- this._positionRaf = null;
172
- this._updateOverlayPositionNow();
173
- });
174
- }
175
-
176
- _updateOverlayPositionNow() {
177
- if (!this._activeImg || !this._overlay) return;
178
- const offsetParent = this._overlay.offsetParent || this._container;
179
- const containerRect = offsetParent.getBoundingClientRect();
180
- const rect = this._activeImg.getBoundingClientRect();
181
-
182
- // Skip DOM writes when position hasn't changed (common during non-scroll rAF ticks)
183
- const p = this._lastOverlayPos;
184
- if (p?.l === rect.left && p.t === rect.top && p.w === rect.width && p.h === rect.height) return;
185
- this._lastOverlayPos = { l: rect.left, t: rect.top, w: rect.width, h: rect.height };
186
-
187
- const left = rect.left - containerRect.left + offsetParent.scrollLeft;
188
- const top = rect.top - containerRect.top + offsetParent.scrollTop;
189
- this._overlay.style.left = `${left}px`;
190
- this._overlay.style.top = `${top}px`;
191
- this._overlay.style.width = `${rect.width}px`;
192
- this._overlay.style.height = `${rect.height}px`;
193
- }
194
-
195
- _startResize(e, pos) {
196
- const img = this._activeImg;
197
- if (!img) return;
198
-
199
- const startX = e.clientX;
200
- const startY = e.clientY;
201
- const startW = img.offsetWidth || img.naturalWidth || 100;
202
- const startH = img.offsetHeight || img.naturalHeight || 100;
203
- const aspectRatio = startW / (startH || 1);
204
- const isCorner = pos.length === 2; // 'nw','ne','se','sw'
205
-
206
- const editable = this.context.layoutInfo.editable;
207
- const minSz = this.context.options?.minImageSize ?? 20;
208
- let _raf = null; // rAF handle — ensures at most one write per paint frame
209
-
210
- const onMove = (me) => {
211
- if (_raf !== null) return; // frame already pending, discard this event
212
- const clientX = me.clientX;
213
- const clientY = me.clientY;
214
- _raf = requestAnimationFrame(() => {
215
- _raf = null;
216
- const dx = clientX - startX;
217
- const dy = clientY - startY;
218
- const maxW = editable.clientWidth || Infinity;
219
- let newW = startW;
220
- let newH = startH;
221
-
222
- if (pos.includes('e')) newW = Math.max(minSz, startW + dx);
223
- if (pos.includes('w')) newW = Math.max(minSz, startW - dx);
224
- if (pos.includes('s')) newH = Math.max(minSz, startH + dy);
225
- if (pos.includes('n')) newH = Math.max(minSz, startH - dy);
226
-
227
- newW = Math.min(newW, maxW);
228
-
229
- if (isCorner) {
230
- if (Math.abs(dx) >= Math.abs(dy)) {
231
- newH = Math.max(minSz, Math.round(newW / aspectRatio));
232
- } else {
233
- newW = Math.min(Math.max(minSz, Math.round(newH * aspectRatio)), maxW);
234
- newH = Math.max(minSz, Math.round(newW / aspectRatio));
235
- }
236
- }
237
-
238
- img.style.width = `${newW}px`;
239
- img.style.height = `${newH}px`;
240
- this._updateOverlayPosition();
241
- });
242
- };
243
-
244
- const onUp = () => {
245
- if (_raf !== null) { cancelAnimationFrame(_raf); _raf = null; }
246
- document.removeEventListener('mousemove', onMove);
247
- document.removeEventListener('mouseup', onUp);
248
- this._dragDisposers = null;
249
- this.context.invoke('editor.afterCommand');
250
- };
251
-
252
- document.addEventListener('mousemove', onMove);
253
- document.addEventListener('mouseup', onUp);
254
- // Track these so destroy() can clean them up if called during an active drag
255
- this._dragDisposers = [
256
- () => document.removeEventListener('mousemove', onMove),
257
- () => document.removeEventListener('mouseup', onUp),
258
- ];
40
+ return /** @type {HTMLImageElement|null} */ (this._active);
259
41
  }
260
42
  }
@@ -2,6 +2,7 @@
2
2
  // Displays a horizontal action bar below (or above) the selected image,
3
3
  // similar in appearance and interaction to LinkTooltip.
4
4
  import { createElement, on } from '../core/dom.js';
5
+ import { BaseMediaTooltip } from './BaseMediaTooltip.js';
5
6
 
6
7
  const ICONS = {
7
8
  floatLeft: `<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="2" y="4" width="8" height="8" rx="1"/><line x1="12" y1="6" x2="22" y2="6"/><line x1="12" y1="9" x2="22" y2="9"/><line x1="12" y1="12" x2="22" y2="12"/><line x1="2" y1="16" x2="22" y2="16"/><line x1="2" y1="20" x2="18" y2="20"/></svg>`,
@@ -16,20 +17,7 @@ const ICONS = {
16
17
  crop: `<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"><polyline points="6 2 6 18 22 18"/><polyline points="2 6 18 6 18 22"/></svg>`,
17
18
  };
18
19
 
19
- const SHOW_DELAY = 100;
20
- const HIDE_DELAY = 180;
21
-
22
- export class ImageTooltip {
23
- /** @param {import('../Context.js').Context} context */
24
- constructor(context) {
25
- this.context = context;
26
- this._el = null;
27
- this._activeImg = null;
28
- this._showTimer = null;
29
- this._hideTimer = null;
30
- this._disposers = [];
31
- }
32
-
20
+ export class ImageTooltip extends BaseMediaTooltip {
33
21
  initialize() {
34
22
  this._el = this._buildTooltip();
35
23
  document.body.appendChild(this._el);
@@ -54,7 +42,7 @@ export class ImageTooltip {
54
42
  // Hide when image is deselected by clicking elsewhere
55
43
  on(document, 'click', (e) => {
56
44
  const et = /** @type {Node} */ (e.target);
57
- if (this._activeImg && !this._activeImg.contains(et) && !this._el.contains(et)) {
45
+ if (this._active && !this._active.contains(et) && !this._el.contains(et)) {
58
46
  this._hide();
59
47
  }
60
48
  }),
@@ -140,97 +128,14 @@ export class ImageTooltip {
140
128
  return el;
141
129
  }
142
130
 
143
- /**
144
- * @param {string} icon
145
- * @param {string} title
146
- * @param {Function} handler
147
- * @param {boolean} [isDanger]
148
- */
149
- _makeBtn(icon, title, handler, isDanger = false) {
150
- const btn = createElement('button', {
151
- type: 'button',
152
- class: isDanger ? 'an-link-tooltip-btn an-link-tooltip-btn--danger' : 'an-link-tooltip-btn',
153
- title,
154
- });
155
- btn.innerHTML = icon;
156
- this._disposers.push(on(btn, 'click', (e) => {
157
- e.preventDefault();
158
- e.stopPropagation();
159
- handler();
160
- }));
161
- return btn;
162
- }
163
-
164
- // ---------------------------------------------------------------------------
165
- // Show / Hide
166
- // ---------------------------------------------------------------------------
167
-
168
- _scheduleShow(img) {
169
- if (this._activeImg === img && this._el.style.display !== 'none') return;
170
- clearTimeout(this._hideTimer);
171
- this._hideTimer = null;
172
- clearTimeout(this._showTimer);
173
- this._showTimer = setTimeout(() => {
174
- this._activeImg = img;
175
- this._show(img);
176
- }, SHOW_DELAY);
177
- }
178
-
179
- _scheduleHide() {
180
- clearTimeout(this._showTimer);
181
- this._showTimer = null;
182
- // Always reset the hide timer so rapid mouseout→mouseover sequences
183
- // don't leave a stale timer that hides the tooltip prematurely.
184
- clearTimeout(this._hideTimer);
185
- this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY);
186
- }
187
-
188
- _show(_img) {
189
- this._el.style.display = 'flex';
190
- // Defer positioning: offsetWidth on a newly-visible element forces layout
191
- requestAnimationFrame(() => {
192
- if (this._activeImg) this._positionNear(this._activeImg);
193
- });
194
- }
195
-
196
- _hide() {
197
- this._el.style.display = 'none';
198
- this._activeImg = null;
199
- this._clearTimers();
200
- }
201
-
202
- _clearTimers() {
203
- clearTimeout(this._showTimer);
204
- clearTimeout(this._hideTimer);
205
- this._showTimer = null;
206
- this._hideTimer = null;
207
- }
208
-
209
- _positionNear(img) {
210
- const rect = img.getBoundingClientRect();
211
- const tipW = this._el.offsetWidth || 220;
212
- const tipH = this._el.offsetHeight || 32;
213
- const margin = 6;
214
-
215
- let top = rect.bottom + margin;
216
- let left = rect.left + (rect.width - tipW) / 2;
217
-
218
- if (top + tipH > globalThis.innerHeight - margin) top = rect.top - tipH - margin;
219
- if (left + tipW > globalThis.innerWidth - margin) left = globalThis.innerWidth - tipW - margin;
220
- if (left < margin) left = margin;
221
-
222
- this._el.style.top = `${top}px`;
223
- this._el.style.left = `${left}px`;
224
- }
225
-
226
131
  // ---------------------------------------------------------------------------
227
132
  // Actions
228
133
  // ---------------------------------------------------------------------------
229
134
 
230
135
  _setFloat(value) {
231
- const img = this._activeImg;
136
+ const img = this._active;
232
137
  if (!img) return;
233
- const target = img.closest('figure.an-figure') || img;
138
+ const target = /** @type {HTMLElement} */ (img.closest('figure.an-figure') || img);
234
139
  target.style.float = value;
235
140
  target.style.display = '';
236
141
  // Clear explicit width set for centering so the figure is free to float
@@ -239,13 +144,13 @@ export class ImageTooltip {
239
144
  target.style.marginRight = value === 'left' ? '12px' : '';
240
145
  this.context.invoke('editor.afterCommand');
241
146
  this.context.invoke('imageResizer.updateOverlay');
242
- requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
147
+ requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
243
148
  }
244
149
 
245
150
  _setCenter() {
246
- const img = this._activeImg;
151
+ const img = this._active;
247
152
  if (!img) return;
248
- const target = img.closest('figure.an-figure') || img;
153
+ const target = /** @type {HTMLElement} */ (img.closest('figure.an-figure') || img);
249
154
  target.style.float = '';
250
155
  target.style.display = 'block';
251
156
  // C1/C3: use fit-content so the block figure shrinks to the image width;
@@ -255,17 +160,17 @@ export class ImageTooltip {
255
160
  target.style.marginRight = 'auto';
256
161
  this.context.invoke('editor.afterCommand');
257
162
  this.context.invoke('imageResizer.updateOverlay');
258
- requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
163
+ requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
259
164
  }
260
165
 
261
166
  _resetSize() {
262
- const img = this._activeImg;
167
+ const img = this._active;
263
168
  if (!img) return;
264
169
  img.style.width = '';
265
170
  img.style.height = '';
266
171
  this.context.invoke('editor.afterCommand');
267
172
  this.context.invoke('imageResizer.updateOverlay');
268
- requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
173
+ requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
269
174
  }
270
175
 
271
176
  /**
@@ -275,7 +180,7 @@ export class ImageTooltip {
275
180
  * @param {number} delta
276
181
  */
277
182
  _rotate(delta) {
278
- const img = this._activeImg;
183
+ const img = this._active;
279
184
  if (!img) return;
280
185
  // Parse current rotation angle from inline transform
281
186
  const current = img.style.transform || '';
@@ -291,11 +196,11 @@ export class ImageTooltip {
291
196
  }
292
197
  this.context.invoke('editor.afterCommand');
293
198
  this.context.invoke('imageResizer.updateOverlay');
294
- requestAnimationFrame(() => { if (this._activeImg) this._positionNear(this._activeImg); });
199
+ requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
295
200
  }
296
201
 
297
202
  _delete() {
298
- const img = this._activeImg;
203
+ const img = this._active;
299
204
  if (!img) return;
300
205
  this._hide();
301
206
  this.context.invoke('imageResizer.deselect');
@@ -309,7 +214,7 @@ export class ImageTooltip {
309
214
  }
310
215
 
311
216
  _crop() {
312
- const img = this._activeImg;
217
+ const img = this._active;
313
218
  if (!img) return;
314
219
  // Hide the tooltip while the crop overlay is active
315
220
  this._hide();
@@ -318,7 +223,7 @@ export class ImageTooltip {
318
223
  }
319
224
 
320
225
  _toggleCaption() {
321
- const img = this._activeImg;
226
+ const img = this._active;
322
227
  if (!img) return;
323
228
 
324
229
  // If already inside a figure, move focus to the figcaption
@@ -167,13 +167,13 @@ export class MarkdownShortcuts {
167
167
 
168
168
  _convertToChecklist() {
169
169
  this._selectLineAndDelete();
170
- this.context.invoke('editor.checklist');
170
+ this.context.invoke('editor.toggleChecklist');
171
171
  this.context.triggerEvent('change', this.context.getHTML());
172
172
  }
173
173
 
174
174
  _convertToHr() {
175
175
  this._selectLineAndDelete();
176
- this.context.invoke('editor.insertHR');
176
+ this.context.invoke('editor.insertHr');
177
177
  this.context.triggerEvent('change', this.context.getHTML());
178
178
  }
179
179