autumnnote 1.0.5 → 1.0.7
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 +2 -2
- package/dist/autumnnote.css +1769 -1
- package/dist/autumnnote.es.js +11885 -6463
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +11579 -84
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +16 -8
- package/src/js/Context.js +8 -6
- package/src/js/core/sanitise.js +20 -2
- package/src/js/editing/Style.js +120 -7
- package/src/js/editing/Typing.js +28 -3
- package/src/js/module/Buttons.js +5 -4
- package/src/js/module/Clipboard.js +25 -20
- package/src/js/module/CodeTooltip.js +10 -6
- package/src/js/module/ContextMenu.js +146 -8
- package/src/js/module/Editor.js +61 -0
- package/src/js/module/FindReplace.js +21 -7
- package/src/js/module/ImageCropOverlay.js +2 -2
- package/src/js/module/ImageDialog.js +17 -2
- package/src/js/module/ImageResizer.js +3 -1
- package/src/js/module/ImageTooltip.js +12 -1
- package/src/js/module/LinkTooltip.js +4 -0
- package/src/js/module/Placeholder.js +2 -1
- package/src/js/module/TableTooltip.js +62 -25
- package/src/js/module/Toolbar.js +129 -100
- 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 +146 -9
|
@@ -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
|
|
|
@@ -104,6 +105,7 @@ export class TableTooltip {
|
|
|
104
105
|
|
|
105
106
|
this._disposers.push(
|
|
106
107
|
on(editable, 'mouseover', (e) => {
|
|
108
|
+
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
107
109
|
const table = e.target.closest('table');
|
|
108
110
|
if (table && editable.contains(table)) {
|
|
109
111
|
const cell = e.target.closest('td, th');
|
|
@@ -168,6 +170,10 @@ export class TableTooltip {
|
|
|
168
170
|
|
|
169
171
|
const onEditorMove = (e) => {
|
|
170
172
|
if (_resizing) return;
|
|
173
|
+
if (this.context.layoutInfo.container.classList.contains('an-disabled')) {
|
|
174
|
+
clearHover();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
171
177
|
// Throttle to one check per animation frame — getBoundingClientRect forces reflow
|
|
172
178
|
if (_rafEditorMove !== null) return;
|
|
173
179
|
const target = e.target;
|
|
@@ -194,6 +200,7 @@ export class TableTooltip {
|
|
|
194
200
|
};
|
|
195
201
|
|
|
196
202
|
const onEditorDown = (e) => {
|
|
203
|
+
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
197
204
|
if (!_nearCell || !_nearEdge) return;
|
|
198
205
|
_resizing = true;
|
|
199
206
|
_edge = _nearEdge;
|
|
@@ -320,8 +327,9 @@ export class TableTooltip {
|
|
|
320
327
|
el.appendChild(this._sep());
|
|
321
328
|
|
|
322
329
|
// Resize
|
|
323
|
-
el.appendChild(this._makeBtn(ICONS.colWidth,
|
|
324
|
-
el.appendChild(this._makeBtn(ICONS.rowHeight,
|
|
330
|
+
el.appendChild(this._makeBtn(ICONS.colWidth, 'Column Width', () => this._openSizePopover('col')));
|
|
331
|
+
el.appendChild(this._makeBtn(ICONS.rowHeight, 'Row Height', () => this._openSizePopover('row')));
|
|
332
|
+
el.appendChild(this._makeBtn(ICONS.tableBorder,'Table Border Width',() => this._openSizePopover('border')));
|
|
325
333
|
|
|
326
334
|
el.appendChild(this._sep());
|
|
327
335
|
|
|
@@ -641,30 +649,59 @@ export class TableTooltip {
|
|
|
641
649
|
_openSizePopover(type) {
|
|
642
650
|
const cell = this._getCell();
|
|
643
651
|
if (!cell || !this._sizePopover) return;
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
652
|
+
|
|
653
|
+
if (type === 'border') {
|
|
654
|
+
const table = cell.closest('table');
|
|
655
|
+
if (!table) return;
|
|
656
|
+
// Read current inline border-width, fall back to computed value, then default 1px
|
|
657
|
+
const firstCell = table.querySelector('td, th');
|
|
658
|
+
const currentPx = firstCell
|
|
659
|
+
? (parseInt(firstCell.style.borderWidth, 10) ||
|
|
660
|
+
parseInt(window.getComputedStyle(firstCell).borderWidth, 10) || 1)
|
|
661
|
+
: 1;
|
|
662
|
+
this._sizeTitleEl.textContent = 'Table Border Width (px)';
|
|
663
|
+
this._sizeInputEl.min = '0';
|
|
664
|
+
this._sizeInputEl.max = '10';
|
|
665
|
+
this._sizeInputEl.value = currentPx;
|
|
666
|
+
this._sizeApply = (val) => {
|
|
667
|
+
// Apply border-width to every cell; with border-collapse:collapse this
|
|
668
|
+
// controls all grid lines including the outer table border.
|
|
669
|
+
// Setting only borderWidth preserves the existing border-color from CSS.
|
|
670
|
+
const cells = Array.from(table.querySelectorAll('td, th'));
|
|
671
|
+
if (val === 0) {
|
|
672
|
+
cells.forEach(c => { c.style.borderWidth = '0'; c.style.borderStyle = 'none'; });
|
|
673
|
+
} else {
|
|
674
|
+
cells.forEach(c => { c.style.borderWidth = `${val}px`; c.style.borderStyle = 'solid'; });
|
|
664
675
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
676
|
+
this.context.invoke('editor.afterCommand');
|
|
677
|
+
};
|
|
678
|
+
} else {
|
|
679
|
+
const isCol = type === 'col';
|
|
680
|
+
this._sizeTitleEl.textContent = isCol ? 'Column Width (px)' : 'Row Height (px)';
|
|
681
|
+
this._sizeInputEl.min = '1';
|
|
682
|
+
this._sizeInputEl.max = '2000';
|
|
683
|
+
this._sizeInputEl.value = isCol
|
|
684
|
+
? (cell.offsetWidth || 120)
|
|
685
|
+
: (cell.closest('tr') ? (cell.closest('tr').offsetHeight || 40) : 40);
|
|
686
|
+
this._sizeApply = (val) => {
|
|
687
|
+
if (isCol) {
|
|
688
|
+
const table = cell.closest('table');
|
|
689
|
+
const visualColIdx = getVisualColIndex(cell);
|
|
690
|
+
const rows = Array.from(table.querySelectorAll('tr'));
|
|
691
|
+
// Batch reads, then writes
|
|
692
|
+
const cells = rows.map(r => getCellAtVisualCol(r, visualColIdx));
|
|
693
|
+
cells.forEach(c => {
|
|
694
|
+
if (c) { c.style.width = `${val}px`; c.style.minWidth = `${val}px`; }
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
const row = cell.closest('tr');
|
|
698
|
+
if (row) {
|
|
699
|
+
for (const c of row.cells) { c.style.height = `${val}px`; c.style.minHeight = `${val}px`; }
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
this.context.invoke('editor.afterCommand');
|
|
703
|
+
};
|
|
704
|
+
}
|
|
668
705
|
|
|
669
706
|
this._sizePopover.style.display = 'block';
|
|
670
707
|
requestAnimationFrame(() => {
|
package/src/js/module/Toolbar.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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
|
|
|
@@ -415,16 +513,34 @@ export class Toolbar {
|
|
|
415
513
|
select.appendChild(opt);
|
|
416
514
|
});
|
|
417
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
|
+
|
|
418
527
|
const disposer = on(select, 'change', (e) => {
|
|
419
|
-
const value = e.target.value;
|
|
420
|
-
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];
|
|
421
530
|
if (!value || selectedOpt.disabled) return;
|
|
422
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
|
+
}
|
|
423
539
|
def.action(this.context, value);
|
|
424
540
|
this.context.invoke('editor.afterCommand');
|
|
425
541
|
});
|
|
426
542
|
|
|
427
|
-
this._disposers.push(disposer);
|
|
543
|
+
this._disposers.push(dMousedown, disposer);
|
|
428
544
|
return select;
|
|
429
545
|
}
|
|
430
546
|
|
|
@@ -448,110 +564,23 @@ export class Toolbar {
|
|
|
448
564
|
});
|
|
449
565
|
|
|
450
566
|
// 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
567
|
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
568
|
const useFaNow = this._faReady;
|
|
540
569
|
if (useFaNow) {
|
|
541
|
-
const faName =
|
|
570
|
+
const faName = _FA_MAP.get(btnDef.icon) || _FA_MAP.get(btnDef.name) || null;
|
|
542
571
|
if (faName) {
|
|
543
572
|
btn.innerHTML = `<i class="${faPrefix} ${faName}" aria-hidden="true"></i>`;
|
|
544
|
-
} else if (
|
|
545
|
-
btn.innerHTML =
|
|
573
|
+
} else if (_SVG_MAP.has(btnDef.icon)) {
|
|
574
|
+
btn.innerHTML = _SVG_MAP.get(btnDef.icon);
|
|
546
575
|
} else {
|
|
547
576
|
btn.textContent = btnDef.icon || btnDef.name;
|
|
548
577
|
}
|
|
549
578
|
} else {
|
|
550
579
|
// FontAwesome absent: use SVG fallback when available
|
|
551
|
-
if (
|
|
552
|
-
btn.innerHTML =
|
|
553
|
-
} else if (
|
|
554
|
-
btn.innerHTML =
|
|
580
|
+
if (_SVG_MAP.has(btnDef.icon)) {
|
|
581
|
+
btn.innerHTML = _SVG_MAP.get(btnDef.icon);
|
|
582
|
+
} else if (_SVG_MAP.has(btnDef.name)) {
|
|
583
|
+
btn.innerHTML = _SVG_MAP.get(btnDef.name);
|
|
555
584
|
} else {
|
|
556
585
|
btn.textContent = btnDef.icon || btnDef.name;
|
|
557
586
|
}
|
|
@@ -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
|