autumnnote 1.7.0 → 1.8.1
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 +15 -3
- package/dist/autumnnote.css +24 -1
- package/dist/autumnnote.es.js +509 -134
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +587 -195
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/js/Context.js +26 -1
- package/src/js/core/markdown.js +32 -6
- package/src/js/editing/Style.js +288 -159
- package/src/js/editing/Table.js +10 -2
- package/src/js/editing/Typing.js +21 -4
- package/src/js/i18n/de.js +17 -0
- package/src/js/i18n/en.js +8 -0
- package/src/js/i18n/es.js +17 -0
- package/src/js/i18n/fr.js +18 -1
- package/src/js/i18n/ja.js +17 -0
- package/src/js/i18n/ko.js +17 -0
- package/src/js/i18n/vi.js +15 -0
- package/src/js/i18n/zh.js +17 -0
- package/src/js/index.js +5 -2
- package/src/js/module/Buttons.js +49 -0
- package/src/js/module/Clipboard.js +13 -1
- package/src/js/module/FindReplace.js +24 -5
- package/src/js/module/ImageDialog.js +1 -0
- package/src/js/module/ImageResizer.js +8 -7
- package/src/js/module/Mention.js +13 -2
- package/src/js/module/TableTooltip.js +21 -0
- package/src/styles/autumnnote.scss +23 -3
- package/types/index.d.ts +84 -3
package/src/js/editing/Typing.js
CHANGED
|
@@ -17,6 +17,25 @@ const _FA_PATTERN = /\bfa-/;
|
|
|
17
17
|
const isFAIcon = (n) => !!(n?.nodeName === 'I' && _FA_PATTERN.test(n.className || ''));
|
|
18
18
|
const isZwsAnchor = (n) => !!(n?.nodeType === Node.TEXT_NODE && (n.textContent === '\u200B' || n.textContent === ''));
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Extracts the content from `startContainer:startOffset` to the end of `li`.
|
|
22
|
+
* Returns an empty fragment if the range is invalid (e.g. detached node).
|
|
23
|
+
* @param {Range} nativeRange
|
|
24
|
+
* @param {Element} li
|
|
25
|
+
* @returns {DocumentFragment}
|
|
26
|
+
*/
|
|
27
|
+
function extractAfterContent(nativeRange, li) {
|
|
28
|
+
try {
|
|
29
|
+
const r = document.createRange();
|
|
30
|
+
r.setStart(nativeRange.startContainer, nativeRange.startOffset);
|
|
31
|
+
r.setEnd(li, li.childNodes.length);
|
|
32
|
+
return r.extractContents();
|
|
33
|
+
} catch (_) {
|
|
34
|
+
void _;
|
|
35
|
+
return document.createDocumentFragment();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
20
39
|
/**
|
|
21
40
|
* Handles special keydown behaviour inside the editor.
|
|
22
41
|
* @param {KeyboardEvent} event
|
|
@@ -312,16 +331,14 @@ export function handleKeydown(event, editable, options = {}) {
|
|
|
312
331
|
if (!nativeRange.collapsed) {
|
|
313
332
|
nativeRange.deleteContents();
|
|
314
333
|
// nativeRange is now collapsed at the deletion point; re-read it
|
|
334
|
+
if (sel.rangeCount === 0 || !checkLi.isConnected) return true;
|
|
315
335
|
nativeRange = sel.getRangeAt(0);
|
|
316
336
|
}
|
|
317
337
|
|
|
318
338
|
// 3. Extract everything from cursor to end of li into afterFrag.
|
|
319
339
|
// Use startContainer/startOffset (cursor position after potential delete),
|
|
320
340
|
// NOT endContainer/endOffset which is wrong for non-collapsed ranges.
|
|
321
|
-
const
|
|
322
|
-
afterRange.setStart(nativeRange.startContainer, nativeRange.startOffset);
|
|
323
|
-
afterRange.setEnd(checkLi, checkLi.childNodes.length);
|
|
324
|
-
const afterFrag = afterRange.extractContents();
|
|
341
|
+
const afterFrag = extractAfterContent(nativeRange, checkLi);
|
|
325
342
|
|
|
326
343
|
// 4. Build the new checklist item with the extracted "after" content.
|
|
327
344
|
const newLi = document.createElement('li');
|
package/src/js/i18n/de.js
CHANGED
|
@@ -151,6 +151,7 @@ export const de = {
|
|
|
151
151
|
findPlaceholder: 'Suchen…',
|
|
152
152
|
searchAriaLabel: 'Suchtext',
|
|
153
153
|
caseSensitive: '\u00a0Groß-/Kleinschreibung',
|
|
154
|
+
wholeWord: 'Ganzes Wort',
|
|
154
155
|
prevBtn: '← Zurück',
|
|
155
156
|
nextBtn: 'Weiter →',
|
|
156
157
|
replacePlaceholder: 'Ersetzen durch…',
|
|
@@ -293,7 +294,16 @@ export const de = {
|
|
|
293
294
|
columnWidth: 'Spaltenbreite',
|
|
294
295
|
rowHeight: 'Zeilenhöhe',
|
|
295
296
|
tableBorderWidth: 'Tabellenrahmenbreite',
|
|
297
|
+
tableBorderColor: 'Tabellenrahmenfarbe',
|
|
296
298
|
deleteTable: 'Tabelle löschen',
|
|
299
|
+
cellAlignLeft: 'Linksbündig',
|
|
300
|
+
cellAlignCenter: 'Zentrieren',
|
|
301
|
+
cellAlignRight: 'Rechtsbündig',
|
|
302
|
+
cellAlignJustify: 'Blocksatz',
|
|
303
|
+
toggleHeaderRow: 'Kopfzeile umschalten',
|
|
304
|
+
cellBackground: 'Zellhintergrund',
|
|
305
|
+
noShading: 'Kein Hintergrund',
|
|
306
|
+
noBorderColor: 'Keine Rahmenfarbe',
|
|
297
307
|
columnWidthPx: 'Spaltenbreite (px)',
|
|
298
308
|
rowHeightPx: 'Zeilenhöhe (px)',
|
|
299
309
|
tableBorderWidthPx: 'Tabellenrahmenbreite (px)',
|
|
@@ -325,4 +335,11 @@ export const de = {
|
|
|
325
335
|
imageSize: (maxSize) =>
|
|
326
336
|
`Die Bilddatei ist zu groß. Die maximal zulässige Größe beträgt ${maxSize} MB.`,
|
|
327
337
|
},
|
|
338
|
+
|
|
339
|
+
autoSaveRestore: {
|
|
340
|
+
found: 'Entwurf gefunden. Wiederherstellen?',
|
|
341
|
+
foundAt: 'Entwurf vom {date}. Wiederherstellen?',
|
|
342
|
+
restore: 'Wiederherstellen',
|
|
343
|
+
discard: 'Verwerfen',
|
|
344
|
+
},
|
|
328
345
|
};
|
package/src/js/i18n/en.js
CHANGED
|
@@ -153,6 +153,7 @@ export const en = {
|
|
|
153
153
|
findPlaceholder: 'Find\u2026',
|
|
154
154
|
searchAriaLabel: 'Search text',
|
|
155
155
|
caseSensitive: '\u00a0Case sensitive',
|
|
156
|
+
wholeWord: 'Whole Word',
|
|
156
157
|
prevBtn: '\u2190 Prev',
|
|
157
158
|
nextBtn: 'Next \u2192',
|
|
158
159
|
replacePlaceholder: 'Replace with\u2026',
|
|
@@ -164,6 +165,13 @@ export const en = {
|
|
|
164
165
|
close: '\u00d7',
|
|
165
166
|
},
|
|
166
167
|
|
|
168
|
+
autoSaveRestore: {
|
|
169
|
+
found: 'Draft found. Restore?',
|
|
170
|
+
foundAt: 'Draft from {date}. Restore?',
|
|
171
|
+
restore: 'Restore',
|
|
172
|
+
discard: 'Discard',
|
|
173
|
+
},
|
|
174
|
+
|
|
167
175
|
shortcutsDialog: {
|
|
168
176
|
title: 'Keyboard Shortcuts',
|
|
169
177
|
ariaLabel: 'Keyboard Shortcuts',
|
package/src/js/i18n/es.js
CHANGED
|
@@ -151,6 +151,7 @@ export const es = {
|
|
|
151
151
|
findPlaceholder: 'Buscar…',
|
|
152
152
|
searchAriaLabel: 'Texto de búsqueda',
|
|
153
153
|
caseSensitive: '\u00a0Distinguir mayúsculas',
|
|
154
|
+
wholeWord: 'Palabra completa',
|
|
154
155
|
prevBtn: '← Anterior',
|
|
155
156
|
nextBtn: 'Siguiente →',
|
|
156
157
|
replacePlaceholder: 'Reemplazar con…',
|
|
@@ -293,7 +294,16 @@ export const es = {
|
|
|
293
294
|
columnWidth: 'Ancho de columna',
|
|
294
295
|
rowHeight: 'Alto de fila',
|
|
295
296
|
tableBorderWidth: 'Grosor del borde de la tabla',
|
|
297
|
+
tableBorderColor: 'Color del borde de la tabla',
|
|
296
298
|
deleteTable: 'Eliminar tabla',
|
|
299
|
+
cellAlignLeft: 'Alinear a la izquierda',
|
|
300
|
+
cellAlignCenter: 'Centrar',
|
|
301
|
+
cellAlignRight: 'Alinear a la derecha',
|
|
302
|
+
cellAlignJustify: 'Justificar',
|
|
303
|
+
toggleHeaderRow: 'Activar fila de encabezado',
|
|
304
|
+
cellBackground: 'Fondo de celda',
|
|
305
|
+
noShading: 'Sin fondo',
|
|
306
|
+
noBorderColor: 'Sin color de borde',
|
|
297
307
|
columnWidthPx: 'Ancho de columna (px)',
|
|
298
308
|
rowHeightPx: 'Alto de fila (px)',
|
|
299
309
|
tableBorderWidthPx: 'Grosor del borde (px)',
|
|
@@ -325,4 +335,11 @@ export const es = {
|
|
|
325
335
|
imageSize: (maxSize) =>
|
|
326
336
|
`El archivo de imagen es demasiado grande. El tamaño máximo permitido es ${maxSize} MB.`,
|
|
327
337
|
},
|
|
338
|
+
|
|
339
|
+
autoSaveRestore: {
|
|
340
|
+
found: 'Borrador encontrado. ¿Restaurar?',
|
|
341
|
+
foundAt: 'Borrador del {date}. ¿Restaurar?',
|
|
342
|
+
restore: 'Restaurar',
|
|
343
|
+
discard: 'Descartar',
|
|
344
|
+
},
|
|
328
345
|
};
|
package/src/js/i18n/fr.js
CHANGED
|
@@ -152,6 +152,7 @@ export const fr = {
|
|
|
152
152
|
findPlaceholder: 'Rechercher\u2026',
|
|
153
153
|
searchAriaLabel: 'Texte à rechercher',
|
|
154
154
|
caseSensitive: '\u00a0Respecter la casse',
|
|
155
|
+
wholeWord: 'Mot entier',
|
|
155
156
|
prevBtn: '\u2190 Préc.',
|
|
156
157
|
nextBtn: 'Suiv. \u2192',
|
|
157
158
|
replacePlaceholder: 'Remplacer par\u2026',
|
|
@@ -294,7 +295,16 @@ export const fr = {
|
|
|
294
295
|
columnWidth: 'Largeur de colonne',
|
|
295
296
|
rowHeight: 'Hauteur de ligne',
|
|
296
297
|
tableBorderWidth: 'Épaisseur des bordures',
|
|
298
|
+
tableBorderColor: 'Couleur de bordure',
|
|
297
299
|
deleteTable: 'Supprimer le tableau',
|
|
300
|
+
cellAlignLeft: 'Aligner \u00e0 gauche',
|
|
301
|
+
cellAlignCenter: 'Centrer',
|
|
302
|
+
cellAlignRight: 'Aligner \u00e0 droite',
|
|
303
|
+
cellAlignJustify: 'Justifier',
|
|
304
|
+
toggleHeaderRow: 'Ligne d\'en-t\u00eate',
|
|
305
|
+
cellBackground: 'Couleur de fond',
|
|
306
|
+
noShading: 'Aucun fond',
|
|
307
|
+
noBorderColor: 'Aucune bordure',
|
|
298
308
|
columnWidthPx: 'Largeur de colonne (px)',
|
|
299
309
|
rowHeightPx: 'Hauteur de ligne (px)',
|
|
300
310
|
tableBorderWidthPx: 'Épaisseur des bordures (px)',
|
|
@@ -326,4 +336,11 @@ export const fr = {
|
|
|
326
336
|
imageSize: (maxSize) =>
|
|
327
337
|
`Le fichier image est trop volumineux. La taille maximale autorisée est de ${maxSize}\u00a0Mo.`,
|
|
328
338
|
},
|
|
329
|
-
|
|
339
|
+
|
|
340
|
+
autoSaveRestore: {
|
|
341
|
+
found: 'Brouillon trouvé. Restaurer ?',
|
|
342
|
+
foundAt: 'Brouillon du {date}. Restaurer ?',
|
|
343
|
+
restore: 'Restaurer',
|
|
344
|
+
discard: 'Ignorer',
|
|
345
|
+
},
|
|
346
|
+
};
|
package/src/js/i18n/ja.js
CHANGED
|
@@ -152,6 +152,7 @@ export const ja = {
|
|
|
152
152
|
findPlaceholder: '検索\u2026',
|
|
153
153
|
searchAriaLabel: '検索テキスト',
|
|
154
154
|
caseSensitive: '\u00a0大文字/小文字を区別',
|
|
155
|
+
wholeWord: '単語単位',
|
|
155
156
|
prevBtn: '\u2190 前へ',
|
|
156
157
|
nextBtn: '次へ \u2192',
|
|
157
158
|
replacePlaceholder: '置換後\u2026',
|
|
@@ -294,7 +295,16 @@ export const ja = {
|
|
|
294
295
|
columnWidth: '列幅',
|
|
295
296
|
rowHeight: '行の高さ',
|
|
296
297
|
tableBorderWidth: 'テーブルの枠幅',
|
|
298
|
+
tableBorderColor: 'テーブルの枠線色',
|
|
297
299
|
deleteTable: 'テーブルを削除',
|
|
300
|
+
cellAlignLeft: '左揃え',
|
|
301
|
+
cellAlignCenter: '中央揃え',
|
|
302
|
+
cellAlignRight: '右揃え',
|
|
303
|
+
cellAlignJustify: '両端揃え',
|
|
304
|
+
toggleHeaderRow: 'ヘッダー行の切り替え',
|
|
305
|
+
cellBackground: 'セル背景色',
|
|
306
|
+
noShading: '背景色なし',
|
|
307
|
+
noBorderColor: '枠線色なし',
|
|
298
308
|
columnWidthPx: '列幅 (px)',
|
|
299
309
|
rowHeightPx: '行の高さ (px)',
|
|
300
310
|
tableBorderWidthPx: 'テーブルの枠幅 (px)',
|
|
@@ -326,4 +336,11 @@ export const ja = {
|
|
|
326
336
|
imageSize: (maxSize) =>
|
|
327
337
|
`画像ファイルが大きすぎます。最大許容サイズは ${maxSize} MB です。`,
|
|
328
338
|
},
|
|
339
|
+
|
|
340
|
+
autoSaveRestore: {
|
|
341
|
+
found: '下書きが見つかりました。復元しますか?',
|
|
342
|
+
foundAt: '{date} の下書きが見つかりました。復元しますか?',
|
|
343
|
+
restore: '復元',
|
|
344
|
+
discard: '破棄',
|
|
345
|
+
},
|
|
329
346
|
};
|
package/src/js/i18n/ko.js
CHANGED
|
@@ -151,6 +151,7 @@ export const ko = {
|
|
|
151
151
|
findPlaceholder: '찾기…',
|
|
152
152
|
searchAriaLabel: '검색 텍스트',
|
|
153
153
|
caseSensitive: '\u00a0대소문자 구분',
|
|
154
|
+
wholeWord: '전체 단어',
|
|
154
155
|
prevBtn: '← 이전',
|
|
155
156
|
nextBtn: '다음 →',
|
|
156
157
|
replacePlaceholder: '바꿀 내용…',
|
|
@@ -293,7 +294,16 @@ export const ko = {
|
|
|
293
294
|
columnWidth: '열 너비',
|
|
294
295
|
rowHeight: '행 높이',
|
|
295
296
|
tableBorderWidth: '표 테두리 너비',
|
|
297
|
+
tableBorderColor: '표 테두리 색상',
|
|
296
298
|
deleteTable: '표 삭제',
|
|
299
|
+
cellAlignLeft: '왼쪽 정렬',
|
|
300
|
+
cellAlignCenter: '가운데 정렬',
|
|
301
|
+
cellAlignRight: '오른쪽 정렬',
|
|
302
|
+
cellAlignJustify: '양쪽 정렬',
|
|
303
|
+
toggleHeaderRow: '머리글 행 전환',
|
|
304
|
+
cellBackground: '셀 배경색',
|
|
305
|
+
noShading: '배경 없음',
|
|
306
|
+
noBorderColor: '테두리 색 없음',
|
|
297
307
|
columnWidthPx: '열 너비 (px)',
|
|
298
308
|
rowHeightPx: '행 높이 (px)',
|
|
299
309
|
tableBorderWidthPx: '표 테두리 너비 (px)',
|
|
@@ -325,4 +335,11 @@ export const ko = {
|
|
|
325
335
|
imageSize: (maxSize) =>
|
|
326
336
|
`이미지 파일이 너무 큽니다. 최대 허용 크기는 ${maxSize} MB입니다.`,
|
|
327
337
|
},
|
|
338
|
+
|
|
339
|
+
autoSaveRestore: {
|
|
340
|
+
found: '초안을 찾았습니다. 복원하시겠습니까?',
|
|
341
|
+
foundAt: '{date}의 초안을 찾았습니다. 복원하시겠습니까?',
|
|
342
|
+
restore: '복원',
|
|
343
|
+
discard: '삭제',
|
|
344
|
+
},
|
|
328
345
|
};
|
package/src/js/i18n/vi.js
CHANGED
|
@@ -152,6 +152,7 @@ export const vi = {
|
|
|
152
152
|
findPlaceholder: 'Tìm\u2026',
|
|
153
153
|
searchAriaLabel: 'Văn bản tìm kiếm',
|
|
154
154
|
caseSensitive: '\u00a0Phân biệt hoa thường',
|
|
155
|
+
wholeWord: 'Toàn bộ từ',
|
|
155
156
|
prevBtn: '\u2190 Trước',
|
|
156
157
|
nextBtn: 'Tiếp \u2192',
|
|
157
158
|
replacePlaceholder: 'Thay thế bằng\u2026',
|
|
@@ -294,9 +295,16 @@ export const vi = {
|
|
|
294
295
|
columnWidth: 'Chiều rộng cột',
|
|
295
296
|
rowHeight: 'Chiều cao hàng',
|
|
296
297
|
tableBorderWidth: 'Độ rộng viền bảng',
|
|
298
|
+
tableBorderColor: 'Màu viền bảng',
|
|
297
299
|
deleteTable: 'Xóa bảng',
|
|
300
|
+
cellAlignLeft: 'Căn trái',
|
|
301
|
+
cellAlignCenter: 'Căn giữa',
|
|
302
|
+
cellAlignRight: 'Căn phải',
|
|
303
|
+
cellAlignJustify: 'Căn đều',
|
|
304
|
+
toggleHeaderRow: 'Bật/tắt hàng tiêu đề',
|
|
298
305
|
cellBackground: 'Màu Nền Ô',
|
|
299
306
|
noShading: 'Xóa Màu Nền',
|
|
307
|
+
noBorderColor: 'Xóa màu viền',
|
|
300
308
|
columnWidthPx: 'Chiều rộng cột (px)',
|
|
301
309
|
rowHeightPx: 'Chiều cao hàng (px)',
|
|
302
310
|
tableBorderWidthPx: 'Độ rộng viền bảng (px)',
|
|
@@ -328,4 +336,11 @@ export const vi = {
|
|
|
328
336
|
imageSize: (maxSize) =>
|
|
329
337
|
`Tệp hình ảnh quá lớn. Kích thước tối đa cho phép là ${maxSize} MB.`,
|
|
330
338
|
},
|
|
339
|
+
|
|
340
|
+
autoSaveRestore: {
|
|
341
|
+
found: 'Tìm thấy bản nháp. Khôi phục?',
|
|
342
|
+
foundAt: 'Bản nháp từ {date}. Khôi phục?',
|
|
343
|
+
restore: 'Khôi phục',
|
|
344
|
+
discard: 'Bỏ qua',
|
|
345
|
+
},
|
|
331
346
|
};
|
package/src/js/i18n/zh.js
CHANGED
|
@@ -152,6 +152,7 @@ export const zh = {
|
|
|
152
152
|
findPlaceholder: '查找\u2026',
|
|
153
153
|
searchAriaLabel: '搜索文字',
|
|
154
154
|
caseSensitive: '\u00a0区分大小写',
|
|
155
|
+
wholeWord: '全字匹配',
|
|
155
156
|
prevBtn: '\u2190 上一个',
|
|
156
157
|
nextBtn: '下一个 \u2192',
|
|
157
158
|
replacePlaceholder: '替换为\u2026',
|
|
@@ -294,7 +295,16 @@ export const zh = {
|
|
|
294
295
|
columnWidth: '列宽',
|
|
295
296
|
rowHeight: '行高',
|
|
296
297
|
tableBorderWidth: '表格边框宽度',
|
|
298
|
+
tableBorderColor: '表格边框颜色',
|
|
297
299
|
deleteTable: '删除表格',
|
|
300
|
+
cellAlignLeft: '左对齐',
|
|
301
|
+
cellAlignCenter: '居中对齐',
|
|
302
|
+
cellAlignRight: '右对齐',
|
|
303
|
+
cellAlignJustify: '两端对齐',
|
|
304
|
+
toggleHeaderRow: '切换标题行',
|
|
305
|
+
cellBackground: '单元格背景',
|
|
306
|
+
noShading: '无底纹',
|
|
307
|
+
noBorderColor: '无边框颜色',
|
|
298
308
|
columnWidthPx: '列宽 (px)',
|
|
299
309
|
rowHeightPx: '行高 (px)',
|
|
300
310
|
tableBorderWidthPx: '表格边框宽度 (px)',
|
|
@@ -326,4 +336,11 @@ export const zh = {
|
|
|
326
336
|
imageSize: (maxSize) =>
|
|
327
337
|
`图片文件过大。最大允许大小为 ${maxSize} MB。`,
|
|
328
338
|
},
|
|
339
|
+
|
|
340
|
+
autoSaveRestore: {
|
|
341
|
+
found: '找到草稿。是否恢复?',
|
|
342
|
+
foundAt: '发现 {date} 的草稿。是否恢复?',
|
|
343
|
+
restore: '恢复',
|
|
344
|
+
discard: '放弃',
|
|
345
|
+
},
|
|
329
346
|
};
|
package/src/js/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// @ts-ignore
|
|
13
13
|
import '../styles/autumnnote.scss';
|
|
14
14
|
import { Context, _customModules, _globalPlugins } from './Context.js';
|
|
15
|
-
import { registerButton } from './module/Buttons.js';
|
|
15
|
+
import { registerButton, buttons } from './module/Buttons.js';
|
|
16
16
|
import { defaultOptions } from './settings.js';
|
|
17
17
|
|
|
18
18
|
// Snapshot of factory defaults taken at module-load time (before any setDefaults() calls)
|
|
@@ -140,8 +140,11 @@ const AutumnNote = {
|
|
|
140
140
|
*/
|
|
141
141
|
registerButton(btnDef) { registerButton(btnDef); return this; },
|
|
142
142
|
|
|
143
|
+
/** All pre-built button definitions — accessible in every module format including UMD/CJS. */
|
|
144
|
+
buttons,
|
|
145
|
+
|
|
143
146
|
/** Library version */
|
|
144
|
-
version: '1.
|
|
147
|
+
version: '1.8.0',
|
|
145
148
|
};
|
|
146
149
|
|
|
147
150
|
// ---------------------------------------------------------------------------
|
package/src/js/module/Buttons.js
CHANGED
|
@@ -348,3 +348,52 @@ export const defaultToolbar = [
|
|
|
348
348
|
[hrBtn, linkBtn, imageBtn, videoBtn, tableBtn, emojiBtn, iconBtn],
|
|
349
349
|
[removeFormatBtn, codeviewBtn, fullscreenBtn, findBtn, printBtn, shortcutsBtn],
|
|
350
350
|
];
|
|
351
|
+
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
// Buttons namespace — all pre-built button definitions in a single object.
|
|
354
|
+
// Exposed as AutumnNote.buttons so UMD / CJS consumers can access them
|
|
355
|
+
// without named imports: AutumnNote.buttons.boldBtn, etc.
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
|
|
358
|
+
export const buttons = {
|
|
359
|
+
boldBtn,
|
|
360
|
+
italicBtn,
|
|
361
|
+
underlineBtn,
|
|
362
|
+
strikeBtn,
|
|
363
|
+
superscriptBtn,
|
|
364
|
+
subscriptBtn,
|
|
365
|
+
alignLeftBtn,
|
|
366
|
+
alignCenterBtn,
|
|
367
|
+
alignRightBtn,
|
|
368
|
+
alignJustifyBtn,
|
|
369
|
+
ulBtn,
|
|
370
|
+
olBtn,
|
|
371
|
+
indentBtn,
|
|
372
|
+
outdentBtn,
|
|
373
|
+
undoBtn,
|
|
374
|
+
redoBtn,
|
|
375
|
+
hrBtn,
|
|
376
|
+
linkBtn,
|
|
377
|
+
imageBtn,
|
|
378
|
+
videoBtn,
|
|
379
|
+
emojiBtn,
|
|
380
|
+
iconBtn,
|
|
381
|
+
tableBtn,
|
|
382
|
+
fontSizeBtn,
|
|
383
|
+
removeFormatBtn,
|
|
384
|
+
directionBtn,
|
|
385
|
+
fontFamilyBtn,
|
|
386
|
+
paragraphStyleBtn,
|
|
387
|
+
lineHeightBtn,
|
|
388
|
+
codeviewBtn,
|
|
389
|
+
fullscreenBtn,
|
|
390
|
+
shortcutsBtn,
|
|
391
|
+
findBtn,
|
|
392
|
+
findReplaceBtn,
|
|
393
|
+
inlineCodeBtn,
|
|
394
|
+
checklistBtn,
|
|
395
|
+
printBtn,
|
|
396
|
+
foreColorBtn,
|
|
397
|
+
backColorBtn,
|
|
398
|
+
defaultToolbar,
|
|
399
|
+
};
|
|
@@ -187,6 +187,18 @@ export class Clipboard {
|
|
|
187
187
|
const forcePlain = this._forcePlain;
|
|
188
188
|
this._forcePlain = false;
|
|
189
189
|
|
|
190
|
+
// Enforce maxPasteSize limit (default 5 MB)
|
|
191
|
+
const maxBytes = (this.options.maxPasteSize ?? 5) * 1024 * 1024;
|
|
192
|
+
if (maxBytes > 0) {
|
|
193
|
+
const text = clipboardData.getData('text/plain') || '';
|
|
194
|
+
const html = clipboardData.getData('text/html') || '';
|
|
195
|
+
const size = Math.max(text.length, html.length);
|
|
196
|
+
if (size > maxBytes) {
|
|
197
|
+
event.preventDefault();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
190
202
|
// 1. Image file in clipboard (screenshot, copy-image-from-browser, etc.)
|
|
191
203
|
if (clipboardData.items) {
|
|
192
204
|
const imageItems = Array.from(clipboardData.items).filter(
|
|
@@ -353,7 +365,7 @@ export class Clipboard {
|
|
|
353
365
|
const mime = /:(.*?);/.exec(header)?.[1] ?? 'image/png';
|
|
354
366
|
const binary = atob(b64);
|
|
355
367
|
const arr = new Uint8Array(binary.length);
|
|
356
|
-
for (let i = 0; i < binary.length; i++) arr[i] = binary.
|
|
368
|
+
for (let i = 0; i < binary.length; i++) arr[i] = binary.charCodeAt(i);
|
|
357
369
|
return new Blob([arr], { type: mime });
|
|
358
370
|
}
|
|
359
371
|
|
|
@@ -31,6 +31,7 @@ export class FindReplace extends BaseDialog {
|
|
|
31
31
|
this._currentIndex = -1;
|
|
32
32
|
this._caseSensitive = false;
|
|
33
33
|
this._useRegex = false;
|
|
34
|
+
this._wholeWord = false;
|
|
34
35
|
/** @type {'find'|'replace'} */
|
|
35
36
|
this._mode = 'find';
|
|
36
37
|
|
|
@@ -39,6 +40,7 @@ export class FindReplace extends BaseDialog {
|
|
|
39
40
|
this._lastQuery = null;
|
|
40
41
|
this._lastCaseSensitive = null;
|
|
41
42
|
this._lastUseRegex = null;
|
|
43
|
+
this._lastWholeWord = null;
|
|
42
44
|
|
|
43
45
|
this._focusTimer = null;
|
|
44
46
|
}
|
|
@@ -179,6 +181,14 @@ export class FindReplace extends BaseDialog {
|
|
|
179
181
|
});
|
|
180
182
|
regexBtn.textContent = '.*';
|
|
181
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
|
+
|
|
182
192
|
const prevBtn = createElement('button', {
|
|
183
193
|
type: 'button',
|
|
184
194
|
class: 'an-fr-icon-btn',
|
|
@@ -198,7 +208,7 @@ export class FindReplace extends BaseDialog {
|
|
|
198
208
|
const counter = createElement('span', { class: 'an-fr-counter' });
|
|
199
209
|
this._counterEl = counter;
|
|
200
210
|
|
|
201
|
-
searchBar.append(findInput, caseCheckbox, caseBtn, regexBtn, prevBtn, nextBtn, counter);
|
|
211
|
+
searchBar.append(findInput, caseCheckbox, caseBtn, regexBtn, wholeWordBtn, prevBtn, nextBtn, counter);
|
|
202
212
|
box.appendChild(searchBar);
|
|
203
213
|
|
|
204
214
|
// ---- Replace row: [input] [Replace] [All] (hidden by default) ----
|
|
@@ -266,7 +276,14 @@ export class FindReplace extends BaseDialog {
|
|
|
266
276
|
this._lastQuery = null;
|
|
267
277
|
this._onSearch();
|
|
268
278
|
});
|
|
269
|
-
|
|
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);
|
|
270
287
|
|
|
271
288
|
return overlay;
|
|
272
289
|
}
|
|
@@ -341,13 +358,14 @@ export class FindReplace extends BaseDialog {
|
|
|
341
358
|
*/
|
|
342
359
|
_findRawMatches(query, root) {
|
|
343
360
|
const results = [];
|
|
344
|
-
// Reuse compiled regex when query, case-sensitivity,
|
|
345
|
-
if (this._lastQuery !== query || this._lastCaseSensitive !== this._caseSensitive || this._lastUseRegex !== this._useRegex) {
|
|
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) {
|
|
346
363
|
const flags = this._caseSensitive ? 'g' : 'gi';
|
|
347
364
|
try {
|
|
348
|
-
|
|
365
|
+
let pattern = this._useRegex
|
|
349
366
|
? query
|
|
350
367
|
: query.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
368
|
+
if (this._wholeWord) pattern = `\\b${pattern}\\b`;
|
|
351
369
|
this._queryRegex = new RegExp(pattern, flags);
|
|
352
370
|
} catch (_) {
|
|
353
371
|
this._queryRegex = null;
|
|
@@ -355,6 +373,7 @@ export class FindReplace extends BaseDialog {
|
|
|
355
373
|
this._lastQuery = query;
|
|
356
374
|
this._lastCaseSensitive = this._caseSensitive;
|
|
357
375
|
this._lastUseRegex = this._useRegex;
|
|
376
|
+
this._lastWholeWord = this._wholeWord;
|
|
358
377
|
}
|
|
359
378
|
if (!this._queryRegex) return results;
|
|
360
379
|
const re = this._queryRegex;
|
|
@@ -107,6 +107,7 @@ export class ImageDialog extends BaseDialog {
|
|
|
107
107
|
// ---------------------------------------------------------------------------
|
|
108
108
|
|
|
109
109
|
_onFileChange() {
|
|
110
|
+
if (this.context._alive === false) return;
|
|
110
111
|
const file = this._fileInput?.files?.[0];
|
|
111
112
|
if (!file?.type?.startsWith('image/')) return;
|
|
112
113
|
|
|
@@ -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
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* debounce: 200,
|
|
18
18
|
* onSearch: (query, callback) => void,
|
|
19
19
|
* onInsert: (item) => string | null,
|
|
20
|
+
* onError: (err: Error) => void,
|
|
20
21
|
* mentionClass: 'an-mention',
|
|
21
22
|
* allowSpaces: false,
|
|
22
23
|
* }
|
|
@@ -277,9 +278,19 @@ export class Mention {
|
|
|
277
278
|
this._renderItems(items);
|
|
278
279
|
this._showDropdown();
|
|
279
280
|
};
|
|
280
|
-
|
|
281
|
+
let result;
|
|
282
|
+
try {
|
|
283
|
+
result = this._cfg.onSearch(this._query, cb);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
this._hideDropdown();
|
|
286
|
+
if (typeof this._cfg.onError === 'function') this._cfg.onError(err);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
281
289
|
if (result && typeof result.then === 'function') {
|
|
282
|
-
result.then(cb).catch(() =>
|
|
290
|
+
result.then(cb).catch((err) => {
|
|
291
|
+
this._hideDropdown();
|
|
292
|
+
if (typeof this._cfg.onError === 'function') this._cfg.onError(err);
|
|
293
|
+
});
|
|
283
294
|
}
|
|
284
295
|
}, this._cfg.debounce);
|
|
285
296
|
}
|
|
@@ -1470,9 +1470,30 @@ export class TableTooltip {
|
|
|
1470
1470
|
: bText.localeCompare(aText);
|
|
1471
1471
|
});
|
|
1472
1472
|
rows.forEach((row) => tbody.appendChild(row));
|
|
1473
|
+
this._markSortIndicator(table, colIdx, direction);
|
|
1473
1474
|
this.context.invoke('editor.afterCommand');
|
|
1474
1475
|
}
|
|
1475
1476
|
|
|
1477
|
+
/**
|
|
1478
|
+
* Marks the header cell of the sorted column with `an-sort-asc`/`an-sort-desc`
|
|
1479
|
+
* so the active sort column and direction are visible, and clears any previous
|
|
1480
|
+
* indicator. No-op for tables without a `<thead>` (no header row to mark).
|
|
1481
|
+
* @param {HTMLTableElement} table
|
|
1482
|
+
* @param {number} colIdx
|
|
1483
|
+
* @param {'asc'|'desc'} direction
|
|
1484
|
+
*/
|
|
1485
|
+
_markSortIndicator(table, colIdx, direction) {
|
|
1486
|
+
const thead = table.querySelector('thead');
|
|
1487
|
+
if (!thead) return;
|
|
1488
|
+
thead.querySelectorAll('.an-sort-asc, .an-sort-desc').forEach((el) => {
|
|
1489
|
+
el.classList.remove('an-sort-asc', 'an-sort-desc');
|
|
1490
|
+
});
|
|
1491
|
+
const headerRow = thead.querySelector('tr');
|
|
1492
|
+
if (!headerRow) return;
|
|
1493
|
+
const headerCell = getCellAtVisualCol(headerRow, colIdx);
|
|
1494
|
+
if (headerCell) headerCell.classList.add(direction === 'asc' ? 'an-sort-asc' : 'an-sort-desc');
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1476
1497
|
_exportTableCSV() {
|
|
1477
1498
|
const table = this._activeTable;
|
|
1478
1499
|
if (!table) return;
|
|
@@ -31,9 +31,13 @@
|
|
|
31
31
|
|
|
32
32
|
&.an-focused {
|
|
33
33
|
border-color: var(--an-focus-color, #{$an-primary});
|
|
34
|
-
// color-mix()
|
|
35
|
-
//
|
|
36
|
-
box-shadow: 0 0 0 3px
|
|
34
|
+
// Fallback glow for browsers without color-mix() (pre-Chrome 111, Safari 15.3, Firefox 112).
|
|
35
|
+
// Uses the static $an-primary value so --an-focus-color overrides are ignored in those browsers.
|
|
36
|
+
box-shadow: 0 0 0 3px rgba($an-primary, 0.18);
|
|
37
|
+
// Modern browsers get a fully themeable glow that respects --an-focus-color.
|
|
38
|
+
@supports (color: color-mix(in srgb, red, blue)) {
|
|
39
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--an-focus-color, #{$an-primary}) 18%, transparent);
|
|
40
|
+
}
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
&.an-disabled {
|
|
@@ -431,6 +435,11 @@
|
|
|
431
435
|
padding: $an-editable-pad;
|
|
432
436
|
min-height: 0;
|
|
433
437
|
outline: none;
|
|
438
|
+
|
|
439
|
+
&:focus-visible {
|
|
440
|
+
outline: 2px solid var(--an-focus-color, #{$an-primary});
|
|
441
|
+
outline-offset: -2px;
|
|
442
|
+
}
|
|
434
443
|
line-height: $an-line-height;
|
|
435
444
|
color: $an-text;
|
|
436
445
|
overflow-y: auto;
|
|
@@ -565,6 +574,17 @@
|
|
|
565
574
|
th {
|
|
566
575
|
background: $an-bg-toolbar;
|
|
567
576
|
font-weight: 600;
|
|
577
|
+
|
|
578
|
+
// Sort indicator — shows the active sorted column and direction
|
|
579
|
+
&.an-sort-asc::after,
|
|
580
|
+
&.an-sort-desc::after {
|
|
581
|
+
display: inline-block;
|
|
582
|
+
margin-left: 6px;
|
|
583
|
+
font-size: 0.7em;
|
|
584
|
+
opacity: 0.6;
|
|
585
|
+
}
|
|
586
|
+
&.an-sort-asc::after { content: '\25B2'; }
|
|
587
|
+
&.an-sort-desc::after { content: '\25BC'; }
|
|
568
588
|
}
|
|
569
589
|
}
|
|
570
590
|
}
|