autumnnote 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/autumnnote.css +324 -2
- package/dist/autumnnote.es.js +638 -227
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +637 -226
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/Context.js +8 -3
- package/src/js/core/detectLang.js +98 -0
- package/src/js/core/dom.js +62 -6
- package/src/js/core/markdown.js +14 -13
- package/src/js/core/range.js +2 -2
- package/src/js/editing/History.js +6 -6
- package/src/js/editing/Style.js +19 -19
- package/src/js/editing/Table.js +4 -6
- package/src/js/editing/Typing.js +6 -6
- package/src/js/i18n/en.js +2 -0
- package/src/js/i18n/vi.js +2 -0
- package/src/js/index.js +3 -2
- package/src/js/module/BubbleToolbar.js +30 -13
- package/src/js/module/Buttons.js +16 -14
- package/src/js/module/Clipboard.js +5 -5
- package/src/js/module/CodeTooltip.js +42 -16
- package/src/js/module/Codeview.js +2 -2
- package/src/js/module/ContextMenu.js +12 -12
- package/src/js/module/Editor.js +64 -12
- package/src/js/module/EmojiDialog.js +19 -13
- package/src/js/module/FindReplace.js +85 -59
- package/src/js/module/Fullscreen.js +1 -1
- package/src/js/module/IconDialog.js +26 -20
- package/src/js/module/ImageCropOverlay.js +6 -6
- package/src/js/module/ImageDialog.js +18 -12
- package/src/js/module/ImageResizer.js +1 -1
- package/src/js/module/ImageTooltip.js +8 -4
- package/src/js/module/LinkDialog.js +18 -12
- package/src/js/module/LinkTooltip.js +5 -2
- package/src/js/module/Mention.js +3 -3
- package/src/js/module/Statusbar.js +2 -2
- package/src/js/module/TableTooltip.js +180 -18
- package/src/js/module/Toolbar.js +16 -15
- package/src/js/module/VideoDialog.js +8 -2
- package/src/js/module/VideoResizer.js +3 -3
- package/src/js/module/VideoTooltip.js +11 -6
- package/src/js/renderer.js +4 -2
- package/src/js/settings.js +53 -36
- package/src/styles/autumnnote.scss +332 -4
|
@@ -39,24 +39,28 @@ export class ImageTooltip {
|
|
|
39
39
|
this._disposers.push(
|
|
40
40
|
on(editable, 'mouseover', (e) => {
|
|
41
41
|
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
42
|
-
const img = e.target
|
|
42
|
+
const img = /** @type {Element} */ (e.target)?.closest('img');
|
|
43
43
|
// Skip when the image is inside a link — LinkTooltip takes priority there
|
|
44
44
|
if (img && editable.contains(img) && !img.closest('a[href]')) {
|
|
45
45
|
this._scheduleShow(img);
|
|
46
46
|
}
|
|
47
47
|
}, { passive: true }),
|
|
48
48
|
on(editable, 'mouseout', (e) => {
|
|
49
|
-
const to = e.relatedTarget;
|
|
49
|
+
const to = /** @type {Node|null} */ (/** @type {MouseEvent} */ (e).relatedTarget);
|
|
50
50
|
if (!to || (!editable.contains(to) && !this._el.contains(to))) {
|
|
51
51
|
this._scheduleHide();
|
|
52
52
|
}
|
|
53
53
|
}, { passive: true }),
|
|
54
54
|
// Hide when image is deselected by clicking elsewhere
|
|
55
55
|
on(document, 'click', (e) => {
|
|
56
|
-
|
|
56
|
+
const et = /** @type {Node} */ (e.target);
|
|
57
|
+
if (this._activeImg && !this._activeImg.contains(et) && !this._el.contains(et)) {
|
|
57
58
|
this._hide();
|
|
58
59
|
}
|
|
59
60
|
}),
|
|
61
|
+
// Hide when the page scrolls or resizes — the tooltip position becomes stale
|
|
62
|
+
on(window, 'scroll', () => this._hide(), { passive: true }),
|
|
63
|
+
on(window, 'resize', () => this._hide(), { passive: true }),
|
|
60
64
|
);
|
|
61
65
|
|
|
62
66
|
return this;
|
|
@@ -181,7 +185,7 @@ export class ImageTooltip {
|
|
|
181
185
|
this._hideTimer = setTimeout(() => this._hide(), HIDE_DELAY);
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
_show(
|
|
188
|
+
_show(_img) {
|
|
185
189
|
this._el.style.display = 'flex';
|
|
186
190
|
// Defer positioning: offsetWidth on a newly-visible element forces layout
|
|
187
191
|
requestAnimationFrame(() => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Inspired by Summernote's LinkDialog — rewritten without jQuery
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createElement, on, trapFocus } from '../core/dom.js';
|
|
6
|
+
import { createElement, on, trapFocus, makeDraggable } from '../core/dom.js';
|
|
7
7
|
import { withSavedRange } from '../core/range.js';
|
|
8
8
|
|
|
9
9
|
export class LinkDialog {
|
|
@@ -63,42 +63,47 @@ export class LinkDialog {
|
|
|
63
63
|
const overlay = createElement('div', { class: 'an-dialog-overlay', role: 'dialog', 'aria-modal': 'true', 'aria-label': L.ariaLabel });
|
|
64
64
|
const box = createElement('div', { class: 'an-dialog-box' });
|
|
65
65
|
|
|
66
|
+
const header = createElement('div', { class: 'an-dialog-header' });
|
|
67
|
+
const iconEl = createElement('span', { class: 'an-dialog-icon' });
|
|
68
|
+
iconEl.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><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"/></svg>`;
|
|
66
69
|
const title = createElement('h3', { class: 'an-dialog-title' });
|
|
67
70
|
title.textContent = L.title;
|
|
71
|
+
header.appendChild(iconEl);
|
|
72
|
+
header.appendChild(title);
|
|
68
73
|
|
|
69
74
|
// URL field
|
|
70
75
|
const urlLabel = createElement('label', { class: 'an-label' });
|
|
71
76
|
urlLabel.textContent = L.url;
|
|
72
|
-
const urlInput = createElement('input', {
|
|
77
|
+
const urlInput = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
73
78
|
type: 'url',
|
|
74
79
|
class: 'an-input',
|
|
75
80
|
placeholder: L.urlPlaceholder,
|
|
76
81
|
id: 'an-link-url',
|
|
77
82
|
name: 'url',
|
|
78
83
|
autocomplete: 'off',
|
|
79
|
-
});
|
|
84
|
+
}));
|
|
80
85
|
this._urlInput = urlInput;
|
|
81
86
|
|
|
82
87
|
// Text field
|
|
83
88
|
const textLabel = createElement('label', { class: 'an-label' });
|
|
84
89
|
textLabel.textContent = L.displayText;
|
|
85
|
-
const textInput = createElement('input', {
|
|
90
|
+
const textInput = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
86
91
|
type: 'text',
|
|
87
92
|
class: 'an-input',
|
|
88
93
|
placeholder: L.textPlaceholder,
|
|
89
94
|
id: 'an-link-text',
|
|
90
95
|
name: 'linkText',
|
|
91
96
|
autocomplete: 'off',
|
|
92
|
-
});
|
|
97
|
+
}));
|
|
93
98
|
this._textInput = textInput;
|
|
94
99
|
|
|
95
100
|
// Open in new tab
|
|
96
101
|
const tabLabel = createElement('label', { class: 'an-label an-label-inline' });
|
|
97
|
-
const tabCheckbox = createElement('input', {
|
|
102
|
+
const tabCheckbox = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
98
103
|
type: 'checkbox',
|
|
99
104
|
id: 'an-link-newtab',
|
|
100
105
|
name: 'openInNewTab',
|
|
101
|
-
});
|
|
106
|
+
}));
|
|
102
107
|
this._tabCheckbox = tabCheckbox;
|
|
103
108
|
tabLabel.appendChild(tabCheckbox);
|
|
104
109
|
tabLabel.appendChild(document.createTextNode(' ' + L.openInNewTab));
|
|
@@ -112,15 +117,16 @@ export class LinkDialog {
|
|
|
112
117
|
btnRow.appendChild(insertBtn);
|
|
113
118
|
btnRow.appendChild(cancelBtn);
|
|
114
119
|
|
|
115
|
-
box.append(
|
|
120
|
+
box.append(header, urlLabel, urlInput, textLabel, textInput, tabLabel, btnRow);
|
|
116
121
|
overlay.appendChild(box);
|
|
122
|
+
makeDraggable(header, box);
|
|
117
123
|
|
|
118
124
|
// Events
|
|
119
125
|
const d1 = on(insertBtn, 'click', () => this._onInsert());
|
|
120
126
|
const d2 = on(cancelBtn, 'click', () => this._close());
|
|
121
127
|
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(); } });
|
|
123
|
-
const d5 = on(textInput, 'keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); this._onInsert(); } });
|
|
128
|
+
const d4 = on(urlInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
|
|
129
|
+
const d5 = on(textInput, 'keydown', (e) => { if (/** @type {KeyboardEvent} */ (e).key === 'Enter') { e.preventDefault(); this._onInsert(); } });
|
|
124
130
|
this._disposers.push(d1, d2, d3, d4, d5);
|
|
125
131
|
|
|
126
132
|
return overlay;
|
|
@@ -142,9 +148,9 @@ export class LinkDialog {
|
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
if (anchor) {
|
|
145
|
-
this._urlInput.value = anchor.getAttribute('href') || '';
|
|
151
|
+
this._urlInput.value = /** @type {Element} */ (anchor).getAttribute('href') || '';
|
|
146
152
|
this._textInput.value = anchor.textContent || '';
|
|
147
|
-
this._tabCheckbox.checked = anchor.getAttribute('target') === '_blank';
|
|
153
|
+
this._tabCheckbox.checked = /** @type {Element} */ (anchor).getAttribute('target') === '_blank';
|
|
148
154
|
} else {
|
|
149
155
|
this._urlInput.value = '';
|
|
150
156
|
this._textInput.value = sel ? sel.toString() : '';
|
|
@@ -33,18 +33,21 @@ export class LinkTooltip {
|
|
|
33
33
|
this._disposers.push(
|
|
34
34
|
// Detect when pointer enters a link
|
|
35
35
|
on(editable, 'mouseover', (e) => {
|
|
36
|
-
const anchor = e.target
|
|
36
|
+
const anchor = /** @type {Element} */ (e.target)?.closest('a[href]');
|
|
37
37
|
if (anchor && editable.contains(anchor)) {
|
|
38
38
|
this._scheduleShow(anchor);
|
|
39
39
|
}
|
|
40
40
|
}),
|
|
41
41
|
// Detect when pointer leaves the editable area entirely
|
|
42
42
|
on(editable, 'mouseout', (e) => {
|
|
43
|
-
const to = e.relatedTarget;
|
|
43
|
+
const to = /** @type {Node|null} */ (/** @type {MouseEvent} */ (e).relatedTarget);
|
|
44
44
|
if (!to || (!editable.contains(to) && !this._el.contains(to))) {
|
|
45
45
|
this._scheduleHide();
|
|
46
46
|
}
|
|
47
47
|
}),
|
|
48
|
+
// Hide when the page scrolls or resizes — the tooltip position becomes stale
|
|
49
|
+
on(window, 'scroll', () => this._hide(), { passive: true }),
|
|
50
|
+
on(window, 'resize', () => this._hide(), { passive: true }),
|
|
48
51
|
);
|
|
49
52
|
|
|
50
53
|
return this;
|
package/src/js/module/Mention.js
CHANGED
|
@@ -96,11 +96,11 @@ export class Mention {
|
|
|
96
96
|
// Event delegation: single listeners on the container instead of per-item
|
|
97
97
|
el.addEventListener('mousedown', (e) => e.preventDefault());
|
|
98
98
|
el.addEventListener('click', (e) => {
|
|
99
|
-
const item = e.target
|
|
99
|
+
const item = /** @type {HTMLElement} */ (/** @type {Element} */ (e.target)?.closest('.an-mention-item'));
|
|
100
100
|
if (item) this._select(+item.dataset.index);
|
|
101
101
|
});
|
|
102
102
|
el.addEventListener('mousemove', (e) => {
|
|
103
|
-
const item = e.target
|
|
103
|
+
const item = /** @type {HTMLElement} */ (/** @type {Element} */ (e.target)?.closest('.an-mention-item'));
|
|
104
104
|
if (item) this._highlightItem(+item.dataset.index);
|
|
105
105
|
});
|
|
106
106
|
|
|
@@ -119,7 +119,7 @@ export class Mention {
|
|
|
119
119
|
const li = document.createElement('div');
|
|
120
120
|
li.className = 'an-mention-item';
|
|
121
121
|
li.setAttribute('role', 'option');
|
|
122
|
-
li.dataset.index = i;
|
|
122
|
+
li.dataset.index = String(i);
|
|
123
123
|
if (item.avatar) {
|
|
124
124
|
const img = document.createElement('img');
|
|
125
125
|
img.src = item.avatar;
|
|
@@ -136,7 +136,7 @@ export class Statusbar {
|
|
|
136
136
|
const MIN_EDITABLE = 40;
|
|
137
137
|
const fixedH = Array.from(containerEl.children)
|
|
138
138
|
.filter(child => !child.classList.contains('an-editable'))
|
|
139
|
-
.reduce((sum, child) => sum + child.offsetHeight, 0);
|
|
139
|
+
.reduce((sum, child) => sum + /** @type {HTMLElement} */ (child).offsetHeight, 0);
|
|
140
140
|
const trueMin = Math.max(this.options.minHeight || 100, fixedH + MIN_EDITABLE);
|
|
141
141
|
containerEl.style.height = `${Math.max(trueMin, startH + delta)}px`;
|
|
142
142
|
};
|
|
@@ -207,7 +207,7 @@ export class Statusbar {
|
|
|
207
207
|
_bindContentEvents() {
|
|
208
208
|
const editable = this.context.layoutInfo.editable;
|
|
209
209
|
const updateDebounced = debounce(() => this.update(), 200);
|
|
210
|
-
const d = on(editable, 'input', updateDebounced);
|
|
210
|
+
const d = on(editable, 'input', /** @type {EventListener} */ (updateDebounced));
|
|
211
211
|
this._disposers.push(d);
|
|
212
212
|
}
|
|
213
213
|
|
|
@@ -58,7 +58,7 @@ function getCellAfterVisualCol(row, visualIdx) {
|
|
|
58
58
|
if (vIdx > visualIdx) {
|
|
59
59
|
// next cell after the one that starts at / spans visualIdx
|
|
60
60
|
const next = c.nextElementSibling;
|
|
61
|
-
return (next && next.tagName === 'TD' || next && next.tagName === 'TH') ? next : null;
|
|
61
|
+
return (next && next.tagName === 'TD' || next && next.tagName === 'TH') ? /** @type {HTMLTableCellElement} */ (next) : null;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
return null;
|
|
@@ -115,8 +115,15 @@ const ICONS = {
|
|
|
115
115
|
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>`,
|
|
116
116
|
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>`,
|
|
117
117
|
selectCells: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z" fill="currentColor" opacity="0.15"/><path d="M4 4 L4 20 L9 15 L12 21 L14 20 L11 14 L17 14 Z"/></svg>`,
|
|
118
|
+
cellShade: `<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"><path d="M19 11L8.93 3.36a1 1 0 0 0-1.29.08L3.22 7.8a1 1 0 0 0-.07 1.29L11 20"/><path d="m5 14 5-5"/><path d="M22 22a2 2 0 0 1-2 2h-3a2 2 0 0 1-2-2c0-1.5 2.5-5 3.5-5s3.5 3.5 3.5 5z"/></svg>`,
|
|
118
119
|
};
|
|
119
120
|
|
|
121
|
+
const SHADE_PRESETS = [
|
|
122
|
+
'#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#efefef', '#ffffff',
|
|
123
|
+
'#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff', '#4a86e8', '#9900ff', '#ff00ff',
|
|
124
|
+
'#f4cccc', '#fce5cd', '#fff2cc', '#d9ead3', '#d0e0e3', '#c9daf8', '#d9d2e9', '#ead1dc',
|
|
125
|
+
];
|
|
126
|
+
|
|
120
127
|
export class TableTooltip {
|
|
121
128
|
/** @param {import('../Context.js').Context} context */
|
|
122
129
|
constructor(context) {
|
|
@@ -131,6 +138,10 @@ export class TableTooltip {
|
|
|
131
138
|
this._sizeApply = null;
|
|
132
139
|
this._sizeTitleEl = null;
|
|
133
140
|
this._sizeInputEl = null;
|
|
141
|
+
// Cell shade popover
|
|
142
|
+
this._shadePopover = null;
|
|
143
|
+
this._shadeTitleEl = null;
|
|
144
|
+
this._shadeColorStrip = null;
|
|
134
145
|
// Cell selection
|
|
135
146
|
this._selectMode = false;
|
|
136
147
|
this._selectedCells = [];
|
|
@@ -147,6 +158,9 @@ export class TableTooltip {
|
|
|
147
158
|
this._sizePopover = this._buildSizePopover();
|
|
148
159
|
document.body.appendChild(this._sizePopover);
|
|
149
160
|
|
|
161
|
+
this._shadePopover = this._buildCellShadePopover();
|
|
162
|
+
document.body.appendChild(this._shadePopover);
|
|
163
|
+
|
|
150
164
|
const editable = this.context.layoutInfo.editable;
|
|
151
165
|
this._editable = editable;
|
|
152
166
|
|
|
@@ -182,16 +196,19 @@ export class TableTooltip {
|
|
|
182
196
|
this._disposers.push(
|
|
183
197
|
on(editable, 'mouseover', (e) => {
|
|
184
198
|
if (this.context.layoutInfo.container.classList.contains('an-disabled')) return;
|
|
185
|
-
const table = e.target
|
|
199
|
+
const table = /** @type {Element} */ (e.target)?.closest('table');
|
|
186
200
|
if (table && editable.contains(table)) {
|
|
187
|
-
const cell = e.target
|
|
188
|
-
if (cell)
|
|
201
|
+
const cell = /** @type {Element} */ (e.target)?.closest('td, th');
|
|
202
|
+
if (cell) {
|
|
203
|
+
this._activeCell = cell;
|
|
204
|
+
this._syncShadeStrip();
|
|
205
|
+
}
|
|
189
206
|
this._scheduleShow(table);
|
|
190
207
|
}
|
|
191
208
|
}, { passive: true }),
|
|
192
209
|
on(editable, 'mouseout', (e) => {
|
|
193
210
|
if (this._selectMode) return; // keep tooltip alive during cell selection
|
|
194
|
-
const to = e.relatedTarget;
|
|
211
|
+
const to = /** @type {Node|null} */ (/** @type {MouseEvent} */ (e).relatedTarget);
|
|
195
212
|
if (!to || (
|
|
196
213
|
!editable.contains(to) &&
|
|
197
214
|
!this._el.contains(to) &&
|
|
@@ -201,14 +218,20 @@ export class TableTooltip {
|
|
|
201
218
|
}
|
|
202
219
|
}, { passive: true }),
|
|
203
220
|
on(document, 'click', (e) => {
|
|
204
|
-
|
|
221
|
+
const et = /** @type {Node} */ (e.target);
|
|
222
|
+
if (this._selectMode && this._activeTable && this._activeTable.contains(et)) return;
|
|
205
223
|
if (this._activeTable &&
|
|
206
|
-
!this._activeTable.contains(
|
|
207
|
-
!this._el.contains(
|
|
208
|
-
!(this._sizePopover && this._sizePopover.contains(
|
|
224
|
+
!this._activeTable.contains(et) &&
|
|
225
|
+
!this._el.contains(et) &&
|
|
226
|
+
!(this._sizePopover && this._sizePopover.contains(et))) {
|
|
209
227
|
this._hide();
|
|
210
228
|
}
|
|
211
229
|
}),
|
|
230
|
+
// Sync shade strip whenever selection moves to a different cell
|
|
231
|
+
on(document, 'selectionchange', () => this._syncShadeStrip()),
|
|
232
|
+
// Hide when the page scrolls or resizes — the tooltip position becomes stale
|
|
233
|
+
on(window, 'scroll', () => this._hide(), { passive: true }),
|
|
234
|
+
on(window, 'resize', () => this._hide(), { passive: true }),
|
|
212
235
|
);
|
|
213
236
|
|
|
214
237
|
this._initResize();
|
|
@@ -369,6 +392,10 @@ export class TableTooltip {
|
|
|
369
392
|
this._sizePopover.parentNode.removeChild(this._sizePopover);
|
|
370
393
|
}
|
|
371
394
|
this._sizePopover = null;
|
|
395
|
+
if (this._shadePopover && this._shadePopover.parentNode) {
|
|
396
|
+
this._shadePopover.parentNode.removeChild(this._shadePopover);
|
|
397
|
+
}
|
|
398
|
+
this._shadePopover = null;
|
|
372
399
|
}
|
|
373
400
|
|
|
374
401
|
// ---------------------------------------------------------------------------
|
|
@@ -417,6 +444,27 @@ export class TableTooltip {
|
|
|
417
444
|
|
|
418
445
|
el.appendChild(this._sep());
|
|
419
446
|
|
|
447
|
+
// Cell background shading — uses color-strip variant like foreColor/hiliteColor buttons
|
|
448
|
+
const shadeBtn = createElement('button', {
|
|
449
|
+
type: 'button',
|
|
450
|
+
class: 'an-link-tooltip-btn an-link-tooltip-btn--shade',
|
|
451
|
+
title: L.cellBackground,
|
|
452
|
+
});
|
|
453
|
+
const shadeSvgWrap = createElement('span', { class: 'an-bubble-btn-svg' });
|
|
454
|
+
shadeSvgWrap.innerHTML = ICONS.cellShade;
|
|
455
|
+
const shadeStrip = createElement('span', { class: 'an-link-tooltip-color-strip' });
|
|
456
|
+
shadeBtn.appendChild(shadeSvgWrap);
|
|
457
|
+
shadeBtn.appendChild(shadeStrip);
|
|
458
|
+
this._shadeColorStrip = shadeStrip;
|
|
459
|
+
this._disposers.push(on(shadeBtn, 'click', (e) => {
|
|
460
|
+
e.preventDefault();
|
|
461
|
+
e.stopPropagation();
|
|
462
|
+
this._openCellShadePopover();
|
|
463
|
+
}));
|
|
464
|
+
el.appendChild(shadeBtn);
|
|
465
|
+
|
|
466
|
+
el.appendChild(this._sep());
|
|
467
|
+
|
|
420
468
|
// Resize
|
|
421
469
|
el.appendChild(this._makeBtn(ICONS.colWidth, L.columnWidth, () => this._openSizePopover('col')));
|
|
422
470
|
el.appendChild(this._makeBtn(ICONS.rowHeight, L.rowHeight, () => this._openSizePopover('row')));
|
|
@@ -434,7 +482,8 @@ export class TableTooltip {
|
|
|
434
482
|
on(el, 'mouseenter', () => this._clearTimers()),
|
|
435
483
|
on(el, 'mouseleave', () => {
|
|
436
484
|
if (this._selectMode) return; // keep tooltip alive during cell selection
|
|
437
|
-
if (this._sizePopover
|
|
485
|
+
if (this._sizePopover && this._sizePopover.style.display !== 'none') return;
|
|
486
|
+
if (this._shadePopover && this._shadePopover.style.display !== 'none') return;
|
|
438
487
|
this._scheduleHide();
|
|
439
488
|
}),
|
|
440
489
|
);
|
|
@@ -494,12 +543,19 @@ export class TableTooltip {
|
|
|
494
543
|
_show() {
|
|
495
544
|
if (!this._activeTable) return;
|
|
496
545
|
this._el.style.display = 'flex';
|
|
546
|
+
this._syncShadeStrip();
|
|
497
547
|
// Defer: offsetWidth on a newly-visible element forces layout synchronously
|
|
498
548
|
requestAnimationFrame(() => {
|
|
499
549
|
if (this._activeTable) this._positionNear(this._activeTable);
|
|
500
550
|
});
|
|
501
551
|
}
|
|
502
552
|
|
|
553
|
+
_syncShadeStrip() {
|
|
554
|
+
if (!this._shadeColorStrip || !this._el || this._el.style.display === 'none') return;
|
|
555
|
+
const cell = this._getCell();
|
|
556
|
+
this._shadeColorStrip.style.background = (cell && cell.style.backgroundColor) || 'transparent';
|
|
557
|
+
}
|
|
558
|
+
|
|
503
559
|
_hide() {
|
|
504
560
|
this._el.style.display = 'none';
|
|
505
561
|
this._activeTable = null;
|
|
@@ -551,7 +607,7 @@ export class TableTooltip {
|
|
|
551
607
|
if (sel && sel.rangeCount) {
|
|
552
608
|
let container = sel.getRangeAt(0).commonAncestorContainer;
|
|
553
609
|
if (container.nodeType === 3) container = container.parentElement;
|
|
554
|
-
const cellFromSel = container &&
|
|
610
|
+
const cellFromSel = container && /** @type {Element} */ (container).closest('td, th');
|
|
555
611
|
if (cellFromSel && this._activeTable && this._activeTable.contains(cellFromSel)) {
|
|
556
612
|
return cellFromSel;
|
|
557
613
|
}
|
|
@@ -901,9 +957,9 @@ export class TableTooltip {
|
|
|
901
957
|
|
|
902
958
|
const titleEl = createElement('div', { class: 'an-size-popover-title' });
|
|
903
959
|
const body = createElement('div', { class: 'an-size-popover-body' });
|
|
904
|
-
const inputEl = createElement('input', {
|
|
960
|
+
const inputEl = /** @type {HTMLInputElement} */ (createElement('input', {
|
|
905
961
|
type: 'number', class: 'an-size-input', min: '1', max: '2000', step: '1',
|
|
906
|
-
});
|
|
962
|
+
}));
|
|
907
963
|
const unitEl = createElement('span', { class: 'an-size-unit' }, ['px']);
|
|
908
964
|
body.appendChild(inputEl);
|
|
909
965
|
body.appendChild(unitEl);
|
|
@@ -931,14 +987,16 @@ export class TableTooltip {
|
|
|
931
987
|
});
|
|
932
988
|
const d2 = on(cancelBtn, 'click', () => this._hideSizePopover());
|
|
933
989
|
const d3 = on(inputEl, 'keydown', (e) => {
|
|
934
|
-
|
|
935
|
-
if (
|
|
990
|
+
const ke = /** @type {KeyboardEvent} */ (e);
|
|
991
|
+
if (ke.key === 'Enter') { e.preventDefault(); applyBtn.click(); }
|
|
992
|
+
if (ke.key === 'Escape') this._hideSizePopover();
|
|
936
993
|
});
|
|
937
994
|
const d4 = on(document, 'click', (e) => {
|
|
995
|
+
const et = /** @type {Node} */ (e.target);
|
|
938
996
|
if (this._sizePopover &&
|
|
939
997
|
this._sizePopover.style.display !== 'none' &&
|
|
940
|
-
!this._sizePopover.contains(
|
|
941
|
-
!this._el.contains(
|
|
998
|
+
!this._sizePopover.contains(et) &&
|
|
999
|
+
!this._el.contains(et)) {
|
|
942
1000
|
this._hideSizePopover();
|
|
943
1001
|
}
|
|
944
1002
|
});
|
|
@@ -964,7 +1022,7 @@ export class TableTooltip {
|
|
|
964
1022
|
this._sizeTitleEl.textContent = this.context.locale.tooltips.table.tableBorderWidthPx;
|
|
965
1023
|
this._sizeInputEl.min = '0';
|
|
966
1024
|
this._sizeInputEl.max = '10';
|
|
967
|
-
this._sizeInputEl.value = currentPx;
|
|
1025
|
+
this._sizeInputEl.value = String(currentPx);
|
|
968
1026
|
this._sizeApply = (val) => {
|
|
969
1027
|
const cells = Array.from(table.querySelectorAll('td, th'));
|
|
970
1028
|
if (val === 0) {
|
|
@@ -1033,4 +1091,108 @@ export class TableTooltip {
|
|
|
1033
1091
|
if (this._sizePopover) this._sizePopover.style.display = 'none';
|
|
1034
1092
|
this._sizeApply = null;
|
|
1035
1093
|
}
|
|
1094
|
+
|
|
1095
|
+
// ---------------------------------------------------------------------------
|
|
1096
|
+
// Cell background shade popover
|
|
1097
|
+
// ---------------------------------------------------------------------------
|
|
1098
|
+
|
|
1099
|
+
_buildCellShadePopover() {
|
|
1100
|
+
const pop = createElement('div', { class: 'an-cell-shade-popover' });
|
|
1101
|
+
pop.style.display = 'none';
|
|
1102
|
+
|
|
1103
|
+
const title = createElement('div', { class: 'an-size-popover-title' });
|
|
1104
|
+
pop.appendChild(title);
|
|
1105
|
+
this._shadeTitleEl = title;
|
|
1106
|
+
|
|
1107
|
+
// 24-color palette — reuse existing CSS classes
|
|
1108
|
+
const palette = createElement('div', { class: 'an-context-color-palette' });
|
|
1109
|
+
SHADE_PRESETS.forEach((color) => {
|
|
1110
|
+
const sw = createElement('div', { class: 'an-context-color-swatch', title: color });
|
|
1111
|
+
sw.style.background = color;
|
|
1112
|
+
this._disposers.push(on(sw, 'click', (e) => {
|
|
1113
|
+
e.stopPropagation();
|
|
1114
|
+
this._applyCellShade(color);
|
|
1115
|
+
}));
|
|
1116
|
+
palette.appendChild(sw);
|
|
1117
|
+
});
|
|
1118
|
+
pop.appendChild(palette);
|
|
1119
|
+
|
|
1120
|
+
// "No shading" row — clears background color
|
|
1121
|
+
const noShadeRow = createElement('div', { class: 'an-context-color-custom' });
|
|
1122
|
+
const noShadeBtn = createElement('button', { type: 'button', class: 'an-shade-no-color' });
|
|
1123
|
+
this._disposers.push(on(noShadeBtn, 'click', () => this._applyCellShade('')));
|
|
1124
|
+
noShadeRow.appendChild(noShadeBtn);
|
|
1125
|
+
pop.appendChild(noShadeRow);
|
|
1126
|
+
this._shadeNoBtn = noShadeBtn;
|
|
1127
|
+
|
|
1128
|
+
// Custom color input
|
|
1129
|
+
const customRow = createElement('div', { class: 'an-context-color-custom' });
|
|
1130
|
+
const colorInput = /** @type {HTMLInputElement} */ (createElement('input', { type: 'color', class: 'an-shade-color-input', value: '#ffffff' }));
|
|
1131
|
+
const customLabel = createElement('span');
|
|
1132
|
+
customLabel.textContent = 'Custom…';
|
|
1133
|
+
this._disposers.push(on(colorInput, 'change', () => this._applyCellShade(colorInput.value)));
|
|
1134
|
+
customRow.appendChild(colorInput);
|
|
1135
|
+
customRow.appendChild(customLabel);
|
|
1136
|
+
pop.appendChild(customRow);
|
|
1137
|
+
|
|
1138
|
+
// Prevent mousedown from collapsing editor selection
|
|
1139
|
+
this._disposers.push(on(pop, 'mousedown', (e) => e.preventDefault()));
|
|
1140
|
+
|
|
1141
|
+
// Keep tooltip alive while hovering popover
|
|
1142
|
+
this._disposers.push(
|
|
1143
|
+
on(pop, 'mouseenter', () => this._clearTimers()),
|
|
1144
|
+
on(pop, 'mouseleave', () => this._scheduleHide()),
|
|
1145
|
+
);
|
|
1146
|
+
|
|
1147
|
+
// Close on outside click
|
|
1148
|
+
this._disposers.push(on(document, 'click', (e) => {
|
|
1149
|
+
const et = /** @type {Node} */ (e.target);
|
|
1150
|
+
if (this._shadePopover &&
|
|
1151
|
+
this._shadePopover.style.display !== 'none' &&
|
|
1152
|
+
!this._shadePopover.contains(et) &&
|
|
1153
|
+
!(this._el && this._el.contains(et))) {
|
|
1154
|
+
this._hideCellShadePopover();
|
|
1155
|
+
}
|
|
1156
|
+
}));
|
|
1157
|
+
|
|
1158
|
+
return pop;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
_openCellShadePopover() {
|
|
1162
|
+
if (!this._shadePopover) return;
|
|
1163
|
+
const L = this.context.locale.tooltips.table;
|
|
1164
|
+
if (this._shadeTitleEl) this._shadeTitleEl.textContent = L.cellBackground;
|
|
1165
|
+
if (this._shadeNoBtn) this._shadeNoBtn.textContent = L.noShading;
|
|
1166
|
+
|
|
1167
|
+
this._shadePopover.style.display = 'block';
|
|
1168
|
+
requestAnimationFrame(() => {
|
|
1169
|
+
if (!this._shadePopover || !this._el) return;
|
|
1170
|
+
const pw = this._shadePopover.offsetWidth || 170;
|
|
1171
|
+
const ph = this._shadePopover.offsetHeight || 120;
|
|
1172
|
+
const tipRect = this._el.getBoundingClientRect();
|
|
1173
|
+
let left = tipRect.left;
|
|
1174
|
+
let top = tipRect.bottom + 6;
|
|
1175
|
+
if (left + pw > window.innerWidth - 8) left = window.innerWidth - pw - 8;
|
|
1176
|
+
if (top + ph > window.innerHeight - 8) top = tipRect.top - ph - 6;
|
|
1177
|
+
this._shadePopover.style.left = `${Math.max(8, left)}px`;
|
|
1178
|
+
this._shadePopover.style.top = `${Math.max(8, top)}px`;
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
_hideCellShadePopover() {
|
|
1183
|
+
if (this._shadePopover) this._shadePopover.style.display = 'none';
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
_applyCellShade(color) {
|
|
1187
|
+
const cells = this._selectMode ? this._selectedCells : [this._getCell()];
|
|
1188
|
+
cells.forEach((cell) => {
|
|
1189
|
+
if (cell) cell.style.backgroundColor = color;
|
|
1190
|
+
});
|
|
1191
|
+
// Update the color strip on the shade button to reflect the applied color
|
|
1192
|
+
if (this._shadeColorStrip) {
|
|
1193
|
+
this._shadeColorStrip.style.background = color || 'transparent';
|
|
1194
|
+
}
|
|
1195
|
+
this._hideCellShadePopover();
|
|
1196
|
+
this.context.invoke('editor.afterCommand');
|
|
1197
|
+
}
|
|
1036
1198
|
}
|
package/src/js/module/Toolbar.js
CHANGED
|
@@ -286,7 +286,7 @@ export class Toolbar {
|
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
const d2 = on(grid, 'mouseover', (e) => {
|
|
289
|
-
const cell = e.target
|
|
289
|
+
const cell = /** @type {Element} */ (e.target)?.closest('.an-table-cell');
|
|
290
290
|
if (!cell) return;
|
|
291
291
|
setHighlight(+cell.getAttribute('data-row'), +cell.getAttribute('data-col'));
|
|
292
292
|
});
|
|
@@ -294,7 +294,7 @@ export class Toolbar {
|
|
|
294
294
|
const d3 = on(grid, 'mouseleave', () => setHighlight(0, 0));
|
|
295
295
|
|
|
296
296
|
const d4 = on(grid, 'click', (e) => {
|
|
297
|
-
const cell = e.target
|
|
297
|
+
const cell = /** @type {Element} */ (e.target)?.closest('.an-table-cell');
|
|
298
298
|
if (!cell) return;
|
|
299
299
|
const rows = +cell.getAttribute('data-row');
|
|
300
300
|
const cols = +cell.getAttribute('data-col');
|
|
@@ -313,7 +313,7 @@ export class Toolbar {
|
|
|
313
313
|
|
|
314
314
|
wrap.appendChild(btn);
|
|
315
315
|
document.body.appendChild(popup);
|
|
316
|
-
return wrap;
|
|
316
|
+
return /** @type {HTMLDivElement} */ (wrap);
|
|
317
317
|
}
|
|
318
318
|
|
|
319
319
|
/**
|
|
@@ -384,7 +384,7 @@ export class Toolbar {
|
|
|
384
384
|
});
|
|
385
385
|
|
|
386
386
|
const customRow = createElement('div', { class: 'an-color-custom' });
|
|
387
|
-
const colorInput = createElement('input', { type: 'color', value: currentColor, title: this.context.locale.toolbar.customColor || 'Custom color' });
|
|
387
|
+
const colorInput = /** @type {HTMLInputElement} */ (createElement('input', { type: 'color', value: currentColor, title: this.context.locale.toolbar.customColor || 'Custom color' }));
|
|
388
388
|
const customLabel = createElement('span', {}, [this.context.locale.toolbar.customColor || 'Custom color']);
|
|
389
389
|
customRow.appendChild(colorInput);
|
|
390
390
|
customRow.appendChild(customLabel);
|
|
@@ -472,17 +472,17 @@ export class Toolbar {
|
|
|
472
472
|
});
|
|
473
473
|
|
|
474
474
|
const d3b = on(swatches, 'click', (e) => {
|
|
475
|
-
const sw = e.target
|
|
476
|
-
if (sw) applyColor(sw.dataset.color);
|
|
475
|
+
const sw = /** @type {Element} */ (e.target)?.closest('.an-color-swatch');
|
|
476
|
+
if (sw) applyColor(/** @type {HTMLElement} */ (sw).dataset.color);
|
|
477
477
|
});
|
|
478
478
|
|
|
479
479
|
const d4 = on(colorInput, 'change', (e) => {
|
|
480
|
-
applyColor(e.target.value);
|
|
480
|
+
applyColor(/** @type {HTMLInputElement} */ (e.target).value);
|
|
481
481
|
});
|
|
482
482
|
|
|
483
483
|
const d5 = on(document, 'click', (e) => {
|
|
484
484
|
// popup is in document.body, not inside wrap — check both
|
|
485
|
-
if (isOpen && !wrap.contains(e.target) && !popup.contains(e.target)) closePopup();
|
|
485
|
+
if (isOpen && !wrap.contains(/** @type {Node} */ (e.target)) && !popup.contains(/** @type {Node} */ (e.target))) closePopup();
|
|
486
486
|
});
|
|
487
487
|
|
|
488
488
|
const d6 = on(popup, 'click', (e) => e.stopPropagation());
|
|
@@ -513,7 +513,7 @@ export class Toolbar {
|
|
|
513
513
|
wrap.appendChild(applyBtn);
|
|
514
514
|
wrap.appendChild(arrowBtn);
|
|
515
515
|
document.body.appendChild(popup);
|
|
516
|
-
return wrap;
|
|
516
|
+
return /** @type {HTMLDivElement} */ (wrap);
|
|
517
517
|
}
|
|
518
518
|
|
|
519
519
|
/**
|
|
@@ -583,7 +583,7 @@ export class Toolbar {
|
|
|
583
583
|
});
|
|
584
584
|
|
|
585
585
|
this._disposers.push(dMousedown, disposer);
|
|
586
|
-
return select;
|
|
586
|
+
return /** @type {HTMLSelectElement} */ (select);
|
|
587
587
|
}
|
|
588
588
|
|
|
589
589
|
/**
|
|
@@ -638,7 +638,7 @@ export class Toolbar {
|
|
|
638
638
|
});
|
|
639
639
|
|
|
640
640
|
this._disposers.push(disposer);
|
|
641
|
-
return btn;
|
|
641
|
+
return /** @type {HTMLButtonElement} */ (btn);
|
|
642
642
|
}
|
|
643
643
|
|
|
644
644
|
// ---------------------------------------------------------------------------
|
|
@@ -659,7 +659,7 @@ export class Toolbar {
|
|
|
659
659
|
// count as "the host page loaded FA" for toolbar icon rendering purposes.
|
|
660
660
|
const links = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
|
|
661
661
|
.filter((l) => l.id !== 'an-fontawesome-css')
|
|
662
|
-
.map((l) => l.href || '').join(' ');
|
|
662
|
+
.map((l) => /** @type {HTMLLinkElement} */ (l).href || '').join(' ');
|
|
663
663
|
_faPageLevelReady = /fontawesome|font-awesome|use\.fontawesome|all\.css/.test(links);
|
|
664
664
|
return _faPageLevelReady;
|
|
665
665
|
}
|
|
@@ -689,7 +689,7 @@ export class Toolbar {
|
|
|
689
689
|
btn.classList.toggle('active', !!def.isActive(this.context));
|
|
690
690
|
}
|
|
691
691
|
if (def && typeof def.isDisabled === 'function') {
|
|
692
|
-
btn.disabled = !!def.isDisabled(this.context);
|
|
692
|
+
/** @type {HTMLButtonElement} */ (btn).disabled = !!def.isDisabled(this.context);
|
|
693
693
|
}
|
|
694
694
|
});
|
|
695
695
|
|
|
@@ -706,10 +706,11 @@ export class Toolbar {
|
|
|
706
706
|
|| '';
|
|
707
707
|
}
|
|
708
708
|
// Try to match against available options (case-insensitive)
|
|
709
|
-
const
|
|
709
|
+
const sel = /** @type {HTMLSelectElement} */ (select);
|
|
710
|
+
const matched = Array.from(sel.options).find(
|
|
710
711
|
(opt) => opt.value && opt.value.toLowerCase() === raw.toLowerCase()
|
|
711
712
|
);
|
|
712
|
-
|
|
713
|
+
sel.value = matched ? matched.value : '';
|
|
713
714
|
});
|
|
714
715
|
}
|
|
715
716
|
|