autumnnote 1.0.4 → 1.0.6

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.
@@ -74,6 +74,7 @@ const ICONS = {
74
74
  mergeCells: `<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="7" width="8" height="10" rx="1"/><rect x="14" y="7" width="8" height="10" rx="1"/><path d="M10 12h4"/><path d="M12 10l2 2-2 2"/></svg>`,
75
75
  colWidth: `<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"><line x1="7" y1="4" x2="7" y2="20"/><line x1="17" y1="4" x2="17" y2="20"/><line x1="7" y1="12" x2="17" y2="12"/><path d="M10 9l-3 3 3 3"/><path d="M14 9l3 3-3 3"/></svg>`,
76
76
  rowHeight: `<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"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="17" x2="20" y2="17"/><line x1="12" y1="7" x2="12" y2="17"/><path d="M9 10l3-3 3 3"/><path d="M9 14l3 3 3-3"/></svg>`,
77
+ tableBorder: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6" stroke-width="1"/><line x1="3" y1="13" x2="21" y2="13" stroke-width="2"/><line x1="3" y1="20" x2="21" y2="20" stroke-width="3"/></svg>`,
77
78
  deleteTable: `<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="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/><line x1="16" y1="16" x2="22" y2="22" stroke="#ef4444"/><line x1="22" y1="16" x2="16" y2="22" stroke="#ef4444"/></svg>`,
78
79
  };
79
80
 
@@ -320,8 +321,9 @@ export class TableTooltip {
320
321
  el.appendChild(this._sep());
321
322
 
322
323
  // Resize
323
- el.appendChild(this._makeBtn(ICONS.colWidth, 'Column Width', () => this._openSizePopover('col')));
324
- el.appendChild(this._makeBtn(ICONS.rowHeight, 'Row Height', () => this._openSizePopover('row')));
324
+ el.appendChild(this._makeBtn(ICONS.colWidth, 'Column Width', () => this._openSizePopover('col')));
325
+ el.appendChild(this._makeBtn(ICONS.rowHeight, 'Row Height', () => this._openSizePopover('row')));
326
+ el.appendChild(this._makeBtn(ICONS.tableBorder,'Table Border Width',() => this._openSizePopover('border')));
325
327
 
326
328
  el.appendChild(this._sep());
327
329
 
@@ -641,30 +643,59 @@ export class TableTooltip {
641
643
  _openSizePopover(type) {
642
644
  const cell = this._getCell();
643
645
  if (!cell || !this._sizePopover) return;
644
- const isCol = type === 'col';
645
- this._sizeTitleEl.textContent = isCol ? 'Column Width (px)' : 'Row Height (px)';
646
- this._sizeInputEl.value = isCol
647
- ? (cell.offsetWidth || 120)
648
- : (cell.closest('tr') ? (cell.closest('tr').offsetHeight || 40) : 40);
649
-
650
- this._sizeApply = (val) => {
651
- if (isCol) {
652
- const table = cell.closest('table');
653
- const visualColIdx = getVisualColIndex(cell);
654
- const rows = Array.from(table.querySelectorAll('tr'));
655
- // Batch reads, then writes
656
- const cells = rows.map(r => getCellAtVisualCol(r, visualColIdx));
657
- cells.forEach(c => {
658
- if (c) { c.style.width = `${val}px`; c.style.minWidth = `${val}px`; }
659
- });
660
- } else {
661
- const row = cell.closest('tr');
662
- if (row) {
663
- for (const c of row.cells) { c.style.height = `${val}px`; c.style.minHeight = `${val}px`; }
646
+
647
+ if (type === 'border') {
648
+ const table = cell.closest('table');
649
+ if (!table) return;
650
+ // Read current inline border-width, fall back to computed value, then default 1px
651
+ const firstCell = table.querySelector('td, th');
652
+ const currentPx = firstCell
653
+ ? (parseInt(firstCell.style.borderWidth, 10) ||
654
+ parseInt(window.getComputedStyle(firstCell).borderWidth, 10) || 1)
655
+ : 1;
656
+ this._sizeTitleEl.textContent = 'Table Border Width (px)';
657
+ this._sizeInputEl.min = '0';
658
+ this._sizeInputEl.max = '10';
659
+ this._sizeInputEl.value = currentPx;
660
+ this._sizeApply = (val) => {
661
+ // Apply border-width to every cell; with border-collapse:collapse this
662
+ // controls all grid lines including the outer table border.
663
+ // Setting only borderWidth preserves the existing border-color from CSS.
664
+ const cells = Array.from(table.querySelectorAll('td, th'));
665
+ if (val === 0) {
666
+ cells.forEach(c => { c.style.borderWidth = '0'; c.style.borderStyle = 'none'; });
667
+ } else {
668
+ cells.forEach(c => { c.style.borderWidth = `${val}px`; c.style.borderStyle = 'solid'; });
664
669
  }
665
- }
666
- this.context.invoke('editor.afterCommand');
667
- };
670
+ this.context.invoke('editor.afterCommand');
671
+ };
672
+ } else {
673
+ const isCol = type === 'col';
674
+ this._sizeTitleEl.textContent = isCol ? 'Column Width (px)' : 'Row Height (px)';
675
+ this._sizeInputEl.min = '1';
676
+ this._sizeInputEl.max = '2000';
677
+ this._sizeInputEl.value = isCol
678
+ ? (cell.offsetWidth || 120)
679
+ : (cell.closest('tr') ? (cell.closest('tr').offsetHeight || 40) : 40);
680
+ this._sizeApply = (val) => {
681
+ if (isCol) {
682
+ const table = cell.closest('table');
683
+ const visualColIdx = getVisualColIndex(cell);
684
+ const rows = Array.from(table.querySelectorAll('tr'));
685
+ // Batch reads, then writes
686
+ const cells = rows.map(r => getCellAtVisualCol(r, visualColIdx));
687
+ cells.forEach(c => {
688
+ if (c) { c.style.width = `${val}px`; c.style.minWidth = `${val}px`; }
689
+ });
690
+ } else {
691
+ const row = cell.closest('tr');
692
+ if (row) {
693
+ for (const c of row.cells) { c.style.height = `${val}px`; c.style.minHeight = `${val}px`; }
694
+ }
695
+ }
696
+ this.context.invoke('editor.afterCommand');
697
+ };
698
+ }
668
699
 
669
700
  this._sizePopover.style.display = 'block';
670
701
  requestAnimationFrame(() => {
@@ -11,6 +11,97 @@ import { createElement, on } from '../core/dom.js';
11
11
  // auto-injects its own FA <link> for the icon-picker glyph rendering.
12
12
  let _faPageLevelReady = null;
13
13
 
14
+ // ---------------------------------------------------------------------------
15
+ // Module-level icon lookup tables — built once, shared across all instances.
16
+ // Previously these were re-created inside _createButton() on every button
17
+ // render, producing O(buttons × map-size) allocations per toolbar init.
18
+ // ---------------------------------------------------------------------------
19
+ const _S = 'stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
20
+ const _svgWrap = (paths) =>
21
+ `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" ${_S} style="display:block">${paths}</svg>`;
22
+
23
+ const _SVG_MAP = new Map([
24
+ // Format
25
+ ['bold', _svgWrap('<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>')],
26
+ ['italic', _svgWrap('<line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>')],
27
+ ['underline', _svgWrap('<path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"/><line x1="4" y1="21" x2="20" y2="21"/>')],
28
+ ['strikethrough', _svgWrap('<path d="M17.3 12H6.7"/><path d="M10 6.5C10 5.1 11.1 4 12.5 4c1.4 0 2.5 1.1 2.5 2.5 0 .8-.4 1.5-1 2"/><path d="M14 17.5C14 19 12.9 20 11.5 20 10.1 20 9 18.9 9 17.5c0-.8.4-1.5 1-2"/>')],
29
+ ['superscript', _svgWrap('<path d="m4 19 8-8"/><path d="m12 19-8-8"/><path d="M20 12h-4c0-1.5.44-2 1.5-2.5S20 8.33 20 7.25C20 6 19 5 17.5 5S15 6 15 7"/>')],
30
+ ['subscript', _svgWrap('<path d="m4 5 8 8"/><path d="m12 5-8 8"/><path d="M20 21h-4c0-1.5.44-2 1.5-2.5S20 17.33 20 16.25C20 15 19 14 17.5 14S15 15 15 16"/>')],
31
+ // Alignment
32
+ ['align-left', _svgWrap('<line x1="21" y1="6" x2="3" y2="6"/><line x1="15" y1="12" x2="3" y2="12"/><line x1="17" y1="18" x2="3" y2="18"/>')],
33
+ ['align-center', _svgWrap('<line x1="21" y1="6" x2="3" y2="6"/><line x1="18" y1="12" x2="6" y2="12"/><line x1="21" y1="18" x2="3" y2="18"/>')],
34
+ ['align-right', _svgWrap('<line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="12" x2="9" y2="12"/><line x1="21" y1="18" x2="7" y2="18"/>')],
35
+ ['align-justify', _svgWrap('<line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="12" x2="3" y2="12"/><line x1="21" y1="18" x2="3" y2="18"/>')],
36
+ // Lists
37
+ ['list-ul', _svgWrap('<line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4" cy="6" r="1" fill="currentColor" stroke="none"/><circle cx="4" cy="12" r="1" fill="currentColor" stroke="none"/><circle cx="4" cy="18" r="1" fill="currentColor" stroke="none"/>')],
38
+ ['list-ol', _svgWrap('<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1V3"/><path d="M4 10h2l-2 2h2"/><path d="M4 16.5A1.5 1.5 0 0 1 5.5 15a1.5 1.5 0 0 1 0 3H4"/>')],
39
+ ['indent', _svgWrap('<polyline points="3 8 7 12 3 16"/><line x1="21" y1="12" x2="11" y2="12"/><line x1="21" y1="6" x2="11" y2="6"/><line x1="21" y1="18" x2="11" y2="18"/>')],
40
+ ['outdent', _svgWrap('<polyline points="7 8 3 12 7 16"/><line x1="21" y1="12" x2="11" y2="12"/><line x1="21" y1="6" x2="11" y2="6"/><line x1="21" y1="18" x2="11" y2="18"/>')],
41
+ // History
42
+ ['undo', _svgWrap('<path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>')],
43
+ ['redo', _svgWrap('<path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13"/>')],
44
+ // Insert
45
+ ['minus', _svgWrap('<line x1="5" y1="12" x2="19" y2="12"/>')],
46
+ ['link', _svgWrap('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>')],
47
+ ['image', _svgWrap('<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>')],
48
+ ['video', _svgWrap('<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/>')],
49
+ ['table', _svgWrap('<rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/>')],
50
+ ['emoji', _svgWrap('<circle cx="12" cy="12" r="10"/><path d="M8.5 14.5s1.5 2.5 3.5 2.5 3.5-2.5 3.5-2.5"/><circle cx="9" cy="9" r="1.5" fill="currentColor" stroke="none"/><circle cx="15" cy="9" r="1.5" fill="currentColor" stroke="none"/>')],
51
+ ['icon', _svgWrap('<circle cx="8" cy="8" r="3"/><circle cx="16" cy="8" r="3"/><rect x="5" y="13" width="6" height="6" rx="1"/><rect x="13" y="13" width="6" height="6" rx="1"/>')],
52
+ // View
53
+ ['code', _svgWrap('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>')],
54
+ ['expand', _svgWrap('<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"/>')],
55
+ // Color pickers
56
+ ['foreColor', _svgWrap('<path d="M4 20L12 4L20 20"/><line x1="7.5" y1="14" x2="16.5" y2="14"/>')],
57
+ ['backColor', _svgWrap('<path d="M3 21v-4l9-9 4 4-9 9z"/><path d="M12 8l4 4"/>')],
58
+ ['keyboard', _svgWrap('<rect x="2" y="6" width="20" height="12" rx="2"/><line x1="6" y1="10" x2="6" y2="10" stroke-width="2.5"/><line x1="10" y1="10" x2="10" y2="10" stroke-width="2.5"/><line x1="14" y1="10" x2="14" y2="10" stroke-width="2.5"/><line x1="18" y1="10" x2="18" y2="10" stroke-width="2.5"/><line x1="8" y1="14" x2="16" y2="14" stroke-width="2"/>')],
59
+ ['caption', _svgWrap('<rect x="3" y="3" width="18" height="11" rx="2"/><line x1="6" y1="18" x2="18" y2="18"/><line x1="9" y1="21" x2="15" y2="21"/>')],
60
+ ['remove-format', _svgWrap('<path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21"/><path d="M22 21H7"/><path d="m5 11 9 9"/>')],
61
+ ['direction', _svgWrap('<path d="M12 20V4"/><path d="m9 7-3 3 3 3"/><path d="M4 10h8"/><path d="m15 7 3 3-3 3"/><path d="M20 10h-8"/>')],
62
+ ['search', _svgWrap('<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>')],
63
+ ['find-replace', _svgWrap('<circle cx="10" cy="10" r="6"/><line x1="18" y1="18" x2="14.35" y2="14.35"/><path d="M16 19h6"/><path d="M19 16v6"/>')],
64
+ ['inline-code', _svgWrap('<path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1"/><path d="M16 3h1a2 2 0 0 1 2 2v5c0 1.1.9 2 2 2a2 2 0 0 1-2 2v5a2 2 0 0 1-2 2h-1"/>')],
65
+ ['checklist', _svgWrap('<rect x="3" y="4" width="5" height="5" rx="1"/><path d="m4 6.5 1 1 2-2"/><rect x="3" y="13" width="5" height="5" rx="1"/><line x1="10" y1="6.5" x2="21" y2="6.5"/><line x1="10" y1="15.5" x2="21" y2="15.5"/>')],
66
+ ['print', _svgWrap('<path d="M6 9V2h12v7"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>')],
67
+ ]);
68
+
69
+ const _FA_MAP = new Map([
70
+ ['bold', 'fa-bold'],
71
+ ['italic', 'fa-italic'],
72
+ ['underline', 'fa-underline'],
73
+ ['strikethrough', 'fa-strikethrough'],
74
+ ['superscript', 'fa-superscript'],
75
+ ['subscript', 'fa-subscript'],
76
+ ['align-left', 'fa-align-left'],
77
+ ['align-center', 'fa-align-center'],
78
+ ['align-right', 'fa-align-right'],
79
+ ['align-justify', 'fa-align-justify'],
80
+ ['list-ul', 'fa-list-ul'],
81
+ ['list-ol', 'fa-list-ol'],
82
+ ['indent', 'fa-indent'],
83
+ ['outdent', 'fa-outdent'],
84
+ ['undo', 'fa-rotate-left'],
85
+ ['redo', 'fa-rotate-right'],
86
+ ['minus', 'fa-minus'],
87
+ ['link', 'fa-link'],
88
+ ['image', 'fa-image'],
89
+ ['code', 'fa-code'],
90
+ ['expand', 'fa-expand'],
91
+ ['emoji', 'fa-face-smile'],
92
+ ['icon', 'fa-icons'],
93
+ ['foreColor', 'fa-font'],
94
+ ['backColor', 'fa-highlighter'],
95
+ ['keyboard', 'fa-keyboard'],
96
+ ['remove-format', 'fa-remove-format'],
97
+ ['direction', 'fa-arrow-right-arrow-left'],
98
+ ['search', 'fa-magnifying-glass'],
99
+ ['find-replace', 'fa-magnifying-glass-plus'],
100
+ ['inline-code', 'fa-code'],
101
+ ['checklist', 'fa-list-check'],
102
+ ['print', 'fa-print'],
103
+ ]);
104
+
14
105
  export class Toolbar {
15
106
  /**
16
107
  * @param {import('../Context.js').Context} context
@@ -55,6 +146,9 @@ export class Toolbar {
55
146
 
56
147
  _buildButtons() {
57
148
  const toolbar = this.options.toolbar || [];
149
+ // Build into a DocumentFragment so all groups are appended in a single
150
+ // DOM operation, avoiding one reflow per group.
151
+ const fragment = document.createDocumentFragment();
58
152
  toolbar.forEach((group) => {
59
153
  const groupEl = createElement('div', { class: 'an-btn-group' });
60
154
  group.forEach((btnDef) => {
@@ -65,8 +159,9 @@ export class Toolbar {
65
159
  else el = this._createButton(btnDef);
66
160
  groupEl.appendChild(el);
67
161
  });
68
- this.el.appendChild(groupEl);
162
+ fragment.appendChild(groupEl);
69
163
  });
164
+ this.el.appendChild(fragment);
70
165
  }
71
166
 
72
167
  /**
@@ -271,10 +366,13 @@ export class Toolbar {
271
366
 
272
367
  const restoreSelection = () => {
273
368
  if (!savedRange) return;
274
- const sel = window.getSelection();
275
- if (sel) {
369
+ try {
370
+ const sel = window.getSelection();
371
+ if (!sel) return;
276
372
  sel.removeAllRanges();
277
373
  sel.addRange(savedRange);
374
+ } catch (_) {
375
+ // Range target may be detached if DOM changed while popup was open
278
376
  }
279
377
  };
280
378
 
@@ -448,110 +546,23 @@ export class Toolbar {
448
546
  });
449
547
 
450
548
  // Render icon: prefer FontAwesome if enabled; otherwise fall back to SVG or text.
451
-
452
- // Heroicons-style stroke SVGs — consistent with context menu icons
453
- const S = 'stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
454
- const svgWrap = (paths) => `<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" ${S} style="display:block">${paths}</svg>`;
455
-
456
- const svgMap = new Map([
457
- // Format
458
- ['bold', svgWrap('<path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/>')],
459
- ['italic', svgWrap('<line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/>')],
460
- ['underline', svgWrap('<path d="M6 3v7a6 6 0 0 0 6 6 6 6 0 0 0 6-6V3"/><line x1="4" y1="21" x2="20" y2="21"/>')],
461
- ['strikethrough', svgWrap('<path d="M17.3 12H6.7"/><path d="M10 6.5C10 5.1 11.1 4 12.5 4c1.4 0 2.5 1.1 2.5 2.5 0 .8-.4 1.5-1 2"/><path d="M14 17.5C14 19 12.9 20 11.5 20 10.1 20 9 18.9 9 17.5c0-.8.4-1.5 1-2"/>')],
462
- ['superscript', svgWrap('<path d="m4 19 8-8"/><path d="m12 19-8-8"/><path d="M20 12h-4c0-1.5.44-2 1.5-2.5S20 8.33 20 7.25C20 6 19 5 17.5 5S15 6 15 7"/>')],
463
- ['subscript', svgWrap('<path d="m4 5 8 8"/><path d="m12 5-8 8"/><path d="M20 21h-4c0-1.5.44-2 1.5-2.5S20 17.33 20 16.25C20 15 19 14 17.5 14S15 15 15 16"/>')],
464
- // Alignment
465
- ['align-left', svgWrap('<line x1="21" y1="6" x2="3" y2="6"/><line x1="15" y1="12" x2="3" y2="12"/><line x1="17" y1="18" x2="3" y2="18"/>')],
466
- ['align-center', svgWrap('<line x1="21" y1="6" x2="3" y2="6"/><line x1="18" y1="12" x2="6" y2="12"/><line x1="21" y1="18" x2="3" y2="18"/>')],
467
- ['align-right', svgWrap('<line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="12" x2="9" y2="12"/><line x1="21" y1="18" x2="7" y2="18"/>')],
468
- ['align-justify', svgWrap('<line x1="21" y1="6" x2="3" y2="6"/><line x1="21" y1="12" x2="3" y2="12"/><line x1="21" y1="18" x2="3" y2="18"/>')],
469
- // Lists
470
- ['list-ul', svgWrap('<line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4" cy="6" r="1" fill="currentColor" stroke="none"/><circle cx="4" cy="12" r="1" fill="currentColor" stroke="none"/><circle cx="4" cy="18" r="1" fill="currentColor" stroke="none"/>')],
471
- ['list-ol', svgWrap('<line x1="10" y1="6" x2="21" y2="6"/><line x1="10" y1="12" x2="21" y2="12"/><line x1="10" y1="18" x2="21" y2="18"/><path d="M4 6h1V3"/><path d="M4 10h2l-2 2h2"/><path d="M4 16.5A1.5 1.5 0 0 1 5.5 15a1.5 1.5 0 0 1 0 3H4"/>')],
472
- ['indent', svgWrap('<polyline points="3 8 7 12 3 16"/><line x1="21" y1="12" x2="11" y2="12"/><line x1="21" y1="6" x2="11" y2="6"/><line x1="21" y1="18" x2="11" y2="18"/>')],
473
- ['outdent', svgWrap('<polyline points="7 8 3 12 7 16"/><line x1="21" y1="12" x2="11" y2="12"/><line x1="21" y1="6" x2="11" y2="6"/><line x1="21" y1="18" x2="11" y2="18"/>')],
474
- // History
475
- ['undo', svgWrap('<path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>')],
476
- ['redo', svgWrap('<path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13"/>')],
477
- // Insert
478
- ['minus', svgWrap('<line x1="5" y1="12" x2="19" y2="12"/>')],
479
- ['link', svgWrap('<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>')],
480
- ['image', svgWrap('<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/>')],
481
- ['video', svgWrap('<polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/>')],
482
- ['table', svgWrap('<rect x="3" y="3" width="18" height="18" rx="1"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="3" y1="15" x2="21" y2="15"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="15" y1="3" x2="15" y2="21"/>')],
483
- ['emoji', svgWrap('<circle cx="12" cy="12" r="10"/><path d="M8.5 14.5s1.5 2.5 3.5 2.5 3.5-2.5 3.5-2.5"/><circle cx="9" cy="9" r="1.5" fill="currentColor" stroke="none"/><circle cx="15" cy="9" r="1.5" fill="currentColor" stroke="none"/>')],
484
- ['icon', svgWrap('<circle cx="8" cy="8" r="3"/><circle cx="16" cy="8" r="3"/><rect x="5" y="13" width="6" height="6" rx="1"/><rect x="13" y="13" width="6" height="6" rx="1"/>')],
485
- // View
486
- ['code', svgWrap('<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>')],
487
- ['expand', svgWrap('<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"/>')],
488
- // Color pickers
489
- ['foreColor', svgWrap('<path d="M4 20L12 4L20 20"/><line x1="7.5" y1="14" x2="16.5" y2="14"/>')],
490
- ['backColor', svgWrap('<path d="M3 21v-4l9-9 4 4-9 9z"/><path d="M12 8l4 4"/>')],
491
- ['keyboard', svgWrap('<rect x="2" y="6" width="20" height="12" rx="2"/><line x1="6" y1="10" x2="6" y2="10" stroke-width="2.5"/><line x1="10" y1="10" x2="10" y2="10" stroke-width="2.5"/><line x1="14" y1="10" x2="14" y2="10" stroke-width="2.5"/><line x1="18" y1="10" x2="18" y2="10" stroke-width="2.5"/><line x1="8" y1="14" x2="16" y2="14" stroke-width="2"/>')],
492
- ['caption', svgWrap('<rect x="3" y="3" width="18" height="11" rx="2"/><line x1="6" y1="18" x2="18" y2="18"/><line x1="9" y1="21" x2="15" y2="21"/>')],
493
- ['remove-format', svgWrap('<path d="m7 21-4.3-4.3c-1-1-1-2.5 0-3.4l9.6-9.6c1-1 2.5-1 3.4 0l5.6 5.6c1 1 1 2.5 0 3.4L13 21"/><path d="M22 21H7"/><path d="m5 11 9 9"/>')],
494
- ['direction', svgWrap('<path d="M12 20V4"/><path d="m9 7-3 3 3 3"/><path d="M4 10h8"/><path d="m15 7 3 3-3 3"/><path d="M20 10h-8"/>')],
495
- ['search', svgWrap('<circle cx="11" cy="11" r="7"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>')],
496
- ['find-replace', svgWrap('<circle cx="10" cy="10" r="6"/><line x1="18" y1="18" x2="14.35" y2="14.35"/><path d="M16 19h6"/><path d="M19 16v6"/>')],
497
- ['inline-code', svgWrap('<path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1"/><path d="M16 3h1a2 2 0 0 1 2 2v5c0 1.1.9 2 2 2a2 2 0 0 1-2 2v5a2 2 0 0 1-2 2h-1"/>')],
498
- ['checklist', svgWrap('<rect x="3" y="4" width="5" height="5" rx="1"/><path d="m4 6.5 1 1 2-2"/><rect x="3" y="13" width="5" height="5" rx="1"/><line x1="10" y1="6.5" x2="21" y2="6.5"/><line x1="10" y1="15.5" x2="21" y2="15.5"/>')],
499
- ['print', svgWrap('<path d="M6 9V2h12v7"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/>')],
500
- ]);
501
-
502
549
  const faPrefix = this.options.fontAwesomeClass || 'fas';
503
- const faMap = new Map([
504
- ['bold', 'fa-bold'],
505
- ['italic', 'fa-italic'],
506
- ['underline', 'fa-underline'],
507
- ['strikethrough', 'fa-strikethrough'],
508
- ['superscript', 'fa-superscript'],
509
- ['subscript', 'fa-subscript'],
510
- ['align-left', 'fa-align-left'],
511
- ['align-center', 'fa-align-center'],
512
- ['align-right', 'fa-align-right'],
513
- ['align-justify', 'fa-align-justify'],
514
- ['list-ul', 'fa-list-ul'],
515
- ['list-ol', 'fa-list-ol'],
516
- ['indent', 'fa-indent'],
517
- ['outdent', 'fa-outdent'],
518
- ['undo', 'fa-rotate-left'],
519
- ['redo', 'fa-rotate-right'],
520
- ['minus', 'fa-minus'],
521
- ['link', 'fa-link'],
522
- ['image', 'fa-image'],
523
- ['code', 'fa-code'],
524
- ['expand', 'fa-expand'],
525
- ['emoji', 'fa-face-smile'],
526
- ['icon', 'fa-icons'],
527
- ['foreColor', 'fa-font'],
528
- ['backColor', 'fa-highlighter'],
529
- ['keyboard', 'fa-keyboard'],
530
- ['remove-format', 'fa-remove-format'],
531
- ['direction', 'fa-arrow-right-arrow-left'],
532
- ['search', 'fa-magnifying-glass'],
533
- ['find-replace', 'fa-magnifying-glass-plus'],
534
- ['inline-code', 'fa-code'],
535
- ['checklist', 'fa-list-check'],
536
- ['print', 'fa-print'],
537
- ]);
538
-
539
550
  const useFaNow = this._faReady;
540
551
  if (useFaNow) {
541
- const faName = faMap.get(btnDef.icon) || faMap.get(btnDef.name) || null;
552
+ const faName = _FA_MAP.get(btnDef.icon) || _FA_MAP.get(btnDef.name) || null;
542
553
  if (faName) {
543
554
  btn.innerHTML = `<i class="${faPrefix} ${faName}" aria-hidden="true"></i>`;
544
- } else if (svgMap.has(btnDef.icon)) {
545
- btn.innerHTML = svgMap.get(btnDef.icon);
555
+ } else if (_SVG_MAP.has(btnDef.icon)) {
556
+ btn.innerHTML = _SVG_MAP.get(btnDef.icon);
546
557
  } else {
547
558
  btn.textContent = btnDef.icon || btnDef.name;
548
559
  }
549
560
  } else {
550
561
  // FontAwesome absent: use SVG fallback when available
551
- if (svgMap.has(btnDef.icon)) {
552
- btn.innerHTML = svgMap.get(btnDef.icon);
553
- } else if (svgMap.has(btnDef.name)) {
554
- btn.innerHTML = svgMap.get(btnDef.name);
562
+ if (_SVG_MAP.has(btnDef.icon)) {
563
+ btn.innerHTML = _SVG_MAP.get(btnDef.icon);
564
+ } else if (_SVG_MAP.has(btnDef.name)) {
565
+ btn.innerHTML = _SVG_MAP.get(btnDef.name);
555
566
  } else {
556
567
  btn.textContent = btnDef.icon || btnDef.name;
557
568
  }
@@ -969,6 +969,55 @@
969
969
  .an-resize-w { top: calc(50% - 4px); left: -4px; cursor: w-resize; }
970
970
  }
971
971
 
972
+ // ---------------------------------------------------------------------------
973
+ // Image crop overlay
974
+ // ---------------------------------------------------------------------------
975
+
976
+ // The scrim and crop box are appended to <body> and positioned fixed, so they
977
+ // live outside .an-container. All styles are standalone (no SCSS nesting).
978
+
979
+ .an-crop-scrim {
980
+ // Inline styles handle position/z-index/background for performance;
981
+ // clip-path is updated by JS every frame so we only declare the transition.
982
+ transition: clip-path 0s; // disable transition — updated synchronously on drag
983
+ }
984
+
985
+ .an-crop-box {
986
+ // Core layout & interaction via inline styles in JS (position changes every drag frame).
987
+ // Only declare the animated ring so it stays in sync with $an-primary.
988
+ outline: none !important;
989
+
990
+ // Subtle inner shadow that frames the "live" area
991
+ box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.45),
992
+ inset 0 0 0 1px rgba(255, 255, 255, 0.25);
993
+ }
994
+
995
+ .an-crop-handle {
996
+ // Inline styles handle size/position; declare hover highlight only.
997
+ &:hover, &:active {
998
+ background: #74affa !important; // $an-primary lightened ~14%
999
+ }
1000
+ }
1001
+
1002
+ .an-crop-toolbar {
1003
+ // Inline styles handle position; add a subtle entrance animation.
1004
+ animation: an-crop-toolbar-in 0.12s ease;
1005
+ }
1006
+
1007
+ @keyframes an-crop-toolbar-in {
1008
+ from { opacity: 0; transform: translateY(4px); }
1009
+ to { opacity: 1; transform: translateY(0); }
1010
+ }
1011
+
1012
+ // Hide canvas-based crop overlay when printing
1013
+ @media print {
1014
+ .an-crop-scrim,
1015
+ .an-crop-box,
1016
+ .an-crop-toolbar {
1017
+ display: none !important;
1018
+ }
1019
+ }
1020
+
972
1021
  // ---------------------------------------------------------------------------
973
1022
  // Video resize overlay
974
1023
  // ---------------------------------------------------------------------------
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AutumnNote – TypeScript declarations
3
- * @version 1.0.0
3
+ * @version 1.0.4
4
4
  */
5
5
 
6
6
  // ---------------------------------------------------------------------------
@@ -21,7 +21,7 @@ export interface AsnOptions {
21
21
  /** Show the resize handle in the statusbar. */
22
22
  resizable?: boolean;
23
23
  /** Toolbar button group configuration. */
24
- toolbar?: Array<Array<ButtonDef | DropdownDef>>;
24
+ toolbar?: Array<Array<ToolbarItemDef>>;
25
25
  /** Use Bootstrap button classes on toolbar buttons. */
26
26
  useBootstrap?: boolean;
27
27
  /** CSS class(es) applied to Bootstrap toolbar buttons. */
@@ -127,6 +127,34 @@ export interface ButtonDef {
127
127
  className?: string;
128
128
  }
129
129
 
130
+ export interface GridButtonDef {
131
+ /** Unique identifier used in toolbar config arrays. */
132
+ name: string;
133
+ /** Discriminator for table grid picker buttons. */
134
+ type: 'grid';
135
+ /** Icon identifier (maps to SVG or Font Awesome class). */
136
+ icon: string;
137
+ /** Tooltip text. */
138
+ tooltip: string;
139
+ /** Action executed when a grid size is selected. */
140
+ action: (context: Context, rows: number, cols: number) => void;
141
+ }
142
+
143
+ export interface ColorPickerDef {
144
+ /** Unique identifier used in toolbar config arrays. */
145
+ name: string;
146
+ /** Discriminator for split color picker buttons. */
147
+ type: 'colorpicker';
148
+ /** Icon identifier (maps to SVG or Font Awesome class). */
149
+ icon: string;
150
+ /** Tooltip text. */
151
+ tooltip: string;
152
+ /** Default color shown in the picker. */
153
+ defaultColor: string;
154
+ /** Action executed when a color is selected/applied. */
155
+ action: (context: Context, color: string) => void;
156
+ }
157
+
130
158
  export interface DropdownDef {
131
159
  /** Unique identifier. */
132
160
  name: string;
@@ -135,13 +163,31 @@ export interface DropdownDef {
135
163
  /** Tooltip text. */
136
164
  tooltip: string;
137
165
  /** Static item list (may be overridden at render time from options). */
138
- items?: string[];
166
+ items?: Array<string | { value: string; label: string }>;
139
167
  /** Action executed when a value is selected. */
140
168
  action: (context: Context, value: string) => void;
141
169
  /** Returns the current value to show as selected. */
142
170
  getValue?: (context: Context) => string;
143
171
  }
144
172
 
173
+ export type ToolbarItemDef = ButtonDef | DropdownDef | GridButtonDef | ColorPickerDef;
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Entry-point re-exports
177
+ // ---------------------------------------------------------------------------
178
+
179
+ /** Runtime default options object (same export as `src/js/index.js`). */
180
+ export declare const defaultOptions: Required<AsnOptions>;
181
+
182
+ // Re-export core utility modules exposed by the package entry-point.
183
+ export * from '../src/js/core/dom.js';
184
+ export * from '../src/js/core/range.js';
185
+ export * from '../src/js/core/func.js';
186
+ export * from '../src/js/core/key.js';
187
+ export * from '../src/js/core/lists.js';
188
+ export * from '../src/js/core/env.js';
189
+ export * from '../src/js/core/sanitise.js';
190
+
145
191
  // ---------------------------------------------------------------------------
146
192
  // Context — per-instance editor hub
147
193
  // ---------------------------------------------------------------------------
@@ -263,8 +309,7 @@ export declare const hrBtn: ButtonDef;
263
309
  export declare const linkBtn: ButtonDef;
264
310
  export declare const imageBtn: ButtonDef;
265
311
  export declare const videoBtn: ButtonDef;
266
- export declare const tableBtn: ButtonDef;
267
- export declare const codeBlockBtn: ButtonDef;
312
+ export declare const tableBtn: GridButtonDef;
268
313
  export declare const codeviewBtn: ButtonDef;
269
314
  export declare const fullscreenBtn: ButtonDef;
270
315
  export declare const emojiBtn: ButtonDef;
@@ -272,16 +317,18 @@ export declare const iconBtn: ButtonDef;
272
317
  export declare const shortcutsBtn: ButtonDef;
273
318
  export declare const findBtn: ButtonDef;
274
319
  export declare const findReplaceBtn: ButtonDef;
320
+ export declare const inlineCodeBtn: ButtonDef;
321
+ export declare const checklistBtn: ButtonDef;
322
+ export declare const printBtn: ButtonDef;
275
323
  export declare const removeFormatBtn: ButtonDef;
276
324
  export declare const directionBtn: ButtonDef;
277
325
  export declare const fontFamilyBtn: DropdownDef;
278
326
  export declare const fontSizeBtn: DropdownDef;
279
- export declare const headingBtn: DropdownDef;
280
327
  export declare const lineHeightBtn: DropdownDef;
281
328
  export declare const paragraphStyleBtn: DropdownDef;
282
- export declare const foreColorBtn: DropdownDef;
283
- export declare const backColorBtn: DropdownDef;
284
- export declare const defaultToolbar: Array<Array<ButtonDef | DropdownDef>>;
329
+ export declare const foreColorBtn: ColorPickerDef;
330
+ export declare const backColorBtn: ColorPickerDef;
331
+ export declare const defaultToolbar: Array<Array<ToolbarItemDef>>;
285
332
 
286
333
  // ---------------------------------------------------------------------------
287
334
  // Main AutumnNote API