autumnnote 1.0.1 → 1.0.4
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 +6 -2
- package/dist/autumnnote.css +1 -1
- package/dist/autumnnote.es.js +2254 -1828
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +11 -11
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +2 -2
- package/src/js/core/sanitise.js +43 -0
- package/src/js/editing/History.js +10 -2
- package/src/js/editing/Style.js +65 -4
- package/src/js/editing/Typing.js +243 -37
- package/src/js/index.umd.js +12 -12
- package/src/js/module/Buttons.js +15 -5
- package/src/js/module/CodeTooltip.js +18 -7
- package/src/js/module/Codeview.js +6 -1
- package/src/js/module/ContextMenu.js +13 -6
- package/src/js/module/Editor.js +70 -2
- package/src/js/module/IconDialog.js +27 -13
- package/src/js/module/ImageResizer.js +63 -29
- package/src/js/module/ImageTooltip.js +77 -16
- package/src/js/module/Placeholder.js +2 -1
- package/src/js/module/Statusbar.js +16 -2
- package/src/js/module/TableTooltip.js +197 -76
- package/src/js/module/Toolbar.js +103 -17
- package/src/js/module/VideoDialog.js +3 -3
- package/src/js/module/VideoResizer.js +67 -36
- package/src/js/module/VideoTooltip.js +82 -6
- package/src/js/renderer.js +5 -0
- package/src/js/settings.js +4 -0
- package/src/styles/autumnnote.scss +61 -31
- package/types/index.d.ts +4 -4
|
@@ -21,14 +21,23 @@ export class VideoResizer {
|
|
|
21
21
|
this._activeWrapper = null;
|
|
22
22
|
this._overlay = null;
|
|
23
23
|
this._disposers = [];
|
|
24
|
+
this._positionRaf = null;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
initialize() {
|
|
27
28
|
this._overlay = this._buildOverlay();
|
|
28
|
-
|
|
29
|
+
const container = this.context.layoutInfo.editable.closest('.an-container') || document.body;
|
|
30
|
+
container.appendChild(this._overlay);
|
|
31
|
+
this._container = container;
|
|
29
32
|
|
|
30
33
|
const editable = this.context.layoutInfo.editable;
|
|
31
34
|
|
|
35
|
+
let _resizeDebounce = null;
|
|
36
|
+
const onWindowResize = () => {
|
|
37
|
+
clearTimeout(_resizeDebounce);
|
|
38
|
+
_resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
|
|
39
|
+
};
|
|
40
|
+
|
|
32
41
|
this._disposers.push(
|
|
33
42
|
on(editable, 'click', (e) => this._onEditorClick(e)),
|
|
34
43
|
on(editable, 'contextmenu', (e) => {
|
|
@@ -37,7 +46,7 @@ export class VideoResizer {
|
|
|
37
46
|
}),
|
|
38
47
|
on(document, 'click', (e) => this._onDocClick(e)),
|
|
39
48
|
on(window, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
40
|
-
on(window, 'resize',
|
|
49
|
+
on(window, 'resize', onWindowResize),
|
|
41
50
|
on(editable, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
42
51
|
);
|
|
43
52
|
|
|
@@ -51,6 +60,10 @@ export class VideoResizer {
|
|
|
51
60
|
this._dragDisposers.forEach((d) => d());
|
|
52
61
|
this._dragDisposers = null;
|
|
53
62
|
}
|
|
63
|
+
if (this._positionRaf) {
|
|
64
|
+
cancelAnimationFrame(this._positionRaf);
|
|
65
|
+
this._positionRaf = null;
|
|
66
|
+
}
|
|
54
67
|
this._deselect();
|
|
55
68
|
if (this._overlay && this._overlay.parentNode) {
|
|
56
69
|
this._overlay.parentNode.removeChild(this._overlay);
|
|
@@ -152,10 +165,22 @@ export class VideoResizer {
|
|
|
152
165
|
}
|
|
153
166
|
|
|
154
167
|
_updateOverlayPosition() {
|
|
168
|
+
if (this._positionRaf) cancelAnimationFrame(this._positionRaf);
|
|
169
|
+
this._positionRaf = requestAnimationFrame(() => {
|
|
170
|
+
this._positionRaf = null;
|
|
171
|
+
this._updateOverlayPositionNow();
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
_updateOverlayPositionNow() {
|
|
155
176
|
if (!this._activeWrapper || !this._overlay) return;
|
|
177
|
+
const offsetParent = this._overlay.offsetParent || this._container;
|
|
178
|
+
const containerRect = offsetParent.getBoundingClientRect();
|
|
156
179
|
const rect = this._activeWrapper.getBoundingClientRect();
|
|
157
|
-
|
|
158
|
-
|
|
180
|
+
const left = rect.left - containerRect.left + offsetParent.scrollLeft;
|
|
181
|
+
const top = rect.top - containerRect.top + offsetParent.scrollTop;
|
|
182
|
+
this._overlay.style.left = `${left}px`;
|
|
183
|
+
this._overlay.style.top = `${top}px`;
|
|
159
184
|
this._overlay.style.width = `${rect.width}px`;
|
|
160
185
|
this._overlay.style.height = `${rect.height}px`;
|
|
161
186
|
}
|
|
@@ -173,43 +198,49 @@ export class VideoResizer {
|
|
|
173
198
|
const isCorner = pos.length === 2;
|
|
174
199
|
|
|
175
200
|
const editable = this.context.layoutInfo.editable;
|
|
201
|
+
let _raf = null; // rAF handle — at most one write per paint frame
|
|
202
|
+
|
|
176
203
|
const onMove = (me) => {
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
204
|
+
if (_raf !== null) return;
|
|
205
|
+
const clientX = me.clientX;
|
|
206
|
+
const clientY = me.clientY;
|
|
207
|
+
_raf = requestAnimationFrame(() => {
|
|
208
|
+
_raf = null;
|
|
209
|
+
const dx = clientX - startX;
|
|
210
|
+
const dy = clientY - startY;
|
|
211
|
+
const maxW = editable.clientWidth || Infinity;
|
|
212
|
+
let newW = startW;
|
|
213
|
+
let newH = startH;
|
|
214
|
+
|
|
215
|
+
if (pos.includes('e')) newW = Math.max(80, startW + dx);
|
|
216
|
+
if (pos.includes('w')) newW = Math.max(80, startW - dx);
|
|
217
|
+
if (pos.includes('s')) newH = Math.max(45, startH + dy);
|
|
218
|
+
if (pos.includes('n')) newH = Math.max(45, startH - dy);
|
|
219
|
+
|
|
220
|
+
newW = Math.min(newW, maxW);
|
|
221
|
+
|
|
222
|
+
if (isCorner) {
|
|
223
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
224
|
+
newH = Math.max(45, Math.round(newW / aspectRatio));
|
|
225
|
+
} else {
|
|
226
|
+
newW = Math.min(Math.max(80, Math.round(newH * aspectRatio)), maxW);
|
|
227
|
+
newH = Math.max(45, Math.round(newW / aspectRatio));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Resize wrapper and inner embed via CSS only — attribute writes are redundant
|
|
232
|
+
wrapper.style.width = `${newW}px`;
|
|
233
|
+
wrapper.style.height = `${newH}px`;
|
|
234
|
+
if (embed) {
|
|
235
|
+
embed.style.width = `${newW}px`;
|
|
236
|
+
embed.style.height = `${newH}px`;
|
|
197
237
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// Resize both the wrapper and the inner embed element
|
|
201
|
-
wrapper.style.width = `${newW}px`;
|
|
202
|
-
wrapper.style.height = `${newH}px`;
|
|
203
|
-
if (embed) {
|
|
204
|
-
embed.width = newW;
|
|
205
|
-
embed.height = newH;
|
|
206
|
-
embed.style.width = `${newW}px`;
|
|
207
|
-
embed.style.height = `${newH}px`;
|
|
208
|
-
}
|
|
209
|
-
this._updateOverlayPosition();
|
|
238
|
+
this._updateOverlayPosition();
|
|
239
|
+
});
|
|
210
240
|
};
|
|
211
241
|
|
|
212
242
|
const onUp = () => {
|
|
243
|
+
if (_raf !== null) { cancelAnimationFrame(_raf); _raf = null; }
|
|
213
244
|
document.removeEventListener('mousemove', onMove);
|
|
214
245
|
document.removeEventListener('mouseup', onUp);
|
|
215
246
|
this._dragDisposers = null;
|
|
@@ -10,6 +10,7 @@ const ICONS = {
|
|
|
10
10
|
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>`,
|
|
11
11
|
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>`,
|
|
12
12
|
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>`,
|
|
13
|
+
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>`,
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
const SHOW_DELAY = 100;
|
|
@@ -24,6 +25,8 @@ export class VideoTooltip {
|
|
|
24
25
|
this._showTimer = null;
|
|
25
26
|
this._hideTimer = null;
|
|
26
27
|
this._disposers = [];
|
|
28
|
+
this._previewMode = false;
|
|
29
|
+
this._previewClickOff = null;
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
initialize() {
|
|
@@ -39,13 +42,13 @@ export class VideoTooltip {
|
|
|
39
42
|
if (wrapper && editable.contains(wrapper)) {
|
|
40
43
|
this._scheduleShow(wrapper);
|
|
41
44
|
}
|
|
42
|
-
}),
|
|
45
|
+
}, { passive: true }),
|
|
43
46
|
on(editable, 'mouseout', (e) => {
|
|
44
47
|
const to = e.relatedTarget;
|
|
45
48
|
if (!to || (!editable.contains(to) && !this._el.contains(to))) {
|
|
46
49
|
this._scheduleHide();
|
|
47
50
|
}
|
|
48
|
-
}),
|
|
51
|
+
}, { passive: true }),
|
|
49
52
|
on(document, 'click', (e) => {
|
|
50
53
|
if (
|
|
51
54
|
this._activeWrapper &&
|
|
@@ -61,6 +64,7 @@ export class VideoTooltip {
|
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
destroy() {
|
|
67
|
+
if (this._previewMode) this._exitPreview();
|
|
64
68
|
this._clearTimers();
|
|
65
69
|
this._disposers.forEach((d) => d());
|
|
66
70
|
this._disposers = [];
|
|
@@ -105,6 +109,11 @@ export class VideoTooltip {
|
|
|
105
109
|
|
|
106
110
|
el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
|
|
107
111
|
|
|
112
|
+
this._previewBtn = this._makeBtn(ICONS.preview, 'Preview Video', () => this._togglePreview());
|
|
113
|
+
el.appendChild(this._previewBtn);
|
|
114
|
+
|
|
115
|
+
el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
|
|
116
|
+
|
|
108
117
|
this._deleteBtn = this._makeBtn(ICONS.deleteVideo, 'Delete Video', () => this._delete(), true);
|
|
109
118
|
|
|
110
119
|
el.appendChild(this._deleteBtn);
|
|
@@ -155,6 +164,8 @@ export class VideoTooltip {
|
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
_scheduleHide() {
|
|
167
|
+
// Keep tooltip alive while preview mode is active
|
|
168
|
+
if (this._previewMode) return;
|
|
158
169
|
clearTimeout(this._showTimer);
|
|
159
170
|
this._showTimer = null;
|
|
160
171
|
if (this._hideTimer) return;
|
|
@@ -163,10 +174,14 @@ export class VideoTooltip {
|
|
|
163
174
|
|
|
164
175
|
_show(wrapper) {
|
|
165
176
|
this._el.style.display = 'flex';
|
|
166
|
-
|
|
177
|
+
// Defer: offsetWidth on a newly-visible element forces synchronous layout
|
|
178
|
+
requestAnimationFrame(() => {
|
|
179
|
+
if (this._activeWrapper) this._positionNear(this._activeWrapper);
|
|
180
|
+
});
|
|
167
181
|
}
|
|
168
182
|
|
|
169
183
|
_hide() {
|
|
184
|
+
if (this._previewMode) this._exitPreview();
|
|
170
185
|
this._el.style.display = 'none';
|
|
171
186
|
this._activeWrapper = null;
|
|
172
187
|
this._clearTimers();
|
|
@@ -209,7 +224,7 @@ export class VideoTooltip {
|
|
|
209
224
|
wrapper.style.marginRight = value === 'left' ? '12px' : '';
|
|
210
225
|
this.context.invoke('editor.afterCommand');
|
|
211
226
|
this.context.invoke('videoResizer.updateOverlay');
|
|
212
|
-
this._positionNear(
|
|
227
|
+
requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
|
|
213
228
|
}
|
|
214
229
|
|
|
215
230
|
_setCenter() {
|
|
@@ -221,7 +236,7 @@ export class VideoTooltip {
|
|
|
221
236
|
wrapper.style.marginRight = 'auto';
|
|
222
237
|
this.context.invoke('editor.afterCommand');
|
|
223
238
|
this.context.invoke('videoResizer.updateOverlay');
|
|
224
|
-
this._positionNear(
|
|
239
|
+
requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
|
|
225
240
|
}
|
|
226
241
|
|
|
227
242
|
_resetSize() {
|
|
@@ -238,7 +253,7 @@ export class VideoTooltip {
|
|
|
238
253
|
}
|
|
239
254
|
this.context.invoke('editor.afterCommand');
|
|
240
255
|
this.context.invoke('videoResizer.updateOverlay');
|
|
241
|
-
this._positionNear(
|
|
256
|
+
requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
|
|
242
257
|
}
|
|
243
258
|
|
|
244
259
|
_delete() {
|
|
@@ -249,4 +264,65 @@ export class VideoTooltip {
|
|
|
249
264
|
if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
|
|
250
265
|
this.context.invoke('editor.afterCommand');
|
|
251
266
|
}
|
|
267
|
+
|
|
268
|
+
// ---------------------------------------------------------------------------
|
|
269
|
+
// Preview mode — temporarily disables the shield so the video is interactive
|
|
270
|
+
// ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
_togglePreview() {
|
|
273
|
+
if (this._previewMode) {
|
|
274
|
+
this._exitPreview();
|
|
275
|
+
} else {
|
|
276
|
+
this._enterPreview();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
_enterPreview() {
|
|
281
|
+
const wrapper = this._activeWrapper;
|
|
282
|
+
if (!wrapper) return;
|
|
283
|
+
this._previewMode = true;
|
|
284
|
+
|
|
285
|
+
// Hide the shield so the iframe / video receives pointer events directly
|
|
286
|
+
const shield = wrapper.querySelector('.an-video-shield');
|
|
287
|
+
if (shield) shield.style.display = 'none';
|
|
288
|
+
|
|
289
|
+
// Hide the resize overlay — it would block interaction with the embed
|
|
290
|
+
this.context.invoke('videoResizer.deselect');
|
|
291
|
+
|
|
292
|
+
// Visual feedback: button turns primary-coloured
|
|
293
|
+
this._previewBtn.classList.add('an-link-tooltip-btn--copied');
|
|
294
|
+
this._previewBtn.title = 'Exit Preview';
|
|
295
|
+
|
|
296
|
+
// Exit preview on mousedown outside the wrapper.
|
|
297
|
+
// Using 'mousedown' (not 'click') so clicks inside an iframe
|
|
298
|
+
// — which never bubble to the outer document — don't accidentally
|
|
299
|
+
// leave preview mode stuck. For <video> elements, mousedown on the
|
|
300
|
+
// video is inside the wrapper so contains() returns true → no exit.
|
|
301
|
+
this._previewClickOff = (e) => {
|
|
302
|
+
if (!wrapper.contains(e.target) && !this._el.contains(e.target)) {
|
|
303
|
+
this._exitPreview();
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
document.addEventListener('mousedown', this._previewClickOff, true);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
_exitPreview() {
|
|
310
|
+
this._previewMode = false;
|
|
311
|
+
|
|
312
|
+
const wrapper = this._activeWrapper;
|
|
313
|
+
if (wrapper) {
|
|
314
|
+
// Restore the shield
|
|
315
|
+
const shield = wrapper.querySelector('.an-video-shield');
|
|
316
|
+
if (shield) shield.style.display = '';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Reset button appearance
|
|
320
|
+
this._previewBtn.classList.remove('an-link-tooltip-btn--copied');
|
|
321
|
+
this._previewBtn.title = 'Preview Video';
|
|
322
|
+
|
|
323
|
+
if (this._previewClickOff) {
|
|
324
|
+
document.removeEventListener('mousedown', this._previewClickOff, true);
|
|
325
|
+
this._previewClickOff = null;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
252
328
|
}
|
package/src/js/renderer.js
CHANGED
|
@@ -99,6 +99,11 @@ export function renderLayout(targetEl, options) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Custom focus ring colour
|
|
103
|
+
if (options.focusColor) {
|
|
104
|
+
container.style.setProperty('--an-focus-color', options.focusColor);
|
|
105
|
+
}
|
|
106
|
+
|
|
102
107
|
// Hide the original element; keep it in DOM for form submission
|
|
103
108
|
targetEl.style.display = 'none';
|
|
104
109
|
targetEl.insertAdjacentElement('afterend', container);
|
package/src/js/settings.js
CHANGED
|
@@ -45,6 +45,7 @@ import { defaultToolbar } from './module/Buttons.js';
|
|
|
45
45
|
* @property {Function} [onDestroy] - Callback fired when the editor is destroyed: (context) => void
|
|
46
46
|
* @property {Function} [onCharLimitReached] - Callback fired when the character limit is hit: (context) => void
|
|
47
47
|
* @property {Function} [onWordLimitReached] - Callback fired when the word limit is hit: (context) => void
|
|
48
|
+
* @property {string} [focusColor] - Custom focus ring colour, e.g. '#f97316'. Overrides the default blue.
|
|
48
49
|
*/
|
|
49
50
|
|
|
50
51
|
/** @type {AsnOptions} */
|
|
@@ -131,4 +132,7 @@ export const defaultOptions = {
|
|
|
131
132
|
onCharLimitReached: null,
|
|
132
133
|
// Callback fired when the word limit is reached: function(context)
|
|
133
134
|
onWordLimitReached: null,
|
|
135
|
+
// Custom focus ring colour — overrides the default blue when set.
|
|
136
|
+
// Accepts any valid CSS colour string, e.g. '#f97316', 'hsl(25,90%,55%)'.
|
|
137
|
+
focusColor: null,
|
|
134
138
|
};
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
.an-container {
|
|
13
13
|
display: flex;
|
|
14
14
|
flex-direction: column;
|
|
15
|
+
position: relative;
|
|
15
16
|
border: 1px solid $an-border;
|
|
16
17
|
border-radius: $an-radius;
|
|
17
18
|
font-family: $an-font-family;
|
|
18
19
|
font-size: $an-font-size;
|
|
19
20
|
box-sizing: border-box;
|
|
20
21
|
background: $an-bg;
|
|
21
|
-
overflow: hidden;
|
|
22
22
|
// Prevent the container from participating in ancestor layout recalculations.
|
|
23
23
|
// This limits reflow thrashing when editing large documents.
|
|
24
24
|
contain: layout;
|
|
@@ -30,14 +30,27 @@
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
&.an-focused {
|
|
33
|
-
border-color: $an-primary;
|
|
34
|
-
|
|
33
|
+
border-color: var(--an-focus-color, #{$an-primary});
|
|
34
|
+
// color-mix() blends the focus colour with transparent for the glow ring.
|
|
35
|
+
// Falls back gracefully to the default blue glow in older browsers.
|
|
36
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--an-focus-color, #{$an-primary}) 18%, transparent);
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
&.an-disabled {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
// Hide toolbar only — read-only view has no editing controls.
|
|
41
|
+
.an-toolbar {
|
|
42
|
+
display: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Allow the user to select and copy text but block actual editing.
|
|
46
|
+
// Do NOT set pointer-events:none or user-select:none on the container —
|
|
47
|
+
// that would prevent text selection inside the editable area.
|
|
48
|
+
.an-editable {
|
|
49
|
+
cursor: text;
|
|
50
|
+
user-select: text;
|
|
51
|
+
// Subtle visual cue that the area is read-only.
|
|
52
|
+
background: #fafafa;
|
|
53
|
+
}
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
&.an-fullscreen {
|
|
@@ -234,9 +247,9 @@
|
|
|
234
247
|
|
|
235
248
|
// Swatch popup panel
|
|
236
249
|
.an-color-popup {
|
|
237
|
-
position:
|
|
238
|
-
top
|
|
239
|
-
|
|
250
|
+
position: fixed;
|
|
251
|
+
// top / left are set dynamically by JS (getBoundingClientRect) so the popup
|
|
252
|
+
// escapes overflow:hidden / overflow:auto ancestors (toolbar scroll mode, etc.)
|
|
240
253
|
z-index: 1100;
|
|
241
254
|
background: $an-bg;
|
|
242
255
|
border: 1px solid $an-border;
|
|
@@ -377,19 +390,25 @@
|
|
|
377
390
|
.an-editable {
|
|
378
391
|
flex: 1;
|
|
379
392
|
padding: $an-editable-pad;
|
|
380
|
-
min-height:
|
|
393
|
+
min-height: 0;
|
|
381
394
|
outline: none;
|
|
382
395
|
line-height: $an-line-height;
|
|
383
396
|
color: $an-text;
|
|
384
397
|
overflow-y: auto;
|
|
385
398
|
word-break: break-word;
|
|
386
399
|
|
|
387
|
-
// Placeholder
|
|
388
|
-
|
|
400
|
+
// Placeholder — only shown when not focused.
|
|
401
|
+
// float:left is the standard contenteditable placeholder technique (used by
|
|
402
|
+
// Quill.js): the pseudo-element is out of normal flow so it never shifts the
|
|
403
|
+
// cursor, yet it renders visually at the start of the empty editing area.
|
|
404
|
+
// Avoid position:absolute here — without position:relative on the parent it
|
|
405
|
+
// would be positioned relative to .an-container (contain:layout) and overlap
|
|
406
|
+
// the toolbar.
|
|
407
|
+
&.an-placeholder:not(:focus)::before {
|
|
389
408
|
content: attr(data-placeholder);
|
|
390
409
|
color: $an-muted;
|
|
391
410
|
pointer-events: none;
|
|
392
|
-
|
|
411
|
+
float: left;
|
|
393
412
|
}
|
|
394
413
|
|
|
395
414
|
// Content styles
|
|
@@ -435,7 +454,7 @@
|
|
|
435
454
|
|
|
436
455
|
.an-video-wrapper {
|
|
437
456
|
position: relative;
|
|
438
|
-
display:
|
|
457
|
+
display: block;
|
|
439
458
|
max-width: 100%;
|
|
440
459
|
margin: 4px 0;
|
|
441
460
|
|
|
@@ -667,6 +686,7 @@
|
|
|
667
686
|
display: flex;
|
|
668
687
|
align-items: center;
|
|
669
688
|
justify-content: space-between;
|
|
689
|
+
flex-shrink: 0;
|
|
670
690
|
height: $an-statusbar-h;
|
|
671
691
|
padding: 0 8px;
|
|
672
692
|
background: $an-statusbar-bg;
|
|
@@ -919,7 +939,7 @@
|
|
|
919
939
|
// ---------------------------------------------------------------------------
|
|
920
940
|
|
|
921
941
|
.an-image-resizer {
|
|
922
|
-
position:
|
|
942
|
+
position: absolute;
|
|
923
943
|
z-index: 10000;
|
|
924
944
|
box-sizing: border-box;
|
|
925
945
|
border: 2px solid $an-primary;
|
|
@@ -956,7 +976,7 @@
|
|
|
956
976
|
$an-video-accent: #8b5cf6; // violet-500
|
|
957
977
|
|
|
958
978
|
.an-video-resizer {
|
|
959
|
-
position:
|
|
979
|
+
position: absolute;
|
|
960
980
|
z-index: 10000;
|
|
961
981
|
box-sizing: border-box;
|
|
962
982
|
border: 2px solid $an-video-accent;
|
|
@@ -986,11 +1006,10 @@ $an-video-accent: #8b5cf6; // violet-500
|
|
|
986
1006
|
.an-resize-w { top: calc(50% - 4px); left: -4px; cursor: w-resize; }
|
|
987
1007
|
}
|
|
988
1008
|
|
|
989
|
-
//
|
|
990
|
-
.an-video-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
}
|
|
1009
|
+
// .an-video-selected is applied by VideoResizer for JS state tracking.
|
|
1010
|
+
// Visual selection is handled entirely by the .an-video-resizer overlay,
|
|
1011
|
+
// so no CSS outline is needed here (two visible frames would appear otherwise).
|
|
1012
|
+
.an-video-selected {}
|
|
994
1013
|
|
|
995
1014
|
// ---------------------------------------------------------------------------
|
|
996
1015
|
// Icon dialog
|
|
@@ -1082,7 +1101,7 @@ $an-video-accent: #8b5cf6; // violet-500
|
|
|
1082
1101
|
display: grid;
|
|
1083
1102
|
grid-template-columns: repeat(auto-fill, 56px);
|
|
1084
1103
|
gap: 3px;
|
|
1085
|
-
|
|
1104
|
+
height: 256px;
|
|
1086
1105
|
overflow-y: auto;
|
|
1087
1106
|
margin-bottom: 10px;
|
|
1088
1107
|
padding: 4px;
|
|
@@ -1244,7 +1263,7 @@ $an-video-accent: #8b5cf6; // violet-500
|
|
|
1244
1263
|
display: grid;
|
|
1245
1264
|
grid-template-columns: repeat(auto-fill, 42px);
|
|
1246
1265
|
gap: 2px;
|
|
1247
|
-
|
|
1266
|
+
height: 288px;
|
|
1248
1267
|
overflow-y: auto;
|
|
1249
1268
|
margin-bottom: 10px;
|
|
1250
1269
|
padding: 4px;
|
|
@@ -1572,6 +1591,11 @@ $an-video-accent: #8b5cf6; // violet-500
|
|
|
1572
1591
|
.an-table-cell {
|
|
1573
1592
|
background: #24273a;
|
|
1574
1593
|
border-color: #3f3f5f;
|
|
1594
|
+
|
|
1595
|
+
&.active {
|
|
1596
|
+
background: rgba(99, 102, 241, 0.3);
|
|
1597
|
+
border-color: #6366f1;
|
|
1598
|
+
}
|
|
1575
1599
|
}
|
|
1576
1600
|
|
|
1577
1601
|
.an-context-item { color: #cdd6f4; }
|
|
@@ -1670,25 +1694,31 @@ $an-video-accent: #8b5cf6; // violet-500
|
|
|
1670
1694
|
.an-editable {
|
|
1671
1695
|
ul.an-checklist {
|
|
1672
1696
|
list-style: none;
|
|
1673
|
-
padding-left: 0
|
|
1697
|
+
padding-left: 0;
|
|
1674
1698
|
margin: 0.5em 0;
|
|
1675
1699
|
|
|
1676
1700
|
li {
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1701
|
+
// Use padding-left + absolute checkbox so text is a normal inline flow
|
|
1702
|
+
// (not an anonymous flex item). This ensures font-size, line-height and
|
|
1703
|
+
// all inline formats are identical to regular paragraph text.
|
|
1704
|
+
position: relative;
|
|
1705
|
+
padding-left: 22px;
|
|
1706
|
+
padding-top: 1px;
|
|
1707
|
+
padding-bottom: 1px;
|
|
1708
|
+
min-height: 1.4em;
|
|
1681
1709
|
line-height: inherit;
|
|
1710
|
+
font-size: inherit;
|
|
1711
|
+
font-family: inherit;
|
|
1682
1712
|
|
|
1683
1713
|
input[type='checkbox'] {
|
|
1684
|
-
|
|
1714
|
+
position: absolute;
|
|
1715
|
+
left: 0;
|
|
1716
|
+
top: 0.25em;
|
|
1685
1717
|
width: 14px;
|
|
1686
1718
|
height: 14px;
|
|
1687
1719
|
margin: 0;
|
|
1688
1720
|
cursor: pointer;
|
|
1689
1721
|
accent-color: $an-primary;
|
|
1690
|
-
position: relative;
|
|
1691
|
-
top: 2px;
|
|
1692
1722
|
}
|
|
1693
1723
|
|
|
1694
1724
|
// Checked item: dim + strike-through the text node
|
package/types/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface AsnOptions {
|
|
|
43
43
|
/** Number of spaces inserted when Tab is pressed (outside a list). */
|
|
44
44
|
tabSize?: number;
|
|
45
45
|
/** Callback on content change. */
|
|
46
|
-
onChange?: (
|
|
46
|
+
onChange?: (html: string) => void;
|
|
47
47
|
/** Callback on editor focus. */
|
|
48
48
|
onFocus?: (context: Context) => void;
|
|
49
49
|
/** Callback fired on editor blur. */
|
|
@@ -59,9 +59,9 @@ export interface AsnOptions {
|
|
|
59
59
|
/** Callback fired when the word limit is reached. */
|
|
60
60
|
onWordLimitReached?: (context: Context) => void;
|
|
61
61
|
/** Custom image upload handler. */
|
|
62
|
-
onImageUpload?: (files:
|
|
62
|
+
onImageUpload?: (files: File[]) => void;
|
|
63
63
|
/** Callback when an image upload error occurs. */
|
|
64
|
-
onImageError?: (error:
|
|
64
|
+
onImageError?: (error: { file?: File; message: string; error?: unknown }) => void;
|
|
65
65
|
/** Stick the toolbar to the viewport top when scrolling. */
|
|
66
66
|
stickyToolbar?: boolean;
|
|
67
67
|
/** Top offset in px for sticky toolbar (e.g. height of a fixed nav bar). */
|
|
@@ -101,7 +101,7 @@ export interface AsnOptions {
|
|
|
101
101
|
/** Insert a header row (<thead>) when creating new tables. */
|
|
102
102
|
tableHeaderRow?: boolean;
|
|
103
103
|
/** Callback fired after every paste event. */
|
|
104
|
-
onPaste?: (data: { text: string; html: string }) => void;
|
|
104
|
+
onPaste?: (data: { text: string; html: string | null }) => void;
|
|
105
105
|
/** Additional color swatches shown at the top of the color picker. */
|
|
106
106
|
colorSwatches?: string[];
|
|
107
107
|
}
|