autumnnote 1.0.6 → 1.0.8
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 +3 -3
- package/dist/autumnnote.css +168 -4
- package/dist/autumnnote.es.js +886 -115
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +886 -115
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +3 -2
- package/src/js/Context.js +6 -0
- package/src/js/core/sanitise.js +20 -2
- package/src/js/editing/Style.js +288 -24
- package/src/js/editing/Typing.js +2 -2
- package/src/js/module/Buttons.js +5 -4
- package/src/js/module/Clipboard.js +8 -0
- package/src/js/module/CodeTooltip.js +1 -0
- package/src/js/module/ContextMenu.js +146 -8
- package/src/js/module/Editor.js +73 -1
- package/src/js/module/EmojiDialog.js +11 -0
- package/src/js/module/IconDialog.js +11 -0
- package/src/js/module/ImageCropOverlay.js +2 -2
- package/src/js/module/ImageDialog.js +22 -3
- package/src/js/module/ImageResizer.js +2 -0
- package/src/js/module/ImageTooltip.js +9 -0
- package/src/js/module/LinkTooltip.js +4 -0
- package/src/js/module/Placeholder.js +7 -1
- package/src/js/module/TableTooltip.js +399 -86
- package/src/js/module/Toolbar.js +21 -3
- package/src/js/module/VideoDialog.js +5 -5
- package/src/js/module/VideoResizer.js +11 -0
- package/src/js/module/VideoTooltip.js +1 -0
- package/src/js/renderer.js +3 -0
- package/src/styles/autumnnote.scss +194 -12
package/src/js/module/Toolbar.js
CHANGED
|
@@ -513,16 +513,34 @@ export class Toolbar {
|
|
|
513
513
|
select.appendChild(opt);
|
|
514
514
|
});
|
|
515
515
|
|
|
516
|
+
// Save the editor selection when the user starts interacting with the
|
|
517
|
+
// dropdown (mousedown fires before the editor loses focus). When the
|
|
518
|
+
// change handler runs, focus has moved to the <select>; we restore the
|
|
519
|
+
// saved range so execCommand / fontSize() act on the intended text.
|
|
520
|
+
/** @type {Range|null} */
|
|
521
|
+
let _savedRange = null;
|
|
522
|
+
const dMousedown = on(select, 'mousedown', () => {
|
|
523
|
+
const sel = window.getSelection();
|
|
524
|
+
_savedRange = (sel && sel.rangeCount) ? sel.getRangeAt(0).cloneRange() : null;
|
|
525
|
+
});
|
|
526
|
+
|
|
516
527
|
const disposer = on(select, 'change', (e) => {
|
|
517
|
-
const value = e.target.value;
|
|
518
|
-
const selectedOpt = e.target.options[e.target.selectedIndex];
|
|
528
|
+
const value = /** @type {HTMLSelectElement} */ (e.target).value;
|
|
529
|
+
const selectedOpt = /** @type {HTMLSelectElement} */ (e.target).options[/** @type {HTMLSelectElement} */ (e.target).selectedIndex];
|
|
519
530
|
if (!value || selectedOpt.disabled) return;
|
|
520
531
|
this.context.invoke('editor.focus');
|
|
532
|
+
// Restore selection saved on mousedown so the action targets the correct text.
|
|
533
|
+
if (_savedRange) {
|
|
534
|
+
try {
|
|
535
|
+
const sel = window.getSelection();
|
|
536
|
+
if (sel) { sel.removeAllRanges(); sel.addRange(_savedRange); }
|
|
537
|
+
} catch (_) { /* range may be stale if DOM changed */ }
|
|
538
|
+
}
|
|
521
539
|
def.action(this.context, value);
|
|
522
540
|
this.context.invoke('editor.afterCommand');
|
|
523
541
|
});
|
|
524
542
|
|
|
525
|
-
this._disposers.push(disposer);
|
|
543
|
+
this._disposers.push(dMousedown, disposer);
|
|
526
544
|
return select;
|
|
527
545
|
}
|
|
528
546
|
|
|
@@ -73,12 +73,12 @@ export class VideoDialog {
|
|
|
73
73
|
// URL input
|
|
74
74
|
const urlLabel = createElement('label', { class: 'an-label' });
|
|
75
75
|
urlLabel.textContent = 'Video URL';
|
|
76
|
-
const urlInput = createElement('input', {
|
|
76
|
+
const urlInput = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
77
77
|
type: 'url',
|
|
78
78
|
class: 'an-input',
|
|
79
79
|
placeholder: 'YouTube, Vimeo, or direct .mp4 URL',
|
|
80
80
|
autocomplete: 'off',
|
|
81
|
-
});
|
|
81
|
+
}));
|
|
82
82
|
this._urlInput = urlInput;
|
|
83
83
|
|
|
84
84
|
// Hint (detected source)
|
|
@@ -88,14 +88,14 @@ export class VideoDialog {
|
|
|
88
88
|
// Width
|
|
89
89
|
const widthLabel = createElement('label', { class: 'an-label' });
|
|
90
90
|
widthLabel.textContent = 'Width (px)';
|
|
91
|
-
const widthInput = createElement('input', {
|
|
91
|
+
const widthInput = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
92
92
|
type: 'number',
|
|
93
93
|
class: 'an-input',
|
|
94
94
|
placeholder: '560',
|
|
95
95
|
min: '80',
|
|
96
96
|
max: '1920',
|
|
97
97
|
value: '560',
|
|
98
|
-
});
|
|
98
|
+
}));
|
|
99
99
|
this._widthInput = widthInput;
|
|
100
100
|
|
|
101
101
|
// Buttons
|
|
@@ -119,7 +119,7 @@ export class VideoDialog {
|
|
|
119
119
|
const d1 = on(insertBtn, 'click', () => this._onInsert());
|
|
120
120
|
const d2 = on(cancelBtn, 'click', () => this._close());
|
|
121
121
|
const d3 = on(overlay, 'click', (e) => { if (e.target === overlay) this._close(); });
|
|
122
|
-
const d4 = on(urlInput, 'keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); this._onInsert(); } });
|
|
122
|
+
const d4 = on(urlInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
|
|
123
123
|
this._disposers.push(d0, d1, d2, d3, d4);
|
|
124
124
|
|
|
125
125
|
return overlay;
|
|
@@ -41,6 +41,7 @@ export class VideoResizer {
|
|
|
41
41
|
this._disposers.push(
|
|
42
42
|
on(editable, 'click', (e) => this._onEditorClick(e)),
|
|
43
43
|
on(editable, 'contextmenu', (e) => {
|
|
44
|
+
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
44
45
|
const wrapper = this._findWrapper(e.target);
|
|
45
46
|
if (wrapper) this._select(wrapper);
|
|
46
47
|
}),
|
|
@@ -48,6 +49,15 @@ export class VideoResizer {
|
|
|
48
49
|
on(window, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
49
50
|
on(window, 'resize', onWindowResize),
|
|
50
51
|
on(editable, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
|
|
52
|
+
// D1: Prevent native browser drag of video wrappers. Without this, a user
|
|
53
|
+
// can hold-and-drag to produce a "copy" that lands outside .an-editable,
|
|
54
|
+
// where the .an-video-shield CSS loses its containing context so the
|
|
55
|
+
// copied video becomes directly playable.
|
|
56
|
+
on(editable, 'dragstart', (e) => {
|
|
57
|
+
if (e.target instanceof Element && e.target.closest('.an-video-wrapper')) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
51
61
|
);
|
|
52
62
|
|
|
53
63
|
return this;
|
|
@@ -131,6 +141,7 @@ export class VideoResizer {
|
|
|
131
141
|
}
|
|
132
142
|
|
|
133
143
|
_onEditorClick(e) {
|
|
144
|
+
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
134
145
|
const wrapper = this._findWrapper(e.target);
|
|
135
146
|
if (wrapper) {
|
|
136
147
|
e.preventDefault();
|
|
@@ -37,6 +37,7 @@ export class VideoTooltip {
|
|
|
37
37
|
|
|
38
38
|
this._disposers.push(
|
|
39
39
|
on(editable, 'mouseover', (e) => {
|
|
40
|
+
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
40
41
|
// The shield div sits on top of iframes — we detect hover via it or the wrapper
|
|
41
42
|
const wrapper = e.target.closest('.an-video-wrapper');
|
|
42
43
|
if (wrapper && editable.contains(wrapper)) {
|
package/src/js/renderer.js
CHANGED
|
@@ -78,6 +78,9 @@ export function renderLayout(targetEl, options) {
|
|
|
78
78
|
// Read-only mode
|
|
79
79
|
if (options.readOnly) {
|
|
80
80
|
container.classList.add('an-disabled');
|
|
81
|
+
editable.querySelectorAll('ul.an-checklist input[type="checkbox"]').forEach((cb) => {
|
|
82
|
+
cb.setAttribute('disabled', '');
|
|
83
|
+
});
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
// Text direction
|
|
@@ -48,8 +48,36 @@
|
|
|
48
48
|
.an-editable {
|
|
49
49
|
cursor: text;
|
|
50
50
|
user-select: text;
|
|
51
|
-
//
|
|
52
|
-
|
|
51
|
+
// No background change — keep the same appearance as editable mode
|
|
52
|
+
// so read-only content looks identical to the live editor.
|
|
53
|
+
|
|
54
|
+
// Block checklist checkbox interaction in read-only mode.
|
|
55
|
+
// Keep opacity:1 so disabled checkboxes are still clearly visible.
|
|
56
|
+
ul.an-checklist input[type='checkbox'] {
|
|
57
|
+
pointer-events: none;
|
|
58
|
+
cursor: default;
|
|
59
|
+
opacity: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Prevent images from being dragged out.
|
|
63
|
+
img {
|
|
64
|
+
-webkit-user-drag: none;
|
|
65
|
+
user-drag: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Prevent video wrapper itself from being dragged, but keep pointer-events
|
|
69
|
+
// active so the video shield works and the iframe is visible/interactive.
|
|
70
|
+
.an-video-wrapper {
|
|
71
|
+
-webkit-user-drag: none;
|
|
72
|
+
user-drag: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// D-3: In read-only mode, disable the video shield so clicks reach the
|
|
76
|
+
// underlying <iframe> / <video> and users can play videos normally.
|
|
77
|
+
// The shield is only needed in edit mode to prevent accidental interaction.
|
|
78
|
+
.an-video-wrapper .an-video-shield {
|
|
79
|
+
pointer-events: none;
|
|
80
|
+
}
|
|
53
81
|
}
|
|
54
82
|
}
|
|
55
83
|
|
|
@@ -87,6 +115,12 @@
|
|
|
87
115
|
align-items: center;
|
|
88
116
|
gap: 2px;
|
|
89
117
|
|
|
118
|
+
// Icon-only toolbar buttons should not have horizontal padding so all
|
|
119
|
+
// buttons appear consistently square (min-width = height = $an-btn-height).
|
|
120
|
+
// Dialog buttons (.an-dialog-actions .an-btn) keep their padding via the
|
|
121
|
+
// lower-specificity .an-btn rule.
|
|
122
|
+
> .an-btn { padding: 0; }
|
|
123
|
+
|
|
90
124
|
// Separator between groups
|
|
91
125
|
& + .an-btn-group {
|
|
92
126
|
position: relative;
|
|
@@ -388,6 +422,7 @@
|
|
|
388
422
|
// =============================================================================
|
|
389
423
|
|
|
390
424
|
.an-editable {
|
|
425
|
+
position: relative; // Establishes containing block for the ::before placeholder
|
|
391
426
|
flex: 1;
|
|
392
427
|
padding: $an-editable-pad;
|
|
393
428
|
min-height: 0;
|
|
@@ -397,24 +432,24 @@
|
|
|
397
432
|
overflow-y: auto;
|
|
398
433
|
word-break: break-word;
|
|
399
434
|
|
|
400
|
-
// Placeholder — only
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
//
|
|
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.
|
|
435
|
+
// Placeholder — shown only when the editable is empty and not focused.
|
|
436
|
+
// Uses position:absolute so it never shifts the cursor or block content,
|
|
437
|
+
// and requires position:relative on this element (added above) so it is
|
|
438
|
+
// scoped to the editable area and never overlaps the toolbar.
|
|
407
439
|
&.an-placeholder:not(:focus)::before {
|
|
408
440
|
content: attr(data-placeholder);
|
|
409
441
|
color: $an-muted;
|
|
410
442
|
pointer-events: none;
|
|
411
|
-
|
|
443
|
+
position: absolute;
|
|
444
|
+
top: 12px; // matches top padding from $an-editable-pad (12px 14px)
|
|
445
|
+
left: 14px; // matches left padding
|
|
446
|
+
right: 14px;
|
|
412
447
|
}
|
|
413
448
|
|
|
414
449
|
// Content styles
|
|
415
450
|
p { margin: 0 0 0.75em; }
|
|
416
451
|
h1, h2, h3, h4, h5, h6 { margin: 0.5em 0; line-height: 1.3; }
|
|
417
|
-
ul, ol { margin: 0 0 0.75em 1.5em;
|
|
452
|
+
ul, ol { margin: 0 0 0.75em 0; padding-left: 1.5em; list-style-position: inside; }
|
|
418
453
|
li { margin-bottom: 0.25em; }
|
|
419
454
|
blockquote {
|
|
420
455
|
margin: 0.5em 0 0.5em 1em;
|
|
@@ -472,9 +507,21 @@
|
|
|
472
507
|
inset: 0;
|
|
473
508
|
z-index: 2;
|
|
474
509
|
cursor: default;
|
|
510
|
+
pointer-events: all; // Explicitly block pointer events reaching the iframe
|
|
475
511
|
}
|
|
476
512
|
}
|
|
477
513
|
|
|
514
|
+
// Ensure video shield stays effective even when a video copy moves outside
|
|
515
|
+
// .an-editable temporarily (e.g. native drag-and-drop).
|
|
516
|
+
// This global rule mirrors the scoped one above.
|
|
517
|
+
@at-root .an-video-wrapper .an-video-shield {
|
|
518
|
+
position: absolute;
|
|
519
|
+
inset: 0;
|
|
520
|
+
z-index: 2;
|
|
521
|
+
cursor: default;
|
|
522
|
+
pointer-events: all;
|
|
523
|
+
}
|
|
524
|
+
|
|
478
525
|
// Table styles — target both classed and unclassed tables
|
|
479
526
|
table, .an-table {
|
|
480
527
|
border-collapse: collapse;
|
|
@@ -605,6 +652,26 @@
|
|
|
605
652
|
letter-spacing: 0.02em;
|
|
606
653
|
}
|
|
607
654
|
|
|
655
|
+
// Active state for toggle buttons in tooltips (e.g. Select Cells)
|
|
656
|
+
.an-link-tooltip-btn--active {
|
|
657
|
+
background: rgba(#3b82f6, 0.12) !important;
|
|
658
|
+
color: #3b82f6 !important;
|
|
659
|
+
svg { stroke: #3b82f6; }
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Cells highlighted when select mode is active
|
|
663
|
+
.an-cell-selected {
|
|
664
|
+
background-color: rgba(#3b82f6, 0.18) !important;
|
|
665
|
+
outline: 2px solid #3b82f6;
|
|
666
|
+
outline-offset: -2px;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Cursor hint for all cells when select mode is on
|
|
670
|
+
.an-table-select-mode td,
|
|
671
|
+
.an-table-select-mode th {
|
|
672
|
+
cursor: cell;
|
|
673
|
+
}
|
|
674
|
+
|
|
608
675
|
// Size popover (column width / row height)
|
|
609
676
|
.an-size-popover {
|
|
610
677
|
position: fixed;
|
|
@@ -934,6 +1001,91 @@
|
|
|
934
1001
|
background: $an-border;
|
|
935
1002
|
}
|
|
936
1003
|
|
|
1004
|
+
// Color-strip variant — SVG stacked above a thin color bar (matches toolbar)
|
|
1005
|
+
.an-context-icon--color {
|
|
1006
|
+
flex-direction: column;
|
|
1007
|
+
gap: 2px;
|
|
1008
|
+
width: 16px;
|
|
1009
|
+
align-items: center;
|
|
1010
|
+
|
|
1011
|
+
.an-context-icon-svg {
|
|
1012
|
+
display: flex;
|
|
1013
|
+
align-items: center;
|
|
1014
|
+
justify-content: center;
|
|
1015
|
+
line-height: 0;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.an-context-color-strip {
|
|
1020
|
+
display: block;
|
|
1021
|
+
width: 100%;
|
|
1022
|
+
height: 3px;
|
|
1023
|
+
border-radius: 1px;
|
|
1024
|
+
flex-shrink: 0;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Color palette inside context menu
|
|
1028
|
+
.an-context-color-palette {
|
|
1029
|
+
display: flex;
|
|
1030
|
+
flex-wrap: wrap;
|
|
1031
|
+
gap: 3px;
|
|
1032
|
+
padding: 6px 12px 2px;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
.an-context-color-swatch {
|
|
1036
|
+
width: 15px;
|
|
1037
|
+
height: 15px;
|
|
1038
|
+
border-radius: 3px;
|
|
1039
|
+
border: 1px solid rgba(0, 0, 0, 0.12);
|
|
1040
|
+
cursor: pointer;
|
|
1041
|
+
flex-shrink: 0;
|
|
1042
|
+
position: relative;
|
|
1043
|
+
transition: transform 0.1s;
|
|
1044
|
+
|
|
1045
|
+
&:hover {
|
|
1046
|
+
transform: scale(1.28);
|
|
1047
|
+
z-index: 1;
|
|
1048
|
+
border-color: rgba(0, 0, 0, 0.4);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
.an-context-color-none {
|
|
1053
|
+
display: flex;
|
|
1054
|
+
align-items: center;
|
|
1055
|
+
justify-content: center;
|
|
1056
|
+
background: #fff !important;
|
|
1057
|
+
color: #999;
|
|
1058
|
+
|
|
1059
|
+
svg { display: block; }
|
|
1060
|
+
|
|
1061
|
+
&:hover { color: #333; }
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.an-context-color-custom {
|
|
1065
|
+
display: flex;
|
|
1066
|
+
align-items: center;
|
|
1067
|
+
gap: 8px;
|
|
1068
|
+
padding: 4px 12px 8px;
|
|
1069
|
+
font-size: 12px;
|
|
1070
|
+
color: $an-muted;
|
|
1071
|
+
cursor: pointer;
|
|
1072
|
+
|
|
1073
|
+
input[type='color'] {
|
|
1074
|
+
width: 18px;
|
|
1075
|
+
height: 18px;
|
|
1076
|
+
padding: 0;
|
|
1077
|
+
border: 1px solid $an-border;
|
|
1078
|
+
border-radius: 3px;
|
|
1079
|
+
cursor: pointer;
|
|
1080
|
+
background: transparent;
|
|
1081
|
+
-webkit-appearance: none;
|
|
1082
|
+
appearance: none;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
span { flex: 1; }
|
|
1086
|
+
&:hover span { color: $an-text; }
|
|
1087
|
+
}
|
|
1088
|
+
|
|
937
1089
|
// ---------------------------------------------------------------------------
|
|
938
1090
|
// Image resize overlay
|
|
939
1091
|
// ---------------------------------------------------------------------------
|
|
@@ -1378,7 +1530,11 @@ $an-video-accent: #8b5cf6; // violet-500
|
|
|
1378
1530
|
margin: 0.75em 0;
|
|
1379
1531
|
text-align: center;
|
|
1380
1532
|
|
|
1381
|
-
img {
|
|
1533
|
+
img {
|
|
1534
|
+
display: block;
|
|
1535
|
+
max-width: 100%;
|
|
1536
|
+
margin: 0 auto; // Centers img when figure uses fit-content or explicit width
|
|
1537
|
+
}
|
|
1382
1538
|
}
|
|
1383
1539
|
|
|
1384
1540
|
figcaption.an-figcaption {
|
|
@@ -1637,6 +1793,17 @@ $an-video-accent: #8b5cf6; // violet-500
|
|
|
1637
1793
|
color: #cdd6f4;
|
|
1638
1794
|
}
|
|
1639
1795
|
|
|
1796
|
+
.an-link-tooltip-btn--active {
|
|
1797
|
+
background: rgba(#6366f1, 0.2) !important;
|
|
1798
|
+
color: #818cf8 !important;
|
|
1799
|
+
svg { stroke: #818cf8; }
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
.an-cell-selected {
|
|
1803
|
+
background-color: rgba(#6366f1, 0.25) !important;
|
|
1804
|
+
outline-color: #6366f1;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1640
1807
|
.an-table-cell {
|
|
1641
1808
|
background: #24273a;
|
|
1642
1809
|
border-color: #3f3f5f;
|
|
@@ -1650,6 +1817,21 @@ $an-video-accent: #8b5cf6; // violet-500
|
|
|
1650
1817
|
.an-context-item { color: #cdd6f4; }
|
|
1651
1818
|
.an-context-sep { background: #3f3f5f; }
|
|
1652
1819
|
|
|
1820
|
+
.an-context-color-swatch {
|
|
1821
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
1822
|
+
&:hover { border-color: rgba(255, 255, 255, 0.5); }
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
.an-context-color-none {
|
|
1826
|
+
background: #1e1e2e !important;
|
|
1827
|
+
color: #888;
|
|
1828
|
+
&:hover { color: #ccc; }
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
.an-context-color-custom {
|
|
1832
|
+
input[type='color'] { border-color: #3f3f5f; }
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1653
1835
|
.an-icon-cat {
|
|
1654
1836
|
border-color: #3f3f5f;
|
|
1655
1837
|
color: #a6adc8;
|