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.
- package/README.md +53 -2
- package/dist/autumnnote.cjs +21 -21
- package/dist/autumnnote.css +1 -1
- package/dist/autumnnote.es.js +1470 -5884
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.min.js +61 -0
- package/dist/autumnnote.umd.js +21 -21
- package/dist/autumnnote.umd.js.map +1 -1
- package/dist/emoji-data-BDQX5kh-.js +2331 -0
- package/dist/emoji-data-BDQX5kh-.js.map +1 -0
- package/package.json +9 -5
- package/src/js/Context.js +216 -17
- package/src/js/editing/History.js +38 -6
- package/src/js/i18n/all.js +27 -0
- package/src/js/i18n/de.js +15 -0
- package/src/js/i18n/en.js +15 -0
- package/src/js/i18n/es.js +15 -0
- package/src/js/i18n/fr.js +15 -0
- package/src/js/i18n/index.js +45 -19
- package/src/js/i18n/ja.js +15 -0
- package/src/js/i18n/ko.js +15 -0
- package/src/js/i18n/vi.js +15 -0
- package/src/js/i18n/zh.js +15 -0
- package/src/js/index.js +24 -2
- package/src/js/index.umd.js +5 -0
- package/src/js/module/BaseMediaTooltip.js +142 -0
- package/src/js/module/BaseResizer.js +312 -0
- package/src/js/module/Clipboard.js +20 -6
- package/src/js/module/Editor.js +15 -1
- package/src/js/module/EmojiDialog.js +41 -494
- package/src/js/module/ImageDialog.js +34 -6
- package/src/js/module/ImageResizer.js +23 -241
- package/src/js/module/ImageTooltip.js +16 -111
- package/src/js/module/MarkdownShortcuts.js +2 -2
- package/src/js/module/SlashMenu.js +376 -0
- package/src/js/module/Statusbar.js +3 -9
- package/src/js/module/VideoResizer.js +38 -239
- package/src/js/module/VideoTooltip.js +27 -114
- package/src/js/module/emoji-data.js +496 -0
- package/src/js/settings.js +27 -0
- package/src/styles/autumnnote.scss +49 -0
- package/types/i18n/all.d.ts +14 -0
- package/types/i18n/de.d.ts +4 -0
- package/types/i18n/en.d.ts +4 -0
- package/types/i18n/es.d.ts +4 -0
- package/types/i18n/fr.d.ts +4 -0
- package/types/i18n/ja.d.ts +4 -0
- package/types/i18n/ko.d.ts +4 -0
- package/types/i18n/vi.d.ts +4 -0
- package/types/i18n/zh.d.ts +4 -0
- package/types/index.d.ts +113 -4
|
@@ -1,54 +1,48 @@
|
|
|
1
1
|
// VideoResizer.js - Interactive resize handles for selected videos in the editor
|
|
2
2
|
// Targets .an-video-wrapper divs (containing <iframe> or <video>)
|
|
3
3
|
import { on } from '../core/dom.js';
|
|
4
|
+
import { BaseResizer } from './BaseResizer.js';
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
{
|
|
8
|
-
{ pos: 'ne', cursor: 'ne-resize' },
|
|
9
|
-
{ pos: 'e', cursor: 'e-resize' },
|
|
10
|
-
{ pos: 'se', cursor: 'se-resize' },
|
|
11
|
-
{ pos: 's', cursor: 's-resize' },
|
|
12
|
-
{ pos: 'sw', cursor: 'sw-resize' },
|
|
13
|
-
{ pos: 'w', cursor: 'w-resize' },
|
|
14
|
-
];
|
|
6
|
+
export class VideoResizer extends BaseResizer {
|
|
7
|
+
get _overlayClass() { return 'an-video-resizer'; }
|
|
8
|
+
get _selectedClass() { return 'an-video-selected'; }
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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'));
|
|
25
20
|
}
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
this._container = container;
|
|
22
|
+
/** @param {HTMLElement} wrapper */
|
|
23
|
+
_getStartSize(wrapper) {
|
|
24
|
+
return { w: wrapper.offsetWidth || 560, h: wrapper.offsetHeight || 315 };
|
|
25
|
+
}
|
|
32
26
|
|
|
33
|
-
|
|
27
|
+
_getMinSize() {
|
|
28
|
+
return { minW: 80, minH: 45 };
|
|
29
|
+
}
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
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
|
+
}
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
45
|
-
const wrapper = this._findWrapper(e.target);
|
|
46
|
-
if (wrapper) this._select(wrapper);
|
|
47
|
-
}),
|
|
48
|
-
on(document, 'click', (e) => this._onDocClick(e)),
|
|
49
|
-
on(globalThis, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
50
|
-
on(globalThis, 'resize', onWindowResize),
|
|
51
|
-
on(editable, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
43
|
+
_extraListeners() {
|
|
44
|
+
const editable = this.context.layoutInfo.editable;
|
|
45
|
+
return [
|
|
52
46
|
// D1: Prevent native browser drag of video wrappers. Without this, a user
|
|
53
47
|
// can hold-and-drag to produce a "copy" that lands outside .an-editable,
|
|
54
48
|
// where the .an-video-shield CSS loses its containing context so the
|
|
@@ -58,210 +52,15 @@ export class VideoResizer {
|
|
|
58
52
|
e.preventDefault();
|
|
59
53
|
}
|
|
60
54
|
}),
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return this;
|
|
64
|
-
}
|
|
65
|
-
|
|
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
|
-
this._overlay?.remove();
|
|
79
|
-
this._overlay = null;
|
|
55
|
+
];
|
|
80
56
|
}
|
|
81
57
|
|
|
82
58
|
// ---------------------------------------------------------------------------
|
|
83
|
-
// Public API
|
|
59
|
+
// Public API used by other modules
|
|
84
60
|
// ---------------------------------------------------------------------------
|
|
85
61
|
|
|
86
62
|
/** @returns {HTMLElement|null} */
|
|
87
63
|
getActiveWrapper() {
|
|
88
|
-
return this.
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
updateOverlay() {
|
|
92
|
-
this._updateOverlayPosition();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
deselect() {
|
|
96
|
-
this._deselect();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ---------------------------------------------------------------------------
|
|
100
|
-
// Internal
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Walk up the DOM from `el` to find the nearest .an-video-wrapper,
|
|
105
|
-
* or an iframe/video whose parent is .an-video-wrapper.
|
|
106
|
-
* @param {EventTarget} el
|
|
107
|
-
* @returns {HTMLElement|null}
|
|
108
|
-
*/
|
|
109
|
-
_findWrapper(el) {
|
|
110
|
-
if (!el || !(el instanceof Element)) return null;
|
|
111
|
-
// Direct hit on wrapper
|
|
112
|
-
if (el.classList?.contains('an-video-wrapper')) return /** @type {HTMLElement} */ (el);
|
|
113
|
-
// Child element (iframe, video, or nested)
|
|
114
|
-
const w = el.closest('.an-video-wrapper');
|
|
115
|
-
if (w) return /** @type {HTMLElement} */ (w);
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
_buildOverlay() {
|
|
120
|
-
const overlay = document.createElement('div');
|
|
121
|
-
overlay.className = 'an-video-resizer';
|
|
122
|
-
overlay.style.display = 'none';
|
|
123
|
-
|
|
124
|
-
HANDLE_DEFS.forEach(({ pos }) => {
|
|
125
|
-
const h = document.createElement('div');
|
|
126
|
-
h.className = `an-resize-handle an-resize-${pos}`;
|
|
127
|
-
h.dataset.handle = pos;
|
|
128
|
-
this._disposers.push(
|
|
129
|
-
on(h, 'mousedown', (e) => {
|
|
130
|
-
e.preventDefault();
|
|
131
|
-
e.stopPropagation();
|
|
132
|
-
this._startResize(e, pos);
|
|
133
|
-
}),
|
|
134
|
-
);
|
|
135
|
-
overlay.appendChild(h);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return overlay;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
_onEditorClick(e) {
|
|
142
|
-
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
143
|
-
const wrapper = this._findWrapper(e.target);
|
|
144
|
-
if (wrapper) {
|
|
145
|
-
e.preventDefault();
|
|
146
|
-
this._select(wrapper);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
_onDocClick(e) {
|
|
151
|
-
if (!this._activeWrapper) return;
|
|
152
|
-
if (this._activeWrapper.contains(e.target)) return;
|
|
153
|
-
if (this._overlay?.contains(e.target)) return;
|
|
154
|
-
if (e.target.closest('.an-contextmenu')) return;
|
|
155
|
-
this._deselect();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
_select(wrapper) {
|
|
159
|
-
if (this._activeWrapper && this._activeWrapper !== wrapper) {
|
|
160
|
-
this._activeWrapper.classList.remove('an-video-selected');
|
|
161
|
-
}
|
|
162
|
-
this._activeWrapper = wrapper;
|
|
163
|
-
wrapper.classList.add('an-video-selected');
|
|
164
|
-
this._updateOverlayPosition();
|
|
165
|
-
this._overlay.style.display = 'block';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
_deselect() {
|
|
169
|
-
if (this._activeWrapper) {
|
|
170
|
-
this._activeWrapper.classList.remove('an-video-selected');
|
|
171
|
-
this._activeWrapper = null;
|
|
172
|
-
}
|
|
173
|
-
if (this._overlay) this._overlay.style.display = 'none';
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
_updateOverlayPosition() {
|
|
177
|
-
if (this._positionRaf) cancelAnimationFrame(this._positionRaf);
|
|
178
|
-
this._positionRaf = requestAnimationFrame(() => {
|
|
179
|
-
this._positionRaf = null;
|
|
180
|
-
this._updateOverlayPositionNow();
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
_updateOverlayPositionNow() {
|
|
185
|
-
if (!this._activeWrapper || !this._overlay) return;
|
|
186
|
-
const offsetParent = this._overlay.offsetParent || this._container;
|
|
187
|
-
const containerRect = offsetParent.getBoundingClientRect();
|
|
188
|
-
const rect = this._activeWrapper.getBoundingClientRect();
|
|
189
|
-
const left = rect.left - containerRect.left + offsetParent.scrollLeft;
|
|
190
|
-
const top = rect.top - containerRect.top + offsetParent.scrollTop;
|
|
191
|
-
this._overlay.style.left = `${left}px`;
|
|
192
|
-
this._overlay.style.top = `${top}px`;
|
|
193
|
-
this._overlay.style.width = `${rect.width}px`;
|
|
194
|
-
this._overlay.style.height = `${rect.height}px`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
_startResize(e, pos) {
|
|
198
|
-
const wrapper = this._activeWrapper;
|
|
199
|
-
if (!wrapper) return;
|
|
200
|
-
|
|
201
|
-
const embed = /** @type {HTMLElement|null} */ (wrapper.querySelector('iframe, video'));
|
|
202
|
-
const startX = e.clientX;
|
|
203
|
-
const startY = e.clientY;
|
|
204
|
-
const startW = wrapper.offsetWidth || 560;
|
|
205
|
-
const startH = wrapper.offsetHeight || 315;
|
|
206
|
-
const aspectRatio = startW / (startH || 1);
|
|
207
|
-
const isCorner = pos.length === 2;
|
|
208
|
-
|
|
209
|
-
const editable = this.context.layoutInfo.editable;
|
|
210
|
-
let _raf = null; // rAF handle — at most one write per paint frame
|
|
211
|
-
|
|
212
|
-
const onMove = (me) => {
|
|
213
|
-
if (_raf !== null) return;
|
|
214
|
-
const clientX = me.clientX;
|
|
215
|
-
const clientY = me.clientY;
|
|
216
|
-
_raf = requestAnimationFrame(() => {
|
|
217
|
-
_raf = null;
|
|
218
|
-
const dx = clientX - startX;
|
|
219
|
-
const dy = clientY - startY;
|
|
220
|
-
const maxW = editable.clientWidth || Infinity;
|
|
221
|
-
let newW = startW;
|
|
222
|
-
let newH = startH;
|
|
223
|
-
|
|
224
|
-
if (pos.includes('e')) newW = Math.max(80, startW + dx);
|
|
225
|
-
if (pos.includes('w')) newW = Math.max(80, startW - dx);
|
|
226
|
-
if (pos.includes('s')) newH = Math.max(45, startH + dy);
|
|
227
|
-
if (pos.includes('n')) newH = Math.max(45, startH - dy);
|
|
228
|
-
|
|
229
|
-
newW = Math.min(newW, maxW);
|
|
230
|
-
|
|
231
|
-
if (isCorner) {
|
|
232
|
-
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
233
|
-
newH = Math.max(45, Math.round(newW / aspectRatio));
|
|
234
|
-
} else {
|
|
235
|
-
newW = Math.min(Math.max(80, Math.round(newH * aspectRatio)), maxW);
|
|
236
|
-
newH = Math.max(45, Math.round(newW / aspectRatio));
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Resize wrapper and inner embed via CSS only — attribute writes are redundant
|
|
241
|
-
wrapper.style.width = `${newW}px`;
|
|
242
|
-
wrapper.style.height = `${newH}px`;
|
|
243
|
-
if (embed) {
|
|
244
|
-
embed.style.width = `${newW}px`;
|
|
245
|
-
embed.style.height = `${newH}px`;
|
|
246
|
-
}
|
|
247
|
-
this._updateOverlayPosition();
|
|
248
|
-
});
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const onUp = () => {
|
|
252
|
-
if (_raf !== null) { cancelAnimationFrame(_raf); _raf = null; }
|
|
253
|
-
document.removeEventListener('mousemove', onMove);
|
|
254
|
-
document.removeEventListener('mouseup', onUp);
|
|
255
|
-
this._dragDisposers = null;
|
|
256
|
-
this.context.invoke('editor.afterCommand');
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
document.addEventListener('mousemove', onMove);
|
|
260
|
-
document.addEventListener('mouseup', onUp);
|
|
261
|
-
// Track these so destroy() can clean them up if called during an active drag
|
|
262
|
-
this._dragDisposers = [
|
|
263
|
-
() => document.removeEventListener('mousemove', onMove),
|
|
264
|
-
() => document.removeEventListener('mouseup', onUp),
|
|
265
|
-
];
|
|
64
|
+
return this._active;
|
|
266
65
|
}
|
|
267
66
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Displays a horizontal action bar below (or above) the selected video,
|
|
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>`,
|
|
@@ -13,22 +14,7 @@ const ICONS = {
|
|
|
13
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>`,
|
|
14
15
|
};
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
const HIDE_DELAY = 180;
|
|
18
|
-
|
|
19
|
-
export class VideoTooltip {
|
|
20
|
-
/** @param {import('../Context.js').Context} context */
|
|
21
|
-
constructor(context) {
|
|
22
|
-
this.context = context;
|
|
23
|
-
this._el = null;
|
|
24
|
-
this._activeWrapper = null;
|
|
25
|
-
this._showTimer = null;
|
|
26
|
-
this._hideTimer = null;
|
|
27
|
-
this._disposers = [];
|
|
28
|
-
this._previewMode = false;
|
|
29
|
-
this._previewClickOff = null;
|
|
30
|
-
}
|
|
31
|
-
|
|
17
|
+
export class VideoTooltip extends BaseMediaTooltip {
|
|
32
18
|
initialize() {
|
|
33
19
|
this._el = this._buildTooltip();
|
|
34
20
|
document.body.appendChild(this._el);
|
|
@@ -54,8 +40,8 @@ export class VideoTooltip {
|
|
|
54
40
|
on(document, 'click', (e) => {
|
|
55
41
|
const target = /** @type {Node} */ (e.target);
|
|
56
42
|
if (
|
|
57
|
-
this.
|
|
58
|
-
!this.
|
|
43
|
+
this._active &&
|
|
44
|
+
!this._active.contains(target) &&
|
|
59
45
|
!this._el.contains(target)
|
|
60
46
|
) {
|
|
61
47
|
this._hide();
|
|
@@ -78,6 +64,17 @@ export class VideoTooltip {
|
|
|
78
64
|
this._el = null;
|
|
79
65
|
}
|
|
80
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
|
+
|
|
81
78
|
// ---------------------------------------------------------------------------
|
|
82
79
|
// Build
|
|
83
80
|
// ---------------------------------------------------------------------------
|
|
@@ -134,96 +131,12 @@ export class VideoTooltip {
|
|
|
134
131
|
return el;
|
|
135
132
|
}
|
|
136
133
|
|
|
137
|
-
/**
|
|
138
|
-
* @param {string} icon
|
|
139
|
-
* @param {string} title
|
|
140
|
-
* @param {Function} handler
|
|
141
|
-
* @param {boolean} [isDanger]
|
|
142
|
-
*/
|
|
143
|
-
_makeBtn(icon, title, handler, isDanger = false) {
|
|
144
|
-
const btn = createElement('button', {
|
|
145
|
-
type: 'button',
|
|
146
|
-
class: isDanger ? 'an-link-tooltip-btn an-link-tooltip-btn--danger' : 'an-link-tooltip-btn',
|
|
147
|
-
title,
|
|
148
|
-
});
|
|
149
|
-
btn.innerHTML = icon;
|
|
150
|
-
this._disposers.push(on(btn, 'click', (e) => {
|
|
151
|
-
e.preventDefault();
|
|
152
|
-
e.stopPropagation();
|
|
153
|
-
handler();
|
|
154
|
-
}));
|
|
155
|
-
return btn;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ---------------------------------------------------------------------------
|
|
159
|
-
// Show / Hide
|
|
160
|
-
// ---------------------------------------------------------------------------
|
|
161
|
-
|
|
162
|
-
_scheduleShow(wrapper) {
|
|
163
|
-
if (this._activeWrapper === wrapper && this._el.style.display !== 'none') return;
|
|
164
|
-
clearTimeout(this._hideTimer);
|
|
165
|
-
this._hideTimer = null;
|
|
166
|
-
clearTimeout(this._showTimer);
|
|
167
|
-
this._showTimer = setTimeout(() => {
|
|
168
|
-
this._activeWrapper = wrapper;
|
|
169
|
-
this._show(wrapper);
|
|
170
|
-
}, SHOW_DELAY);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
_scheduleHide() {
|
|
174
|
-
// Keep tooltip alive while preview mode is active
|
|
175
|
-
if (this._previewMode) return;
|
|
176
|
-
clearTimeout(this._showTimer);
|
|
177
|
-
this._showTimer = null;
|
|
178
|
-
if (this._hideTimer) return;
|
|
179
|
-
this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
_show(_wrapper) {
|
|
183
|
-
this._el.style.display = 'flex';
|
|
184
|
-
// Defer: offsetWidth on a newly-visible element forces synchronous layout
|
|
185
|
-
requestAnimationFrame(() => {
|
|
186
|
-
if (this._activeWrapper) this._positionNear(this._activeWrapper);
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
_hide() {
|
|
191
|
-
if (this._previewMode) this._exitPreview();
|
|
192
|
-
this._el.style.display = 'none';
|
|
193
|
-
this._activeWrapper = null;
|
|
194
|
-
this._clearTimers();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
_clearTimers() {
|
|
198
|
-
clearTimeout(this._showTimer);
|
|
199
|
-
clearTimeout(this._hideTimer);
|
|
200
|
-
this._showTimer = null;
|
|
201
|
-
this._hideTimer = null;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
_positionNear(wrapper) {
|
|
205
|
-
const rect = wrapper.getBoundingClientRect();
|
|
206
|
-
const tipW = this._el.offsetWidth || 220;
|
|
207
|
-
const tipH = this._el.offsetHeight || 32;
|
|
208
|
-
const margin = 6;
|
|
209
|
-
|
|
210
|
-
let top = rect.bottom + margin;
|
|
211
|
-
let left = rect.left + (rect.width - tipW) / 2;
|
|
212
|
-
|
|
213
|
-
if (top + tipH > globalThis.innerHeight - margin) top = rect.top - tipH - margin;
|
|
214
|
-
if (left + tipW > globalThis.innerWidth - margin) left = globalThis.innerWidth - tipW - margin;
|
|
215
|
-
if (left < margin) left = margin;
|
|
216
|
-
|
|
217
|
-
this._el.style.top = `${top}px`;
|
|
218
|
-
this._el.style.left = `${left}px`;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
134
|
// ---------------------------------------------------------------------------
|
|
222
135
|
// Actions
|
|
223
136
|
// ---------------------------------------------------------------------------
|
|
224
137
|
|
|
225
138
|
_setFloat(value) {
|
|
226
|
-
const wrapper = this.
|
|
139
|
+
const wrapper = this._active;
|
|
227
140
|
if (!wrapper) return;
|
|
228
141
|
wrapper.style.float = value;
|
|
229
142
|
wrapper.style.display = value ? 'inline-block' : '';
|
|
@@ -231,11 +144,11 @@ export class VideoTooltip {
|
|
|
231
144
|
wrapper.style.marginRight = value === 'left' ? '12px' : '';
|
|
232
145
|
this.context.invoke('editor.afterCommand');
|
|
233
146
|
this.context.invoke('videoResizer.updateOverlay');
|
|
234
|
-
requestAnimationFrame(() => { if (this.
|
|
147
|
+
requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
|
|
235
148
|
}
|
|
236
149
|
|
|
237
150
|
_setCenter() {
|
|
238
|
-
const wrapper = this.
|
|
151
|
+
const wrapper = this._active;
|
|
239
152
|
if (!wrapper) return;
|
|
240
153
|
wrapper.style.float = '';
|
|
241
154
|
wrapper.style.display = 'block';
|
|
@@ -243,13 +156,13 @@ export class VideoTooltip {
|
|
|
243
156
|
wrapper.style.marginRight = 'auto';
|
|
244
157
|
this.context.invoke('editor.afterCommand');
|
|
245
158
|
this.context.invoke('videoResizer.updateOverlay');
|
|
246
|
-
requestAnimationFrame(() => { if (this.
|
|
159
|
+
requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
|
|
247
160
|
}
|
|
248
161
|
|
|
249
162
|
_resetSize() {
|
|
250
|
-
const wrapper = this.
|
|
163
|
+
const wrapper = this._active;
|
|
251
164
|
if (!wrapper) return;
|
|
252
|
-
const embed = wrapper.querySelector('iframe, video');
|
|
165
|
+
const embed = /** @type {HTMLElement|null} */ (wrapper.querySelector('iframe, video'));
|
|
253
166
|
wrapper.style.width = '';
|
|
254
167
|
wrapper.style.height = '';
|
|
255
168
|
if (embed) {
|
|
@@ -260,11 +173,11 @@ export class VideoTooltip {
|
|
|
260
173
|
}
|
|
261
174
|
this.context.invoke('editor.afterCommand');
|
|
262
175
|
this.context.invoke('videoResizer.updateOverlay');
|
|
263
|
-
requestAnimationFrame(() => { if (this.
|
|
176
|
+
requestAnimationFrame(() => { if (this._active) this._positionNear(this._active); });
|
|
264
177
|
}
|
|
265
178
|
|
|
266
179
|
_delete() {
|
|
267
|
-
const wrapper = this.
|
|
180
|
+
const wrapper = this._active;
|
|
268
181
|
if (!wrapper) return;
|
|
269
182
|
this._hide();
|
|
270
183
|
this.context.invoke('videoResizer.deselect');
|
|
@@ -285,12 +198,12 @@ export class VideoTooltip {
|
|
|
285
198
|
}
|
|
286
199
|
|
|
287
200
|
_enterPreview() {
|
|
288
|
-
const wrapper = this.
|
|
201
|
+
const wrapper = this._active;
|
|
289
202
|
if (!wrapper) return;
|
|
290
203
|
this._previewMode = true;
|
|
291
204
|
|
|
292
205
|
// Hide the shield so the iframe / video receives pointer events directly
|
|
293
|
-
const shield = wrapper.querySelector('.an-video-shield');
|
|
206
|
+
const shield = /** @type {HTMLElement|null} */ (wrapper.querySelector('.an-video-shield'));
|
|
294
207
|
if (shield) shield.style.display = 'none';
|
|
295
208
|
|
|
296
209
|
// Hide the resize overlay — it would block interaction with the embed
|
|
@@ -316,10 +229,10 @@ export class VideoTooltip {
|
|
|
316
229
|
_exitPreview() {
|
|
317
230
|
this._previewMode = false;
|
|
318
231
|
|
|
319
|
-
const wrapper = this.
|
|
232
|
+
const wrapper = this._active;
|
|
320
233
|
if (wrapper) {
|
|
321
234
|
// Restore the shield
|
|
322
|
-
const shield = wrapper.querySelector('.an-video-shield');
|
|
235
|
+
const shield = /** @type {HTMLElement|null} */ (wrapper.querySelector('.an-video-shield'));
|
|
323
236
|
if (shield) shield.style.display = '';
|
|
324
237
|
}
|
|
325
238
|
|