autumnnote 1.6.7 → 1.7.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.
@@ -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 = 'Custom…';
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
  }
@@ -70,11 +70,14 @@ export function renderLayout(targetEl, options) {
70
70
 
71
71
  container.appendChild(editable);
72
72
 
73
- // Apply dark theme — also add to body so floating elements (dialogs, tooltips,
74
- // popovers) appended to document.body inherit the dark CSS rules.
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
@@ -108,6 +108,12 @@
108
108
  user-select: none;
109
109
  // Round the top corners to match the container border-radius
110
110
  border-radius: $an-radius $an-radius 0 0;
111
+ overflow-x: auto;
112
+ scrollbar-width: none;
113
+
114
+ &::-webkit-scrollbar {
115
+ display: none;
116
+ }
111
117
  }
112
118
 
113
119
  .an-btn-group {
@@ -477,6 +483,26 @@
477
483
  padding: 0;
478
484
  }
479
485
  }
486
+
487
+ // Code block line numbers (toggled via CodeTooltip)
488
+ .an-code-line-numbers {
489
+ position: relative;
490
+ padding-left: 3.5em !important;
491
+
492
+ &::before {
493
+ content: '';
494
+ position: absolute;
495
+ left: 0;
496
+ top: 0;
497
+ bottom: 0;
498
+ width: 3em;
499
+ background: rgba(0, 0, 0, 0.04);
500
+ border-right: 1px solid rgba(0, 0, 0, 0.1);
501
+ pointer-events: none;
502
+ z-index: 0;
503
+ }
504
+ }
505
+
480
506
  a { color: $an-primary; }
481
507
  hr { border: none; border-top: 2px solid $an-border; margin: 1em 0; }
482
508
  img.an-image {
@@ -1938,6 +1964,11 @@ $an-video-accent: #8b5cf6; // violet-500
1938
1964
  color: #cdd6f4;
1939
1965
  }
1940
1966
 
1967
+ .an-code-line-numbers::before {
1968
+ background: rgba(255, 255, 255, 0.05);
1969
+ border-right-color: rgba(255, 255, 255, 0.1);
1970
+ }
1971
+
1941
1972
  hr { border-color: #3f3f5f; }
1942
1973
 
1943
1974
  table, .an-table {
@@ -2328,6 +2359,156 @@ $an-video-accent: #8b5cf6; // violet-500
2328
2359
  color: $an-dark-muted;
2329
2360
  }
2330
2361
 
2362
+ // =============================================================================
2363
+ // Auto theme — mirrors dark theme when OS prefers dark and no explicit theme set
2364
+ // =============================================================================
2365
+
2366
+ @media (prefers-color-scheme: dark) {
2367
+ .an-theme-auto {
2368
+ border-color: #3f3f5f;
2369
+ background: #1e1e2e;
2370
+ color: #cdd6f4;
2371
+
2372
+ &.an-focused {
2373
+ border-color: #89b4fa;
2374
+ box-shadow: 0 0 0 3px rgba(#89b4fa, 0.2);
2375
+ }
2376
+
2377
+ .an-toolbar {
2378
+ background: #181825;
2379
+ border-color: #3f3f5f;
2380
+ }
2381
+
2382
+ .an-btn {
2383
+ color: #cdd6f4;
2384
+
2385
+ &:hover {
2386
+ background: #2a2a3e;
2387
+ border-color: #3f3f5f;
2388
+ color: #cdd6f4;
2389
+
2390
+ svg { stroke: #cdd6f4; }
2391
+ }
2392
+
2393
+ &.active {
2394
+ background: #1a3a5c;
2395
+ border-color: #89b4fa;
2396
+ color: #89b4fa;
2397
+
2398
+ svg { stroke: #89b4fa; }
2399
+ }
2400
+
2401
+ &.an-btn-primary {
2402
+ background: #3b5bdb;
2403
+ border-color: #3b5bdb;
2404
+ color: #fff;
2405
+ }
2406
+ }
2407
+
2408
+ .an-select {
2409
+ background-color: #1e1e2e;
2410
+ border-color: #3f3f5f;
2411
+ color: #cdd6f4;
2412
+
2413
+ &:hover, &:focus { border-color: #89b4fa; }
2414
+ }
2415
+
2416
+ .an-editable {
2417
+ background: #1e1e2e;
2418
+ color: #cdd6f4;
2419
+
2420
+ a { color: #89b4fa; }
2421
+
2422
+ blockquote {
2423
+ border-color: #3f3f5f;
2424
+ color: #a6adc8;
2425
+ }
2426
+
2427
+ pre:not([class*='language-']),
2428
+ code:not([class*='language-']) {
2429
+ background: #313244;
2430
+ color: #cdd6f4;
2431
+ }
2432
+
2433
+ hr { border-color: #3f3f5f; }
2434
+
2435
+ table, .an-table {
2436
+ td, th { border-color: #3f3f5f; }
2437
+ th { background: #313244; }
2438
+ }
2439
+
2440
+ &.an-placeholder::before { color: #6c7086; }
2441
+
2442
+ figcaption.an-figcaption { color: #6c7086; }
2443
+ }
2444
+
2445
+ .an-statusbar {
2446
+ background: #181825;
2447
+ border-color: #3f3f5f;
2448
+ color: #6c7086;
2449
+ }
2450
+
2451
+ .an-link-tooltip,
2452
+ .an-contextmenu {
2453
+ background: #24273a;
2454
+ border-color: #3f3f5f;
2455
+ color: #cdd6f4;
2456
+ }
2457
+
2458
+ .an-link-tooltip-url { color: #89b4fa; }
2459
+
2460
+ .an-link-tooltip-btn {
2461
+ color: #a6adc8;
2462
+
2463
+ &:hover {
2464
+ background: #2a2a3e;
2465
+ color: #cdd6f4;
2466
+ }
2467
+ }
2468
+
2469
+ .an-dialog-overlay { background: rgba(0, 0, 0, 0.65); }
2470
+
2471
+ .an-dialog-box {
2472
+ background: #24273a;
2473
+ color: #cdd6f4;
2474
+ border: 1px solid #3f3f5f;
2475
+ }
2476
+
2477
+ .an-dialog-title { color: #cdd6f4; }
2478
+
2479
+ .an-input {
2480
+ background: #313244;
2481
+ border-color: #3f3f5f;
2482
+ color: #cdd6f4;
2483
+
2484
+ &:focus {
2485
+ border-color: #89b4fa;
2486
+ box-shadow: 0 0 0 3px rgba(#89b4fa, 0.15);
2487
+ }
2488
+ }
2489
+
2490
+ .an-color-popup,
2491
+ .an-table-picker-popup,
2492
+ .an-size-popover,
2493
+ .an-cell-shade-popover {
2494
+ background: #24273a;
2495
+ border-color: #3f3f5f;
2496
+ color: #cdd6f4;
2497
+ }
2498
+
2499
+ .an-context-color-swatch {
2500
+ border-color: rgba(255, 255, 255, 0.15);
2501
+ &:hover { border-color: rgba(255, 255, 255, 0.5); }
2502
+ }
2503
+
2504
+ .an-label { color: #cdd6f4; }
2505
+ }
2506
+
2507
+ .an-theme-auto .an-editable ul.an-checklist li:has(input[type='checkbox']:checked) {
2508
+ color: $an-dark-muted;
2509
+ }
2510
+ }
2511
+
2331
2512
  // =============================================================================
2332
2513
  // Print styles
2333
2514
  // =============================================================================
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AutumnNote – TypeScript declarations
3
- * @version 1.0.9
3
+ * @version 1.7.0
4
4
  */
5
5
 
6
6
  // ---------------------------------------------------------------------------
@@ -67,7 +67,7 @@ export interface AsnOptions {
67
67
  /** Top offset in px for sticky toolbar (e.g. height of a fixed nav bar). */
68
68
  stickyToolbarOffset?: number;
69
69
  /** Colour theme: 'light' (default) | 'dark'. */
70
- theme?: 'light' | 'dark';
70
+ theme?: 'light' | 'dark' | 'auto';
71
71
  /** Auto-load Prism.js for syntax highlighting inside code blocks. */
72
72
  codeHighlight?: boolean;
73
73
  /** CDN base URL for Prism assets. */
@@ -158,8 +158,13 @@ export interface MentionOptions {
158
158
  maxResults?: number;
159
159
  /** Debounce delay in ms before calling onSearch. Default: 200. */
160
160
  debounce?: number;
161
- /** Required. Called with (query, callback) to fetch matching items. */
162
- onSearch: (query: string, callback: (items: MentionItem[]) => void) => void;
161
+ /**
162
+ * Called to fetch matching items for the given query.
163
+ * Supports both callback and Promise styles:
164
+ * - Callback: `onSearch(query, callback) { callback(items); }`
165
+ * - Promise: `async onSearch(query) { return items; }`
166
+ */
167
+ onSearch: (query: string, callback: (items: MentionItem[]) => void) => void | Promise<MentionItem[]>;
163
168
  /** Optional. Return custom HTML string for the inserted chip, or null to use the default. */
164
169
  onInsert?: (item: MentionItem) => string | null;
165
170
  /** CSS class added to the inserted mention chip. Default: 'an-mention'. */
@@ -379,6 +384,12 @@ export declare class Context {
379
384
  */
380
385
  clearHistory(): void;
381
386
 
387
+ /** Returns the number of available undo steps. */
388
+ getUndoCount(): number;
389
+
390
+ /** Returns the number of available redo steps. */
391
+ getRedoCount(): number;
392
+
382
393
  /** Returns true when the editor has no meaningful content. */
383
394
  isEmpty(): boolean;
384
395
 
@@ -400,6 +411,9 @@ export declare class Context {
400
411
  /** Returns the current character count (excluding newlines) of the editor content. */
401
412
  getCharCount(): number;
402
413
 
414
+ /** Returns an array of heading objects representing the table of contents. */
415
+ getTableOfContents(): { level: number; text: string; element: HTMLElement }[];
416
+
403
417
  /** Downloads the editor content as an HTML file. */
404
418
  downloadHTML(filename?: string): void;
405
419