autumnnote 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +74 -6
  2. package/dist/autumnnote.cjs +27 -22
  3. package/dist/autumnnote.es.js +474 -591
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.min.js +27 -22
  6. package/dist/autumnnote.umd.js +27 -22
  7. package/dist/autumnnote.umd.js.map +1 -1
  8. package/dist/icon-data-V0Xqv-wX.js +255 -0
  9. package/dist/icon-data-V0Xqv-wX.js.map +1 -0
  10. package/package.json +12 -3
  11. package/types/index.d.ts +8 -0
  12. package/src/js/Context.js +0 -854
  13. package/src/js/core/detectLang.js +0 -98
  14. package/src/js/core/dom.js +0 -372
  15. package/src/js/core/env.js +0 -38
  16. package/src/js/core/key.js +0 -66
  17. package/src/js/core/lists.js +0 -121
  18. package/src/js/core/markdown.js +0 -695
  19. package/src/js/core/range.js +0 -194
  20. package/src/js/core/sanitise.js +0 -304
  21. package/src/js/editing/History.js +0 -266
  22. package/src/js/editing/Style.js +0 -812
  23. package/src/js/editing/Table.js +0 -105
  24. package/src/js/editing/Typing.js +0 -397
  25. package/src/js/index.js +0 -193
  26. package/src/js/index.umd.js +0 -17
  27. package/src/js/module/AutoSaveRestore.js +0 -125
  28. package/src/js/module/BaseDialog.js +0 -133
  29. package/src/js/module/BaseMediaTooltip.js +0 -142
  30. package/src/js/module/BaseResizer.js +0 -322
  31. package/src/js/module/BubbleToolbar.js +0 -483
  32. package/src/js/module/Buttons.js +0 -399
  33. package/src/js/module/Clipboard.js +0 -579
  34. package/src/js/module/CodeTooltip.js +0 -493
  35. package/src/js/module/Codeview.js +0 -125
  36. package/src/js/module/ContextMenu.js +0 -621
  37. package/src/js/module/Editor.js +0 -747
  38. package/src/js/module/EmojiDialog.js +0 -254
  39. package/src/js/module/FindReplace.js +0 -512
  40. package/src/js/module/Fullscreen.js +0 -80
  41. package/src/js/module/IconDialog.js +0 -618
  42. package/src/js/module/ImageCropOverlay.js +0 -586
  43. package/src/js/module/ImageDialog.js +0 -193
  44. package/src/js/module/ImageResizer.js +0 -42
  45. package/src/js/module/ImageTooltip.js +0 -285
  46. package/src/js/module/LinkDialog.js +0 -145
  47. package/src/js/module/LinkTooltip.js +0 -250
  48. package/src/js/module/MarkdownShortcuts.js +0 -250
  49. package/src/js/module/Mention.js +0 -365
  50. package/src/js/module/Placeholder.js +0 -51
  51. package/src/js/module/ShortcutsDialog.js +0 -111
  52. package/src/js/module/SlashMenu.js +0 -376
  53. package/src/js/module/Statusbar.js +0 -246
  54. package/src/js/module/TableTooltip.js +0 -1392
  55. package/src/js/module/Toolbar.js +0 -855
  56. package/src/js/module/VideoDialog.js +0 -193
  57. package/src/js/module/VideoResizer.js +0 -66
  58. package/src/js/module/VideoTooltip.js +0 -248
  59. package/src/js/module/emoji-data.js +0 -496
  60. package/src/js/module/table-grid.js +0 -102
  61. package/src/js/module/table-icons.js +0 -33
  62. package/src/js/renderer.js +0 -120
  63. package/src/js/settings.js +0 -214
  64. package/src/styles/_variables.scss +0 -48
  65. package/src/styles/autumnnote.scss +0 -2877
@@ -1,193 +0,0 @@
1
- /**
2
- * VideoDialog.js - Dialog for inserting videos (YouTube, Vimeo, or direct file)
3
- * Supports:
4
- * • YouTube watch URLs → <iframe> embed
5
- * • YouTube short URLs → <iframe> embed
6
- * • Vimeo URLs → <iframe> embed
7
- * • Direct video URLs → <video> element (.mp4 / .webm / .ogg)
8
- */
9
-
10
- import { createElement, on } from '../core/dom.js';
11
- import { sanitiseUrl } from '../core/sanitise.js';
12
- import { BaseDialog } from './BaseDialog.js';
13
-
14
- const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg>`;
15
-
16
- export class VideoDialog extends BaseDialog {
17
- // ---------------------------------------------------------------------------
18
- // Public API
19
- // ---------------------------------------------------------------------------
20
-
21
- show() {
22
- this._saveRange();
23
- this._urlInput.value = '';
24
- this._widthInput.value = '560';
25
- this._hintEl.textContent = '';
26
- this._open();
27
- }
28
-
29
- // ---------------------------------------------------------------------------
30
- // Build dialog
31
- // ---------------------------------------------------------------------------
32
-
33
- _buildDialog() {
34
- const L = this.context.locale.videoDialog;
35
- const { overlay, box } = this._buildDialogShell(L.ariaLabel, ICON_SVG, L.title);
36
-
37
- // URL input
38
- const urlLabel = createElement('label', { class: 'an-label' });
39
- urlLabel.textContent = L.videoUrl;
40
- const urlInput = /** @type {HTMLInputElement} */ (createElement('input', {
41
- type: 'url',
42
- class: 'an-input',
43
- placeholder: L.urlPlaceholder,
44
- autocomplete: 'off',
45
- }));
46
- this._urlInput = urlInput;
47
- this._firstInput = urlInput;
48
-
49
- // Hint (detected source)
50
- const hintEl = createElement('p', { class: 'an-dialog-hint' });
51
- this._hintEl = hintEl;
52
-
53
- // Width
54
- const widthLabel = createElement('label', { class: 'an-label' });
55
- widthLabel.textContent = L.widthLabel;
56
- const widthInput = /** @type {HTMLInputElement} */ (createElement('input', {
57
- type: 'number',
58
- class: 'an-input',
59
- placeholder: '560',
60
- min: '80',
61
- max: '1920',
62
- value: '560',
63
- }));
64
- this._widthInput = widthInput;
65
-
66
- const btnRow = this._buildButtonRow(L.insertBtn, L.cancelBtn, () => this._onInsert());
67
- box.append(urlLabel, urlInput, hintEl, widthLabel, widthInput, btnRow);
68
-
69
- // Live URL hint
70
- const d0 = on(urlInput, 'input', () => {
71
- const info = this._parseVideoUrl(urlInput.value.trim());
72
- let hint = '';
73
- if (info) { hint = this.context.locale.videoDialog.detected(info.type); }
74
- else if (urlInput.value) { hint = this.context.locale.videoDialog.unknownFormat; }
75
- hintEl.textContent = hint;
76
- });
77
- const d4 = on(urlInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
78
- this._disposers.push(d0, d4);
79
-
80
- return overlay;
81
- }
82
-
83
- // ---------------------------------------------------------------------------
84
- // Actions
85
- // ---------------------------------------------------------------------------
86
-
87
- _onInsert() {
88
- const rawUrl = this._urlInput.value.trim();
89
- const width = Math.max(80, Number.parseInt(this._widthInput.value, 10) || 560);
90
-
91
- if (!rawUrl) {
92
- this._urlInput.focus();
93
- return;
94
- }
95
-
96
- const html = this._buildEmbedHtml(rawUrl, width);
97
- if (!html) {
98
- this._hintEl.textContent = this.context.locale.videoDialog.invalidUrl;
99
- this._urlInput.focus();
100
- return;
101
- }
102
-
103
- if (this._savedRange) this._savedRange.select();
104
- this.context.invoke('editor.insertVideo', html);
105
- this._close();
106
- }
107
-
108
- // ---------------------------------------------------------------------------
109
- // URL parsing & HTML building
110
- // ---------------------------------------------------------------------------
111
-
112
- /**
113
- * Parses a video URL and returns { type, embedUrl } or null.
114
- * @param {string} url
115
- * @returns {{ type: string, embedUrl: string }|null}
116
- */
117
- _parseVideoUrl(url) {
118
- if (!url) return null;
119
- if (!sanitiseUrl(url, { media: true })) return null;
120
-
121
- // YouTube watch: https://www.youtube.com/watch?v=ID
122
- const ytWatch = /(?:youtube\.com\/watch\?(?:.*&)?v=|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/.exec(url);
123
- if (ytWatch) return { type: 'YouTube', embedUrl: `https://www.youtube.com/embed/${ytWatch[1]}` };
124
-
125
- // YouTube short: https://youtu.be/ID
126
- const ytShort = /youtu\.be\/([a-zA-Z0-9_-]{11})/.exec(url);
127
- if (ytShort) return { type: 'YouTube', embedUrl: `https://www.youtube.com/embed/${ytShort[1]}` };
128
-
129
- // YouTube Shorts: https://www.youtube.com/shorts/ID
130
- const ytShorts = /youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/.exec(url);
131
- if (ytShorts) return { type: 'YouTube Shorts', embedUrl: `https://www.youtube.com/embed/${ytShorts[1]}` };
132
-
133
- // Vimeo: https://vimeo.com/ID
134
- const vimeo = /vimeo\.com\/(\d+)/.exec(url);
135
- if (vimeo) return { type: 'Vimeo', embedUrl: `https://player.vimeo.com/video/${vimeo[1]}` };
136
-
137
- // Direct video file
138
- if (/\.(mp4|webm|ogg|ogv|mov)(#.*|\?.*)?$/i.test(url)) {
139
- return { type: 'Direct video', embedUrl: url };
140
- }
141
-
142
- return null;
143
- }
144
-
145
- /**
146
- * Builds the HTML string to insert.
147
- * @param {string} url
148
- * @param {number} width
149
- * @returns {string|null}
150
- */
151
- _buildEmbedHtml(url, width) {
152
- const info = this._parseVideoUrl(url);
153
- const height = Math.round(width * 9 / 16); // 16:9
154
-
155
- if (info && (info.type === 'YouTube' || info.type === 'YouTube Shorts' || info.type === 'Vimeo')) {
156
- const iframeTitle = `${info.type} video player`;
157
- return (
158
- `<div class="an-video-wrapper" style="position:relative;display:block;width:${width}px;max-width:100%">` +
159
- `<iframe src="${info.embedUrl}" width="${width}" height="${height}" ` +
160
- `title="${iframeTitle}" ` +
161
- `frameborder="0" allowfullscreen ` +
162
- `allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" ` +
163
- `style="display:block;max-width:100%"></iframe>` +
164
- `<div class="an-video-shield"></div>` +
165
- `</div>`
166
- );
167
- }
168
-
169
- if (info?.type === 'Direct video') {
170
- const src = info.embedUrl.replaceAll('"', '%22');
171
- return (
172
- `<div class="an-video-wrapper" style="position:relative;display:block;width:${width}px;max-width:100%">` +
173
- `<video src="${src}" width="${width}" height="${height}" controls ` +
174
- `style="display:block;max-width:100%"></video>` +
175
- `<div class="an-video-shield"></div>` +
176
- `</div>`
177
- );
178
- }
179
-
180
- // Unknown URL — let the user try as a direct video
181
- const safeSrc = sanitiseUrl(url, { media: true });
182
- if (!safeSrc) return null;
183
-
184
- const escapedSrc = safeSrc.replaceAll('"', '%22');
185
- return (
186
- `<div class="an-video-wrapper" style="position:relative;display:block;width:${width}px;max-width:100%">` +
187
- `<video src="${escapedSrc}" width="${width}" height="${height}" controls ` +
188
- `style="display:block;max-width:100%"></video>` +
189
- `<div class="an-video-shield"></div>` +
190
- `</div>`
191
- );
192
- }
193
- }
@@ -1,66 +0,0 @@
1
- // VideoResizer.js - Interactive resize handles for selected videos in the editor
2
- // Targets .an-video-wrapper divs (containing <iframe> or <video>)
3
- import { on } from '../core/dom.js';
4
- import { BaseResizer } from './BaseResizer.js';
5
-
6
- export class VideoResizer extends BaseResizer {
7
- get _overlayClass() { return 'an-video-resizer'; }
8
- get _selectedClass() { return 'an-video-selected'; }
9
-
10
- /**
11
- * Walk up the DOM from `el` to find the nearest .an-video-wrapper,
12
- * or an iframe/video whose parent is .an-video-wrapper.
13
- * @param {EventTarget|null} el
14
- * @returns {HTMLElement|null}
15
- */
16
- _findTarget(el) {
17
- if (!el || !(el instanceof Element)) return null;
18
- if (el.classList?.contains('an-video-wrapper')) return /** @type {HTMLElement} */ (el);
19
- return /** @type {HTMLElement|null} */ (el.closest('.an-video-wrapper'));
20
- }
21
-
22
- /** @param {HTMLElement} wrapper */
23
- _getStartSize(wrapper) {
24
- return { w: wrapper.offsetWidth || 560, h: wrapper.offsetHeight || 315 };
25
- }
26
-
27
- _getMinSize() {
28
- return { minW: 80, minH: 45 };
29
- }
30
-
31
- /** @param {HTMLElement} wrapper @param {number} w @param {number} h */
32
- _applySize(wrapper, w, h) {
33
- // Resize wrapper and inner embed via CSS only — attribute writes are redundant
34
- wrapper.style.width = `${w}px`;
35
- wrapper.style.height = `${h}px`;
36
- const embed = /** @type {HTMLElement|null} */ (wrapper.querySelector('iframe, video'));
37
- if (embed) {
38
- embed.style.width = `${w}px`;
39
- embed.style.height = `${h}px`;
40
- }
41
- }
42
-
43
- _extraListeners() {
44
- const editable = this.context.layoutInfo.editable;
45
- return [
46
- // D1: Prevent native browser drag of video wrappers. Without this, a user
47
- // can hold-and-drag to produce a "copy" that lands outside .an-editable,
48
- // where the .an-video-shield CSS loses its containing context so the
49
- // copied video becomes directly playable.
50
- on(editable, 'dragstart', (e) => {
51
- if (e.target instanceof Element && e.target.closest('.an-video-wrapper')) {
52
- e.preventDefault();
53
- }
54
- }),
55
- ];
56
- }
57
-
58
- // ---------------------------------------------------------------------------
59
- // Public API used by other modules
60
- // ---------------------------------------------------------------------------
61
-
62
- /** @returns {HTMLElement|null} */
63
- getActiveWrapper() {
64
- return this._active;
65
- }
66
- }
@@ -1,248 +0,0 @@
1
- // VideoTooltip.js - Hover tooltip for video wrappers inside the editor
2
- // Displays a horizontal action bar below (or above) the selected video,
3
- // similar in appearance and interaction to LinkTooltip.
4
- import { createElement, on } from '../core/dom.js';
5
- import { BaseMediaTooltip } from './BaseMediaTooltip.js';
6
-
7
- const ICONS = {
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>`,
9
- floatRight: `<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="14" y="4" width="8" height="8" rx="1"/><line x1="2" y1="6" x2="12" y2="6"/><line x1="2" y1="9" x2="12" y2="9"/><line x1="2" y1="12" x2="12" y2="12"/><line x1="2" y1="16" x2="22" y2="16"/><line x1="2" y1="20" x2="18" y2="20"/></svg>`,
10
- floatNone: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="8" height="8" rx="1"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="3" y1="19" x2="17" y2="19"/></svg>`,
11
- alignCenter: `<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="7" y="4" width="10" height="8" rx="1"/><line x1="3" y1="16" x2="21" y2="16"/><line x1="6" y1="20" x2="18" y2="20"/></svg>`,
12
- originalSize:`<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="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>`,
13
- deleteVideo: `<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="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>`,
14
- preview: `<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"><polygon points="5 3 19 12 5 21 5 3"/></svg>`,
15
- };
16
-
17
- export class VideoTooltip extends BaseMediaTooltip {
18
- initialize() {
19
- this._el = this._buildTooltip();
20
- document.body.appendChild(this._el);
21
-
22
- const editable = this.context.layoutInfo.editable;
23
-
24
- this._disposers.push(
25
- on(editable, 'mouseover', (e) => {
26
- if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
27
- // The shield div sits on top of iframes — we detect hover via it or the wrapper
28
- const target = /** @type {Element} */ (e.target);
29
- const wrapper = target?.closest('.an-video-wrapper');
30
- if (wrapper && editable.contains(wrapper)) {
31
- this._scheduleShow(wrapper);
32
- }
33
- }, { passive: true }),
34
- on(editable, 'mouseout', (e) => {
35
- const to = /** @type {MouseEvent} */ (e).relatedTarget;
36
- if (!to || (!editable.contains(/** @type {Node} */ (to)) && !this._el.contains(/** @type {Node} */ (to)))) {
37
- this._scheduleHide();
38
- }
39
- }, { passive: true }),
40
- on(document, 'click', (e) => {
41
- const target = /** @type {Node} */ (e.target);
42
- if (
43
- this._active &&
44
- !this._active.contains(target) &&
45
- !this._el.contains(target)
46
- ) {
47
- this._hide();
48
- }
49
- }),
50
- // Hide when the page scrolls or resizes — the tooltip position becomes stale
51
- on(globalThis, 'scroll', () => this._hide(), { passive: true }),
52
- on(globalThis, 'resize', () => this._hide(), { passive: true }),
53
- );
54
-
55
- return this;
56
- }
57
-
58
- destroy() {
59
- if (this._previewMode) this._exitPreview();
60
- this._clearTimers();
61
- this._disposers.forEach((d) => d());
62
- this._disposers = [];
63
- this._el?.remove();
64
- this._el = null;
65
- }
66
-
67
- // ---------------------------------------------------------------------------
68
- // Base hooks — keep the bar pinned while a preview is playing
69
- // ---------------------------------------------------------------------------
70
-
71
- /** @returns {boolean} */
72
- _canHide() { return !this._previewMode; }
73
-
74
- _beforeHide() {
75
- if (this._previewMode) this._exitPreview();
76
- }
77
-
78
- // ---------------------------------------------------------------------------
79
- // Build
80
- // ---------------------------------------------------------------------------
81
-
82
- _buildTooltip() {
83
- const L = this.context.locale.tooltips.video;
84
- const el = createElement('div', {
85
- class: 'an-link-tooltip an-video-tooltip',
86
- role: 'toolbar',
87
- 'aria-label': L.ariaLabel,
88
- });
89
- el.style.display = 'none';
90
-
91
- // Label
92
- this._label = createElement('span', { class: 'an-link-tooltip-url' });
93
- this._label.textContent = L.label;
94
- el.appendChild(this._label);
95
-
96
- el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
97
-
98
- this._floatLeftBtn = this._makeBtn(ICONS.floatLeft, L.floatLeft, () => this._setFloat('left'));
99
- this._floatNoneBtn = this._makeBtn(ICONS.floatNone, L.noFloat, () => this._setFloat(''));
100
- this._alignCenterBtn = this._makeBtn(ICONS.alignCenter, L.alignCenter, () => this._setCenter());
101
- this._floatRightBtn = this._makeBtn(ICONS.floatRight, L.floatRight, () => this._setFloat('right'));
102
-
103
- el.appendChild(this._floatLeftBtn);
104
- el.appendChild(this._floatNoneBtn);
105
- el.appendChild(this._alignCenterBtn);
106
- el.appendChild(this._floatRightBtn);
107
-
108
- el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
109
-
110
- this._originalBtn = this._makeBtn(ICONS.originalSize, L.originalSize, () => this._resetSize());
111
-
112
- el.appendChild(this._originalBtn);
113
-
114
- el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
115
-
116
- this._previewBtn = this._makeBtn(ICONS.preview, L.previewVideo, () => this._togglePreview());
117
- el.appendChild(this._previewBtn);
118
-
119
- el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
120
-
121
- this._deleteBtn = this._makeBtn(ICONS.deleteVideo, L.deleteVideo, () => this._delete(), true);
122
-
123
- el.appendChild(this._deleteBtn);
124
-
125
- // Keep tooltip alive while hovering it
126
- this._disposers.push(
127
- on(el, 'mouseenter', () => this._clearTimers()),
128
- on(el, 'mouseleave', () => this._scheduleHide()),
129
- );
130
-
131
- return el;
132
- }
133
-
134
- // ---------------------------------------------------------------------------
135
- // Actions
136
- // ---------------------------------------------------------------------------
137
-
138
- _setFloat(value) {
139
- const wrapper = this._active;
140
- if (!wrapper) return;
141
- wrapper.style.float = value;
142
- wrapper.style.display = value ? 'inline-block' : '';
143
- wrapper.style.marginLeft = value === 'right' ? '12px' : '';
144
- wrapper.style.marginRight = value === 'left' ? '12px' : '';
145
- this.context.invoke('editor.afterCommand');
146
- this.context.invoke('videoResizer.updateOverlay');
147
- requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
148
- }
149
-
150
- _setCenter() {
151
- const wrapper = this._active;
152
- if (!wrapper) return;
153
- wrapper.style.float = '';
154
- wrapper.style.display = 'block';
155
- wrapper.style.marginLeft = 'auto';
156
- wrapper.style.marginRight = 'auto';
157
- this.context.invoke('editor.afterCommand');
158
- this.context.invoke('videoResizer.updateOverlay');
159
- requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
160
- }
161
-
162
- _resetSize() {
163
- const wrapper = this._active;
164
- if (!wrapper) return;
165
- const embed = /** @type {HTMLElement|null} */ (wrapper.querySelector('iframe, video'));
166
- wrapper.style.width = '';
167
- wrapper.style.height = '';
168
- if (embed) {
169
- embed.removeAttribute('width');
170
- embed.removeAttribute('height');
171
- embed.style.width = '';
172
- embed.style.height = '';
173
- }
174
- this.context.invoke('editor.afterCommand');
175
- this.context.invoke('videoResizer.updateOverlay');
176
- requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
177
- }
178
-
179
- _delete() {
180
- const wrapper = this._active;
181
- if (!wrapper) return;
182
- this._hide();
183
- this.context.invoke('videoResizer.deselect');
184
- wrapper.remove();
185
- this.context.invoke('editor.afterCommand');
186
- }
187
-
188
- // ---------------------------------------------------------------------------
189
- // Preview mode — temporarily disables the shield so the video is interactive
190
- // ---------------------------------------------------------------------------
191
-
192
- _togglePreview() {
193
- if (this._previewMode) {
194
- this._exitPreview();
195
- } else {
196
- this._enterPreview();
197
- }
198
- }
199
-
200
- _enterPreview() {
201
- const wrapper = this._active;
202
- if (!wrapper) return;
203
- this._previewMode = true;
204
-
205
- // Hide the shield so the iframe / video receives pointer events directly
206
- const shield = /** @type {HTMLElement|null} */ (wrapper.querySelector('.an-video-shield'));
207
- if (shield) shield.style.display = 'none';
208
-
209
- // Hide the resize overlay — it would block interaction with the embed
210
- this.context.invoke('videoResizer.deselect');
211
-
212
- // Visual feedback: button turns primary-coloured
213
- this._previewBtn.classList.add('an-link-tooltip-btn--copied');
214
- this._previewBtn.title = this.context.locale.tooltips.video.exitPreview;
215
-
216
- // Exit preview on mousedown outside the wrapper.
217
- // Using 'mousedown' (not 'click') so clicks inside an iframe
218
- // — which never bubble to the outer document — don't accidentally
219
- // leave preview mode stuck. For <video> elements, mousedown on the
220
- // video is inside the wrapper so contains() returns true → no exit.
221
- this._previewClickOff = (e) => {
222
- if (!wrapper.contains(e.target) && !this._el.contains(e.target)) {
223
- this._exitPreview();
224
- }
225
- };
226
- document.addEventListener('mousedown', this._previewClickOff, true);
227
- }
228
-
229
- _exitPreview() {
230
- this._previewMode = false;
231
-
232
- const wrapper = this._active;
233
- if (wrapper) {
234
- // Restore the shield
235
- const shield = /** @type {HTMLElement|null} */ (wrapper.querySelector('.an-video-shield'));
236
- if (shield) shield.style.display = '';
237
- }
238
-
239
- // Reset button appearance
240
- this._previewBtn.classList.remove('an-link-tooltip-btn--copied');
241
- this._previewBtn.title = this.context.locale.tooltips.video.previewVideo;
242
-
243
- if (this._previewClickOff) {
244
- document.removeEventListener('mousedown', this._previewClickOff, true);
245
- this._previewClickOff = null;
246
- }
247
- }
248
- }