autumnnote 1.6.7 → 1.8.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 +11 -5
- package/dist/autumnnote.css +164 -0
- package/dist/autumnnote.es.js +580 -39
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +658 -100
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/Context.js +63 -2
- package/src/js/editing/History.js +10 -0
- package/src/js/i18n/de.js +14 -0
- package/src/js/i18n/en.js +22 -0
- package/src/js/i18n/es.js +14 -0
- package/src/js/i18n/fr.js +15 -1
- package/src/js/i18n/ja.js +14 -0
- package/src/js/i18n/ko.js +14 -0
- package/src/js/i18n/vi.js +14 -0
- package/src/js/i18n/zh.js +14 -0
- package/src/js/index.js +5 -2
- package/src/js/module/BaseDialog.js +7 -0
- package/src/js/module/Buttons.js +49 -0
- package/src/js/module/Clipboard.js +12 -0
- package/src/js/module/CodeTooltip.js +23 -0
- package/src/js/module/Editor.js +8 -0
- package/src/js/module/FindReplace.js +50 -6
- package/src/js/module/ImageResizer.js +8 -7
- package/src/js/module/Mention.js +9 -2
- package/src/js/module/Statusbar.js +3 -3
- package/src/js/module/TableTooltip.js +287 -2
- package/src/js/renderer.js +5 -2
- package/src/styles/autumnnote.scss +181 -0
- package/types/index.d.ts +99 -4
|
@@ -30,6 +30,8 @@ export class FindReplace extends BaseDialog {
|
|
|
30
30
|
this._matches = [];
|
|
31
31
|
this._currentIndex = -1;
|
|
32
32
|
this._caseSensitive = false;
|
|
33
|
+
this._useRegex = false;
|
|
34
|
+
this._wholeWord = false;
|
|
33
35
|
/** @type {'find'|'replace'} */
|
|
34
36
|
this._mode = 'find';
|
|
35
37
|
|
|
@@ -37,6 +39,8 @@ export class FindReplace extends BaseDialog {
|
|
|
37
39
|
this._queryRegex = null;
|
|
38
40
|
this._lastQuery = null;
|
|
39
41
|
this._lastCaseSensitive = null;
|
|
42
|
+
this._lastUseRegex = null;
|
|
43
|
+
this._lastWholeWord = null;
|
|
40
44
|
|
|
41
45
|
this._focusTimer = null;
|
|
42
46
|
}
|
|
@@ -169,6 +173,22 @@ export class FindReplace extends BaseDialog {
|
|
|
169
173
|
});
|
|
170
174
|
caseBtn.textContent = 'Aa';
|
|
171
175
|
|
|
176
|
+
const regexBtn = createElement('button', {
|
|
177
|
+
type: 'button',
|
|
178
|
+
class: 'an-fr-icon-btn',
|
|
179
|
+
title: L.useRegex,
|
|
180
|
+
'aria-label': L.useRegex,
|
|
181
|
+
});
|
|
182
|
+
regexBtn.textContent = '.*';
|
|
183
|
+
|
|
184
|
+
const wholeWordBtn = createElement('button', {
|
|
185
|
+
type: 'button',
|
|
186
|
+
class: 'an-fr-icon-btn',
|
|
187
|
+
title: L.wholeWord,
|
|
188
|
+
'aria-label': L.wholeWord,
|
|
189
|
+
});
|
|
190
|
+
wholeWordBtn.innerHTML = `<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="7" width="18" height="10" rx="2"/><line x1="7" y1="21" x2="7" y2="17"/><line x1="17" y1="21" x2="17" y2="17"/></svg>`;
|
|
191
|
+
|
|
172
192
|
const prevBtn = createElement('button', {
|
|
173
193
|
type: 'button',
|
|
174
194
|
class: 'an-fr-icon-btn',
|
|
@@ -188,7 +208,7 @@ export class FindReplace extends BaseDialog {
|
|
|
188
208
|
const counter = createElement('span', { class: 'an-fr-counter' });
|
|
189
209
|
this._counterEl = counter;
|
|
190
210
|
|
|
191
|
-
searchBar.append(findInput, caseCheckbox, caseBtn, prevBtn, nextBtn, counter);
|
|
211
|
+
searchBar.append(findInput, caseCheckbox, caseBtn, regexBtn, wholeWordBtn, prevBtn, nextBtn, counter);
|
|
192
212
|
box.appendChild(searchBar);
|
|
193
213
|
|
|
194
214
|
// ---- Replace row: [input] [Replace] [All] (hidden by default) ----
|
|
@@ -249,7 +269,21 @@ export class FindReplace extends BaseDialog {
|
|
|
249
269
|
this._replace();
|
|
250
270
|
}
|
|
251
271
|
});
|
|
252
|
-
|
|
272
|
+
const dRegex = on(regexBtn, 'click', () => {
|
|
273
|
+
this._useRegex = !this._useRegex;
|
|
274
|
+
regexBtn.classList.toggle('an-fr-icon-btn--active', this._useRegex);
|
|
275
|
+
this._queryRegex = null; // force recompile
|
|
276
|
+
this._lastQuery = null;
|
|
277
|
+
this._onSearch();
|
|
278
|
+
});
|
|
279
|
+
const dWholeWord = on(wholeWordBtn, 'click', () => {
|
|
280
|
+
this._wholeWord = !this._wholeWord;
|
|
281
|
+
wholeWordBtn.classList.toggle('an-fr-icon-btn--active', this._wholeWord);
|
|
282
|
+
this._queryRegex = null;
|
|
283
|
+
this._lastQuery = null;
|
|
284
|
+
this._onSearch();
|
|
285
|
+
});
|
|
286
|
+
this._disposers.push(d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, dRegex, dWholeWord);
|
|
253
287
|
|
|
254
288
|
return overlay;
|
|
255
289
|
}
|
|
@@ -324,14 +358,24 @@ export class FindReplace extends BaseDialog {
|
|
|
324
358
|
*/
|
|
325
359
|
_findRawMatches(query, root) {
|
|
326
360
|
const results = [];
|
|
327
|
-
// Reuse compiled regex when query
|
|
328
|
-
if (this._lastQuery !== query || this._lastCaseSensitive !== this._caseSensitive) {
|
|
361
|
+
// Reuse compiled regex when query, case-sensitivity, regex mode, and whole-word haven't changed
|
|
362
|
+
if (this._lastQuery !== query || this._lastCaseSensitive !== this._caseSensitive || this._lastUseRegex !== this._useRegex || this._lastWholeWord !== this._wholeWord) {
|
|
329
363
|
const flags = this._caseSensitive ? 'g' : 'gi';
|
|
330
|
-
|
|
331
|
-
|
|
364
|
+
try {
|
|
365
|
+
let pattern = this._useRegex
|
|
366
|
+
? query
|
|
367
|
+
: query.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
368
|
+
if (this._wholeWord) pattern = `\\b${pattern}\\b`;
|
|
369
|
+
this._queryRegex = new RegExp(pattern, flags);
|
|
370
|
+
} catch (_) {
|
|
371
|
+
this._queryRegex = null;
|
|
372
|
+
}
|
|
332
373
|
this._lastQuery = query;
|
|
333
374
|
this._lastCaseSensitive = this._caseSensitive;
|
|
375
|
+
this._lastUseRegex = this._useRegex;
|
|
376
|
+
this._lastWholeWord = this._wholeWord;
|
|
334
377
|
}
|
|
378
|
+
if (!this._queryRegex) return results;
|
|
335
379
|
const re = this._queryRegex;
|
|
336
380
|
|
|
337
381
|
// Cap results to prevent blocking the main thread on very large documents
|
|
@@ -204,6 +204,7 @@ export class ImageResizer {
|
|
|
204
204
|
const isCorner = pos.length === 2; // 'nw','ne','se','sw'
|
|
205
205
|
|
|
206
206
|
const editable = this.context.layoutInfo.editable;
|
|
207
|
+
const minSz = this.context.options?.minImageSize ?? 20;
|
|
207
208
|
let _raf = null; // rAF handle — ensures at most one write per paint frame
|
|
208
209
|
|
|
209
210
|
const onMove = (me) => {
|
|
@@ -218,19 +219,19 @@ export class ImageResizer {
|
|
|
218
219
|
let newW = startW;
|
|
219
220
|
let newH = startH;
|
|
220
221
|
|
|
221
|
-
if (pos.includes('e')) newW = Math.max(
|
|
222
|
-
if (pos.includes('w')) newW = Math.max(
|
|
223
|
-
if (pos.includes('s')) newH = Math.max(
|
|
224
|
-
if (pos.includes('n')) newH = Math.max(
|
|
222
|
+
if (pos.includes('e')) newW = Math.max(minSz, startW + dx);
|
|
223
|
+
if (pos.includes('w')) newW = Math.max(minSz, startW - dx);
|
|
224
|
+
if (pos.includes('s')) newH = Math.max(minSz, startH + dy);
|
|
225
|
+
if (pos.includes('n')) newH = Math.max(minSz, startH - dy);
|
|
225
226
|
|
|
226
227
|
newW = Math.min(newW, maxW);
|
|
227
228
|
|
|
228
229
|
if (isCorner) {
|
|
229
230
|
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
230
|
-
newH = Math.max(
|
|
231
|
+
newH = Math.max(minSz, Math.round(newW / aspectRatio));
|
|
231
232
|
} else {
|
|
232
|
-
newW = Math.min(Math.max(
|
|
233
|
-
newH = Math.max(
|
|
233
|
+
newW = Math.min(Math.max(minSz, Math.round(newH * aspectRatio)), maxW);
|
|
234
|
+
newH = Math.max(minSz, Math.round(newW / aspectRatio));
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
|
package/src/js/module/Mention.js
CHANGED
|
@@ -269,14 +269,21 @@ export class Mention {
|
|
|
269
269
|
this._query = query;
|
|
270
270
|
clearTimeout(this._debounceTimer);
|
|
271
271
|
this._debounceTimer = setTimeout(() => {
|
|
272
|
-
|
|
272
|
+
const cb = (items) => {
|
|
273
273
|
if (!Array.isArray(items) || items.length === 0) {
|
|
274
274
|
this._hideDropdown();
|
|
275
275
|
return;
|
|
276
276
|
}
|
|
277
277
|
this._renderItems(items);
|
|
278
278
|
this._showDropdown();
|
|
279
|
-
}
|
|
279
|
+
};
|
|
280
|
+
const result = this._cfg.onSearch(this._query, cb);
|
|
281
|
+
if (result && typeof result.then === 'function') {
|
|
282
|
+
result.then(cb).catch((err) => {
|
|
283
|
+
this._hideDropdown();
|
|
284
|
+
if (typeof this._cfg.onError === 'function') this._cfg.onError(err);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
280
287
|
}, this._cfg.debounce);
|
|
281
288
|
}
|
|
282
289
|
|
|
@@ -90,9 +90,9 @@ export class Statusbar {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
// Counters
|
|
93
|
-
this._wordCountEl = createElement('span', { class: 'an-word-count' });
|
|
94
|
-
this._charCountEl = createElement('span', { class: 'an-char-count' });
|
|
95
|
-
const info = createElement('div', { class: 'an-status-info' });
|
|
93
|
+
this._wordCountEl = createElement('span', { class: 'an-word-count', role: 'status', 'aria-live': 'polite', 'aria-atomic': 'true' });
|
|
94
|
+
this._charCountEl = createElement('span', { class: 'an-char-count', 'aria-live': 'polite', 'aria-atomic': 'true' });
|
|
95
|
+
const info = createElement('div', { class: 'an-status-info', 'aria-label': 'Editor statistics' });
|
|
96
96
|
info.appendChild(this._wordCountEl);
|
|
97
97
|
info.appendChild(this._charCountEl);
|
|
98
98
|
this.el.appendChild(info);
|
|
@@ -116,6 +116,16 @@ const ICONS = {
|
|
|
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
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>`,
|
|
119
|
+
borderColor: `<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="14" rx="1"/><line x1="3" y1="10" x2="21" y2="10" stroke-width="1.5"/><line x1="12" y1="3" x2="12" y2="17" stroke-width="1.5"/><path d="M3 21h18" stroke-width="3"/></svg>`,
|
|
120
|
+
alignLeft: `<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="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"/></svg>`,
|
|
121
|
+
alignCenter: `<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="21" y1="6" x2="3" y2="6"/><line x1="17" y1="12" x2="7" y2="12"/><line x1="19" y1="18" x2="5" y2="18"/></svg>`,
|
|
122
|
+
alignRight: `<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="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"/></svg>`,
|
|
123
|
+
alignJustify: `<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="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"/></svg>`,
|
|
124
|
+
headerRow: `<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"/><rect x="3" y="3" width="18" height="8" rx="1" fill="currentColor" opacity="0.2"/><line x1="3" y1="11" x2="21" y2="11"/><line x1="3" y1="16" x2="21" y2="16"/><line x1="9" y1="11" x2="9" y2="21"/><line x1="15" y1="11" x2="15" y2="21"/></svg>`,
|
|
125
|
+
sortAsc: `<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="6" x2="11" y2="6"/><line x1="4" y1="12" x2="11" y2="12"/><line x1="4" y1="18" x2="13" y2="18"/><path d="M15 9l3-3 3 3"/><line x1="18" y1="6" x2="18" y2="18"/></svg>`,
|
|
126
|
+
sortDesc: `<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="6" x2="13" y2="6"/><line x1="4" y1="12" x2="11" y2="12"/><line x1="4" y1="18" x2="11" y2="18"/><path d="M15 15l3 3 3-3"/><line x1="18" y1="6" x2="18" y2="18"/></svg>`,
|
|
127
|
+
exportCSV: `<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="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`,
|
|
128
|
+
cellPadding: `<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"/><rect x="7" y="7" width="10" height="10" rx="0.5" stroke-dasharray="2 1.5"/></svg>`,
|
|
119
129
|
};
|
|
120
130
|
|
|
121
131
|
const SHADE_PRESETS = [
|
|
@@ -142,6 +152,11 @@ export class TableTooltip {
|
|
|
142
152
|
this._shadePopover = null;
|
|
143
153
|
this._shadeTitleEl = null;
|
|
144
154
|
this._shadeColorStrip = null;
|
|
155
|
+
// Border color popover
|
|
156
|
+
this._borderColorPopover = null;
|
|
157
|
+
this._borderColorTitleEl = null;
|
|
158
|
+
this._borderColorStrip = null;
|
|
159
|
+
this._borderColorNoBtn = null;
|
|
145
160
|
// Cell selection
|
|
146
161
|
this._selectMode = false;
|
|
147
162
|
this._selectedCells = [];
|
|
@@ -161,6 +176,9 @@ export class TableTooltip {
|
|
|
161
176
|
this._shadePopover = this._buildCellShadePopover();
|
|
162
177
|
document.body.appendChild(this._shadePopover);
|
|
163
178
|
|
|
179
|
+
this._borderColorPopover = this._buildBorderColorPopover();
|
|
180
|
+
document.body.appendChild(this._borderColorPopover);
|
|
181
|
+
|
|
164
182
|
const editable = this.context.layoutInfo.editable;
|
|
165
183
|
this._editable = editable;
|
|
166
184
|
|
|
@@ -220,7 +238,8 @@ export class TableTooltip {
|
|
|
220
238
|
if (this._activeTable &&
|
|
221
239
|
!this._activeTable.contains(et) &&
|
|
222
240
|
!this._el.contains(et) &&
|
|
223
|
-
!this._sizePopover?.contains(et)
|
|
241
|
+
!this._sizePopover?.contains(et) &&
|
|
242
|
+
!this._borderColorPopover?.contains(et)) {
|
|
224
243
|
this._hide();
|
|
225
244
|
}
|
|
226
245
|
}),
|
|
@@ -398,6 +417,10 @@ export class TableTooltip {
|
|
|
398
417
|
this._shadePopover.remove();
|
|
399
418
|
}
|
|
400
419
|
this._shadePopover = null;
|
|
420
|
+
if (this._borderColorPopover?.parentNode) {
|
|
421
|
+
this._borderColorPopover.remove();
|
|
422
|
+
}
|
|
423
|
+
this._borderColorPopover = null;
|
|
401
424
|
}
|
|
402
425
|
|
|
403
426
|
// ---------------------------------------------------------------------------
|
|
@@ -437,6 +460,8 @@ export class TableTooltip {
|
|
|
437
460
|
el.appendChild(this._makeBtn(ICONS.colLeft, L.addColumnLeft, () => this._addColumn('left')));
|
|
438
461
|
el.appendChild(this._makeBtn(ICONS.colRight, L.addColumnRight, () => this._addColumn('right')));
|
|
439
462
|
el.appendChild(this._makeBtn(ICONS.deleteCol, L.deleteColumn, () => this._deleteColumn()));
|
|
463
|
+
el.appendChild(this._makeBtn(ICONS.sortAsc, L.sortAsc, () => this._sortColumn('asc')));
|
|
464
|
+
el.appendChild(this._makeBtn(ICONS.sortDesc, L.sortDesc, () => this._sortColumn('desc')));
|
|
440
465
|
|
|
441
466
|
el.appendChild(this._sep());
|
|
442
467
|
|
|
@@ -446,6 +471,15 @@ export class TableTooltip {
|
|
|
446
471
|
|
|
447
472
|
el.appendChild(this._sep());
|
|
448
473
|
|
|
474
|
+
// Cell text alignment
|
|
475
|
+
el.appendChild(this._makeBtn(ICONS.alignLeft, L.cellAlignLeft, () => this._applyCellAlign('left')));
|
|
476
|
+
el.appendChild(this._makeBtn(ICONS.alignCenter, L.cellAlignCenter, () => this._applyCellAlign('center')));
|
|
477
|
+
el.appendChild(this._makeBtn(ICONS.alignRight, L.cellAlignRight, () => this._applyCellAlign('right')));
|
|
478
|
+
el.appendChild(this._makeBtn(ICONS.alignJustify, L.cellAlignJustify, () => this._applyCellAlign('justify')));
|
|
479
|
+
el.appendChild(this._makeBtn(ICONS.headerRow, L.toggleHeaderRow, () => this._toggleHeaderRow()));
|
|
480
|
+
|
|
481
|
+
el.appendChild(this._sep());
|
|
482
|
+
|
|
449
483
|
// Cell background shading — uses color-strip variant like foreColor/hiliteColor buttons
|
|
450
484
|
const shadeBtn = createElement('button', {
|
|
451
485
|
type: 'button',
|
|
@@ -471,12 +505,35 @@ export class TableTooltip {
|
|
|
471
505
|
el.appendChild(this._makeBtn(ICONS.colWidth, L.columnWidth, () => this._openSizePopover('col')));
|
|
472
506
|
el.appendChild(this._makeBtn(ICONS.rowHeight, L.rowHeight, () => this._openSizePopover('row')));
|
|
473
507
|
el.appendChild(this._makeBtn(ICONS.tableBorder,L.tableBorderWidth, () => this._openSizePopover('border')));
|
|
508
|
+
el.appendChild(this._makeBtn(ICONS.cellPadding, L.cellPadding, () => this._openSizePopover('cellPadding')));
|
|
509
|
+
|
|
510
|
+
// Table border color button — color-strip variant like shade button
|
|
511
|
+
const borderColorBtn = createElement('button', {
|
|
512
|
+
type: 'button',
|
|
513
|
+
class: 'an-link-tooltip-btn an-link-tooltip-btn--shade',
|
|
514
|
+
title: L.tableBorderColor,
|
|
515
|
+
});
|
|
516
|
+
const borderColorSvgWrap = createElement('span', { class: 'an-bubble-btn-svg' });
|
|
517
|
+
borderColorSvgWrap.innerHTML = ICONS.borderColor;
|
|
518
|
+
const borderColorStrip = createElement('span', { class: 'an-link-tooltip-color-strip' });
|
|
519
|
+
borderColorBtn.appendChild(borderColorSvgWrap);
|
|
520
|
+
borderColorBtn.appendChild(borderColorStrip);
|
|
521
|
+
this._borderColorStrip = borderColorStrip;
|
|
522
|
+
this._disposers.push(on(borderColorBtn, 'click', (e) => {
|
|
523
|
+
e.preventDefault();
|
|
524
|
+
e.stopPropagation();
|
|
525
|
+
this._openBorderColorPopover();
|
|
526
|
+
}));
|
|
527
|
+
el.appendChild(borderColorBtn);
|
|
474
528
|
|
|
475
529
|
el.appendChild(this._sep());
|
|
476
530
|
|
|
477
531
|
// Delete table (danger)
|
|
478
532
|
el.appendChild(this._makeBtn(ICONS.deleteTable, L.deleteTable, () => this._deleteTable(), true));
|
|
479
533
|
|
|
534
|
+
el.appendChild(this._sep());
|
|
535
|
+
el.appendChild(this._makeBtn(ICONS.exportCSV, L.exportCSV, () => this._exportTableCSV()));
|
|
536
|
+
|
|
480
537
|
// Keep tooltip alive while hovering.
|
|
481
538
|
// Don't schedule hide on mouseleave when the size popover is open —
|
|
482
539
|
// the user is moving the mouse toward it.
|
|
@@ -486,6 +543,7 @@ export class TableTooltip {
|
|
|
486
543
|
if (this._selectMode) return; // keep tooltip alive during cell selection
|
|
487
544
|
if (this._sizePopover && this._sizePopover.style.display !== 'none') return;
|
|
488
545
|
if (this._shadePopover && this._shadePopover.style.display !== 'none') return;
|
|
546
|
+
if (this._borderColorPopover && this._borderColorPopover.style.display !== 'none') return;
|
|
489
547
|
this._scheduleHide();
|
|
490
548
|
}),
|
|
491
549
|
);
|
|
@@ -546,6 +604,7 @@ export class TableTooltip {
|
|
|
546
604
|
if (!this._activeTable) return;
|
|
547
605
|
this._el.style.display = 'flex';
|
|
548
606
|
this._syncShadeStrip();
|
|
607
|
+
this._syncBorderColorStrip();
|
|
549
608
|
// Defer: offsetWidth on a newly-visible element forces layout synchronously
|
|
550
609
|
requestAnimationFrame(() => {
|
|
551
610
|
if (this._activeTable) this._positionNear(this._activeTable);
|
|
@@ -571,6 +630,7 @@ export class TableTooltip {
|
|
|
571
630
|
this._clearSelection();
|
|
572
631
|
this._clearTimers();
|
|
573
632
|
this._hideSizePopover();
|
|
633
|
+
this._hideBorderColorPopover();
|
|
574
634
|
}
|
|
575
635
|
|
|
576
636
|
_clearTimers() {
|
|
@@ -1048,6 +1108,22 @@ export class TableTooltip {
|
|
|
1048
1108
|
}
|
|
1049
1109
|
this.context.invoke('editor.afterCommand');
|
|
1050
1110
|
};
|
|
1111
|
+
} else if (type === 'cellPadding') {
|
|
1112
|
+
const cells = this._getSelectedCells();
|
|
1113
|
+
const firstCell = cells[0] || this._getCell();
|
|
1114
|
+
const currentPad = firstCell
|
|
1115
|
+
? (Number.parseInt(firstCell.style.padding, 10) ||
|
|
1116
|
+
Number.parseInt(firstCell.style.paddingTop, 10) || 4)
|
|
1117
|
+
: 4;
|
|
1118
|
+
this._sizeTitleEl.textContent = this.context.locale.tooltips.table.cellPaddingPx;
|
|
1119
|
+
this._sizeInputEl.min = '0';
|
|
1120
|
+
this._sizeInputEl.max = '40';
|
|
1121
|
+
this._sizeInputEl.value = String(currentPad);
|
|
1122
|
+
this._sizeApply = (val) => {
|
|
1123
|
+
const activeCells = this._getSelectedCells();
|
|
1124
|
+
activeCells.forEach((c) => { if (c) c.style.padding = `${val}px`; });
|
|
1125
|
+
this.context.invoke('editor.afterCommand');
|
|
1126
|
+
};
|
|
1051
1127
|
} else {
|
|
1052
1128
|
const isCol = type === 'col';
|
|
1053
1129
|
const activeCells = this._getSelectedCells().filter((c) => {
|
|
@@ -1148,7 +1224,7 @@ export class TableTooltip {
|
|
|
1148
1224
|
const customRow = createElement('div', { class: 'an-context-color-custom' });
|
|
1149
1225
|
const colorInput = /** @type {HTMLInputElement} */ (createElement('input', { type: 'color', class: 'an-shade-color-input', value: '#ffffff' }));
|
|
1150
1226
|
const customLabel = createElement('span');
|
|
1151
|
-
customLabel.textContent =
|
|
1227
|
+
customLabel.textContent = this.context.locale.contextMenu.customColorLabel;
|
|
1152
1228
|
this._disposers.push(on(colorInput, 'change', () => this._applyCellShade(colorInput.value)));
|
|
1153
1229
|
customRow.appendChild(colorInput);
|
|
1154
1230
|
customRow.appendChild(customLabel);
|
|
@@ -1211,4 +1287,213 @@ export class TableTooltip {
|
|
|
1211
1287
|
this._hideCellShadePopover();
|
|
1212
1288
|
this.context.invoke('editor.afterCommand');
|
|
1213
1289
|
}
|
|
1290
|
+
|
|
1291
|
+
// ---------------------------------------------------------------------------
|
|
1292
|
+
// Table border color popover
|
|
1293
|
+
// ---------------------------------------------------------------------------
|
|
1294
|
+
|
|
1295
|
+
_buildBorderColorPopover() {
|
|
1296
|
+
const pop = createElement('div', { class: 'an-cell-shade-popover' });
|
|
1297
|
+
pop.style.display = 'none';
|
|
1298
|
+
|
|
1299
|
+
const title = createElement('div', { class: 'an-size-popover-title' });
|
|
1300
|
+
pop.appendChild(title);
|
|
1301
|
+
this._borderColorTitleEl = title;
|
|
1302
|
+
|
|
1303
|
+
const palette = createElement('div', { class: 'an-context-color-palette' });
|
|
1304
|
+
SHADE_PRESETS.forEach((color) => {
|
|
1305
|
+
const sw = createElement('div', { class: 'an-context-color-swatch', title: color });
|
|
1306
|
+
sw.style.background = color;
|
|
1307
|
+
this._disposers.push(on(sw, 'click', (e) => {
|
|
1308
|
+
e.stopPropagation();
|
|
1309
|
+
this._applyBorderColor(color);
|
|
1310
|
+
}));
|
|
1311
|
+
palette.appendChild(sw);
|
|
1312
|
+
});
|
|
1313
|
+
pop.appendChild(palette);
|
|
1314
|
+
|
|
1315
|
+
const noColorRow = createElement('div', { class: 'an-context-color-custom' });
|
|
1316
|
+
const noColorBtn = createElement('button', { type: 'button', class: 'an-shade-no-color' });
|
|
1317
|
+
this._disposers.push(on(noColorBtn, 'click', () => this._applyBorderColor('')));
|
|
1318
|
+
noColorRow.appendChild(noColorBtn);
|
|
1319
|
+
pop.appendChild(noColorRow);
|
|
1320
|
+
this._borderColorNoBtn = noColorBtn;
|
|
1321
|
+
|
|
1322
|
+
const customRow = createElement('div', { class: 'an-context-color-custom' });
|
|
1323
|
+
const colorInput = /** @type {HTMLInputElement} */ (createElement('input', { type: 'color', class: 'an-shade-color-input', value: '#000000' }));
|
|
1324
|
+
const customLabel = createElement('span');
|
|
1325
|
+
customLabel.textContent = this.context.locale.contextMenu.customColorLabel;
|
|
1326
|
+
this._disposers.push(on(colorInput, 'change', () => this._applyBorderColor(colorInput.value)));
|
|
1327
|
+
customRow.appendChild(colorInput);
|
|
1328
|
+
customRow.appendChild(customLabel);
|
|
1329
|
+
pop.appendChild(customRow);
|
|
1330
|
+
|
|
1331
|
+
this._disposers.push(
|
|
1332
|
+
on(pop, 'mousedown', (e) => e.preventDefault()),
|
|
1333
|
+
on(pop, 'mouseenter', () => this._clearTimers()),
|
|
1334
|
+
on(pop, 'mouseleave', () => this._scheduleHide()),
|
|
1335
|
+
on(document, 'click', (e) => {
|
|
1336
|
+
const et = /** @type {Node} */ (e.target);
|
|
1337
|
+
if (this._borderColorPopover &&
|
|
1338
|
+
this._borderColorPopover.style.display !== 'none' &&
|
|
1339
|
+
!this._borderColorPopover.contains(et) &&
|
|
1340
|
+
!this._el?.contains(et)) {
|
|
1341
|
+
this._hideBorderColorPopover();
|
|
1342
|
+
}
|
|
1343
|
+
}),
|
|
1344
|
+
);
|
|
1345
|
+
|
|
1346
|
+
return pop;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
_openBorderColorPopover() {
|
|
1350
|
+
if (!this._borderColorPopover) return;
|
|
1351
|
+
const L = this.context.locale.tooltips.table;
|
|
1352
|
+
if (this._borderColorTitleEl) this._borderColorTitleEl.textContent = L.tableBorderColor;
|
|
1353
|
+
if (this._borderColorNoBtn) this._borderColorNoBtn.textContent = L.noBorderColor;
|
|
1354
|
+
|
|
1355
|
+
this._borderColorPopover.style.display = 'block';
|
|
1356
|
+
requestAnimationFrame(() => {
|
|
1357
|
+
if (!this._borderColorPopover || !this._el) return;
|
|
1358
|
+
const pw = this._borderColorPopover.offsetWidth || 170;
|
|
1359
|
+
const ph = this._borderColorPopover.offsetHeight || 120;
|
|
1360
|
+
const tipRect = this._el.getBoundingClientRect();
|
|
1361
|
+
let left = tipRect.left;
|
|
1362
|
+
let top = tipRect.bottom + 6;
|
|
1363
|
+
if (left + pw > globalThis.innerWidth - 8) left = globalThis.innerWidth - pw - 8;
|
|
1364
|
+
if (top + ph > globalThis.innerHeight - 8) top = tipRect.top - ph - 6;
|
|
1365
|
+
this._borderColorPopover.style.left = `${Math.max(8, left)}px`;
|
|
1366
|
+
this._borderColorPopover.style.top = `${Math.max(8, top)}px`;
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
_hideBorderColorPopover() {
|
|
1371
|
+
if (this._borderColorPopover) this._borderColorPopover.style.display = 'none';
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
_applyBorderColor(color) {
|
|
1375
|
+
const table = this._activeTable;
|
|
1376
|
+
if (!table) return;
|
|
1377
|
+
Array.from(table.querySelectorAll('td, th')).forEach((c) => { c.style.borderColor = color; });
|
|
1378
|
+
if (this._borderColorStrip) {
|
|
1379
|
+
this._borderColorStrip.style.background = color || 'transparent';
|
|
1380
|
+
}
|
|
1381
|
+
this._hideBorderColorPopover();
|
|
1382
|
+
this.context.invoke('editor.afterCommand');
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
_syncBorderColorStrip() {
|
|
1386
|
+
if (!this._borderColorStrip || !this._el || this._el.style.display === 'none') return;
|
|
1387
|
+
const firstCell = this._activeTable?.querySelector('td, th');
|
|
1388
|
+
this._borderColorStrip.style.background = firstCell?.style.borderColor || 'transparent';
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// ---------------------------------------------------------------------------
|
|
1392
|
+
// Cell text alignment
|
|
1393
|
+
// ---------------------------------------------------------------------------
|
|
1394
|
+
|
|
1395
|
+
_applyCellAlign(align) {
|
|
1396
|
+
const cells = this._getSelectedCells();
|
|
1397
|
+
cells.forEach((c) => { if (c) c.style.textAlign = align; });
|
|
1398
|
+
this.context.invoke('editor.afterCommand');
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// ---------------------------------------------------------------------------
|
|
1402
|
+
// Toggle header row
|
|
1403
|
+
// ---------------------------------------------------------------------------
|
|
1404
|
+
|
|
1405
|
+
_toggleHeaderRow() {
|
|
1406
|
+
const table = this._activeTable;
|
|
1407
|
+
if (!table) return;
|
|
1408
|
+
const firstRow = table.querySelector('tr');
|
|
1409
|
+
if (!firstRow) return;
|
|
1410
|
+
|
|
1411
|
+
const isInThead = firstRow.closest('thead') !== null;
|
|
1412
|
+
|
|
1413
|
+
if (isInThead) {
|
|
1414
|
+
// Convert th → td and move row to tbody
|
|
1415
|
+
let tbody = table.querySelector('tbody');
|
|
1416
|
+
if (!tbody) {
|
|
1417
|
+
tbody = document.createElement('tbody');
|
|
1418
|
+
table.appendChild(tbody);
|
|
1419
|
+
}
|
|
1420
|
+
Array.from(firstRow.cells).forEach((cell) => {
|
|
1421
|
+
const td = document.createElement('td');
|
|
1422
|
+
td.innerHTML = cell.innerHTML;
|
|
1423
|
+
td.style.cssText = cell.style.cssText;
|
|
1424
|
+
firstRow.replaceChild(td, cell);
|
|
1425
|
+
});
|
|
1426
|
+
tbody.insertBefore(firstRow, tbody.firstChild);
|
|
1427
|
+
const thead = table.querySelector('thead');
|
|
1428
|
+
if (thead && thead.rows.length === 0) thead.remove();
|
|
1429
|
+
} else {
|
|
1430
|
+
// Convert td → th and move row to thead
|
|
1431
|
+
let thead = table.querySelector('thead');
|
|
1432
|
+
if (!thead) {
|
|
1433
|
+
thead = document.createElement('thead');
|
|
1434
|
+
table.insertBefore(thead, table.firstChild);
|
|
1435
|
+
}
|
|
1436
|
+
Array.from(firstRow.cells).forEach((cell) => {
|
|
1437
|
+
const th = document.createElement('th');
|
|
1438
|
+
th.innerHTML = cell.innerHTML;
|
|
1439
|
+
th.style.cssText = cell.style.cssText;
|
|
1440
|
+
firstRow.replaceChild(th, cell);
|
|
1441
|
+
});
|
|
1442
|
+
thead.appendChild(firstRow);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
this.context.invoke('editor.afterCommand');
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
_sortColumn(direction) {
|
|
1449
|
+
const table = this._activeTable;
|
|
1450
|
+
if (!table) return;
|
|
1451
|
+
const cell = this._getCell();
|
|
1452
|
+
if (!cell) return;
|
|
1453
|
+
const colIdx = getVisualColIndex(cell);
|
|
1454
|
+
if (colIdx === -1) return;
|
|
1455
|
+
const tbody = table.querySelector('tbody') || table;
|
|
1456
|
+
const rows = Array.from(tbody.querySelectorAll(':scope > tr'));
|
|
1457
|
+
if (rows.length < 2) return;
|
|
1458
|
+
rows.sort((a, b) => {
|
|
1459
|
+
const aCell = getCellAtVisualCol(a, colIdx);
|
|
1460
|
+
const bCell = getCellAtVisualCol(b, colIdx);
|
|
1461
|
+
const aText = (aCell?.textContent || '').trim();
|
|
1462
|
+
const bText = (bCell?.textContent || '').trim();
|
|
1463
|
+
const aNum = parseFloat(aText);
|
|
1464
|
+
const bNum = parseFloat(bText);
|
|
1465
|
+
if (!isNaN(aNum) && !isNaN(bNum)) {
|
|
1466
|
+
return direction === 'asc' ? aNum - bNum : bNum - aNum;
|
|
1467
|
+
}
|
|
1468
|
+
return direction === 'asc'
|
|
1469
|
+
? aText.localeCompare(bText)
|
|
1470
|
+
: bText.localeCompare(aText);
|
|
1471
|
+
});
|
|
1472
|
+
rows.forEach((row) => tbody.appendChild(row));
|
|
1473
|
+
this.context.invoke('editor.afterCommand');
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
_exportTableCSV() {
|
|
1477
|
+
const table = this._activeTable;
|
|
1478
|
+
if (!table) return;
|
|
1479
|
+
const rows = Array.from(table.querySelectorAll('tr'));
|
|
1480
|
+
const csv = rows.map((row) =>
|
|
1481
|
+
Array.from(row.querySelectorAll('td, th'))
|
|
1482
|
+
.map((cell) => {
|
|
1483
|
+
const text = (cell.textContent || '').trim().replace(/"/g, '""');
|
|
1484
|
+
return `"${text}"`;
|
|
1485
|
+
})
|
|
1486
|
+
.join(',')
|
|
1487
|
+
).join('\n');
|
|
1488
|
+
const blob = new Blob(['' + csv], { type: 'text/csv;charset=utf-8;' });
|
|
1489
|
+
const url = URL.createObjectURL(blob);
|
|
1490
|
+
const a = document.createElement('a');
|
|
1491
|
+
a.href = url;
|
|
1492
|
+
a.download = 'table.csv';
|
|
1493
|
+
a.style.display = 'none';
|
|
1494
|
+
document.body.appendChild(a);
|
|
1495
|
+
a.click();
|
|
1496
|
+
a.remove();
|
|
1497
|
+
URL.revokeObjectURL(url);
|
|
1498
|
+
}
|
|
1214
1499
|
}
|
package/src/js/renderer.js
CHANGED
|
@@ -70,11 +70,14 @@ export function renderLayout(targetEl, options) {
|
|
|
70
70
|
|
|
71
71
|
container.appendChild(editable);
|
|
72
72
|
|
|
73
|
-
// Apply
|
|
74
|
-
// popovers) appended to document.body inherit the
|
|
73
|
+
// Apply theme — also add to body so floating elements (dialogs, tooltips,
|
|
74
|
+
// popovers) appended to document.body inherit the CSS rules.
|
|
75
75
|
if (options.theme === 'dark') {
|
|
76
76
|
container.classList.add('an-theme-dark');
|
|
77
77
|
document.body.classList.add('an-theme-dark');
|
|
78
|
+
} else if (options.theme === 'auto') {
|
|
79
|
+
container.classList.add('an-theme-auto');
|
|
80
|
+
document.body.classList.add('an-theme-auto');
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
// Read-only mode
|