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.
@@ -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)) {
@@ -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
- // Subtle visual cue that the area is read-only.
52
- background: #fafafa;
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 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.
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
- float: left;
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; padding: 0; }
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 { display: block; max-width: 100%; }
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;