autumnnote 1.0.8 → 1.1.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.
Files changed (41) hide show
  1. package/README.md +401 -628
  2. package/dist/autumnnote.css +169 -0
  3. package/dist/autumnnote.es.js +3669 -179
  4. package/dist/autumnnote.es.js.map +1 -1
  5. package/dist/autumnnote.umd.js +3668 -178
  6. package/dist/autumnnote.umd.js.map +1 -1
  7. package/package.json +3 -2
  8. package/src/js/Context.js +18 -2
  9. package/src/js/core/func.js +5 -5
  10. package/src/js/i18n/de.js +320 -0
  11. package/src/js/i18n/en.js +328 -0
  12. package/src/js/i18n/es.js +320 -0
  13. package/src/js/i18n/fr.js +321 -0
  14. package/src/js/i18n/index.js +59 -0
  15. package/src/js/i18n/ja.js +321 -0
  16. package/src/js/i18n/ko.js +320 -0
  17. package/src/js/i18n/vi.js +321 -0
  18. package/src/js/i18n/zh.js +321 -0
  19. package/src/js/index.js +2 -1
  20. package/src/js/module/AutoSaveRestore.js +126 -0
  21. package/src/js/module/BubbleToolbar.js +243 -0
  22. package/src/js/module/CodeTooltip.js +12 -9
  23. package/src/js/module/ContextMenu.js +41 -37
  24. package/src/js/module/EmojiDialog.js +8 -7
  25. package/src/js/module/FindReplace.js +14 -13
  26. package/src/js/module/IconDialog.js +14 -13
  27. package/src/js/module/ImageDialog.js +17 -16
  28. package/src/js/module/ImageTooltip.js +13 -12
  29. package/src/js/module/LinkDialog.js +10 -9
  30. package/src/js/module/LinkTooltip.js +6 -5
  31. package/src/js/module/MarkdownShortcuts.js +253 -0
  32. package/src/js/module/Mention.js +337 -0
  33. package/src/js/module/ShortcutsDialog.js +5 -4
  34. package/src/js/module/Statusbar.js +6 -5
  35. package/src/js/module/TableTooltip.js +22 -19
  36. package/src/js/module/Toolbar.js +21 -15
  37. package/src/js/module/VideoDialog.js +10 -9
  38. package/src/js/module/VideoTooltip.js +12 -11
  39. package/src/js/settings.js +23 -0
  40. package/src/styles/autumnnote.scss +191 -0
  41. package/types/index.d.ts +114 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autumnnote",
3
- "version": "1.0.8",
3
+ "version": "1.1.0",
4
4
  "description": "A modern, lightweight WYSIWYG editor — built with vanilla JavaScript, no jQuery required.",
5
5
  "main": "dist/autumnnote.umd.js",
6
6
  "module": "dist/autumnnote.es.js",
@@ -23,7 +23,8 @@
23
23
  "typecheck": "tsc --noEmit",
24
24
  "build:cdn": "vite build --config vite.cdn.config.js",
25
25
  "analyze": "cross-env ANALYZE=1 vite build",
26
- "bench": "vitest bench"
26
+ "bench": "vitest bench",
27
+ "test:coverage": "vitest run --coverage"
27
28
  },
28
29
  "keywords": [
29
30
  "wysiwyg",
package/src/js/Context.js CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { mergeDeep } from './core/func.js';
8
8
  import { defaultOptions } from './settings.js';
9
+ import { resolveLocale } from './i18n/index.js';
9
10
  import { renderLayout } from './renderer.js';
10
11
  import { on } from './core/dom.js';
11
12
 
@@ -33,6 +34,10 @@ import { ContextMenu } from './module/ContextMenu.js';
33
34
  import { ShortcutsDialog } from './module/ShortcutsDialog.js';
34
35
  import { FindReplace } from './module/FindReplace.js';
35
36
  import { ImageCropOverlay } from './module/ImageCropOverlay.js';
37
+ import { AutoSaveRestore } from './module/AutoSaveRestore.js';
38
+ import { MarkdownShortcuts } from './module/MarkdownShortcuts.js';
39
+ import { BubbleToolbar } from './module/BubbleToolbar.js';
40
+ import { Mention } from './module/Mention.js';
36
41
 
37
42
  /** Module registry shared across all Context instances (populated via AutumnNote.registerModule). */
38
43
  export const _customModules = new Map();
@@ -46,6 +51,9 @@ export class Context {
46
51
  this.targetEl = targetEl;
47
52
  this.options = mergeDeep(defaultOptions, userOptions);
48
53
 
54
+ /** @type {import('./i18n/index.js').AsnLocale} */
55
+ this.locale = resolveLocale(this.options.lang);
56
+
49
57
  /** @type {{ container: HTMLElement, editable: HTMLElement, toolbar?: HTMLElement, statusbar?: HTMLElement }} */
50
58
  this.layoutInfo = {};
51
59
 
@@ -135,6 +143,10 @@ export class Context {
135
143
  register('shortcutsDialog', ShortcutsDialog);
136
144
  register('findReplace', FindReplace);
137
145
  register('imageCropOverlay', ImageCropOverlay);
146
+ register('autoSaveRestore', AutoSaveRestore);
147
+ register('markdownShortcuts', MarkdownShortcuts);
148
+ register('bubbleToolbar', BubbleToolbar);
149
+ register('mention', Mention);
138
150
 
139
151
  // Custom modules registered via AutumnNote.registerModule()
140
152
  for (const [name, ModuleClass] of _customModules) {
@@ -177,10 +189,14 @@ export class Context {
177
189
  const d3 = this.on('change', () => this._syncToTarget());
178
190
  this._disposers.push(d0, d1, d2, d3);
179
191
 
180
- // Auto-save to localStorage on every change
192
+ // Auto-save to localStorage on every change (also writes :asrmeta for restore banner)
181
193
  if (this.options.autoSave && this.options.autoSaveKey) {
182
194
  const d4 = this.on('change', () => {
183
- try { localStorage.setItem(this.options.autoSaveKey, this.getHTML()); } catch (_) {}
195
+ try {
196
+ const key = this.options.autoSaveKey;
197
+ localStorage.setItem(key, this.getHTML());
198
+ localStorage.setItem(key + ':asrmeta', JSON.stringify({ savedAt: Date.now() }));
199
+ } catch (_) {}
184
200
  });
185
201
  this._disposers.push(d4);
186
202
  }
@@ -110,11 +110,11 @@ export function mergeDeep(target, source) {
110
110
  if (isPlainObject(target) && isPlainObject(source)) {
111
111
  for (const key of Object.keys(source)) {
112
112
  if (isPlainObject(source[key])) {
113
- if (!(key in target)) {
114
- output[key] = mergeDeep({}, source[key]);
115
- } else {
116
- output[key] = mergeDeep(target[key], source[key]);
117
- }
113
+ // When target[key] is null / undefined / a non-object (e.g. the `mention: null`
114
+ // default), merge into an empty object instead of passing null to the next
115
+ // recursive call — which would silently drop all source properties.
116
+ const base = isPlainObject(target[key]) ? target[key] : {};
117
+ output[key] = mergeDeep(base, source[key]);
118
118
  } else if (Array.isArray(source[key])) {
119
119
  output[key] = [...source[key]];
120
120
  } else {
@@ -0,0 +1,320 @@
1
+ /**
2
+ * de.js - German locale
3
+ * Deep-merged over en.js; only keys that differ from English are listed.
4
+ */
5
+
6
+ /** @type {Partial<import('../../../types/index.js').AsnLocale>} */
7
+ export const de = {
8
+ toolbar: {
9
+ bold: 'Fett (Ctrl+B)',
10
+ italic: 'Kursiv (Ctrl+I)',
11
+ underline: 'Unterstrichen (Ctrl+U)',
12
+ strikethrough: 'Durchgestrichen',
13
+ superscript: 'Hochgestellt',
14
+ subscript: 'Tiefgestellt',
15
+ alignLeft: 'Linksbündig',
16
+ alignCenter: 'Zentriert',
17
+ alignRight: 'Rechtsbündig',
18
+ alignJustify: 'Blocksatz',
19
+ ul: 'Ungeordnete Liste',
20
+ ol: 'Geordnete Liste',
21
+ checklist: 'Checkliste',
22
+ indent: 'Einzug vergrößern',
23
+ outdent: 'Einzug verkleinern',
24
+ undo: 'Rückgängig (Ctrl+Z)',
25
+ redo: 'Wiederholen (Ctrl+Y)',
26
+ hr: 'Horizontale Linie',
27
+ link: 'Link einfügen',
28
+ image: 'Bild einfügen',
29
+ video: 'Video einfügen',
30
+ emoji: 'Emoji einfügen',
31
+ icon: 'FA-Symbol einfügen',
32
+ table: 'Tabelle einfügen',
33
+ fontSize: 'Schriftgröße',
34
+ fontSizePlaceholder: 'Größe',
35
+ removeFormat: 'Formatierung entfernen',
36
+ direction: 'Textrichtung umschalten (LTR / RTL)',
37
+ fontFamily: 'Schriftart',
38
+ paragraphStyle: 'Absatzstil',
39
+ paragraphStylePlaceholder: 'Stil',
40
+ lineHeight: 'Zeilenhöhe',
41
+ lineHeightPlaceholder: '↕ Zeile',
42
+ codeview: 'HTML-Code-Ansicht',
43
+ fullscreen: 'Vollbild',
44
+ shortcuts: 'Tastenkürzel (Ctrl+Shift+/)',
45
+ find: 'Suchen (Ctrl+F)',
46
+ findReplace: 'Suchen & Ersetzen (Ctrl+H)',
47
+ inlineCode: 'Inline-Code (Ctrl+`)',
48
+ print: 'Drucken',
49
+ foreColor: 'Textfarbe',
50
+ backColor: 'Hervorhebungsfarbe',
51
+ chooseTextColor: 'Textfarbe auswählen',
52
+ chooseHighlightColor: 'Hervorhebungsfarbe auswählen',
53
+ customColor: 'Benutzerdefinierte Farbe',
54
+ insertTableLabel: 'Tabelle einfügen',
55
+ paragraphItems: {
56
+ p: 'Normal',
57
+ blockquote: 'Zitat',
58
+ pre: 'Code',
59
+ },
60
+ },
61
+
62
+ linkDialog: {
63
+ ariaLabel: 'Link einfügen',
64
+ title: 'Link einfügen',
65
+ url: 'URL',
66
+ urlPlaceholder: 'https://',
67
+ displayText: 'Anzeigetext',
68
+ textPlaceholder: 'Linktext',
69
+ openInNewTab: 'In neuem Tab öffnen',
70
+ insertBtn: 'Einfügen',
71
+ cancelBtn: 'Abbrechen',
72
+ },
73
+
74
+ imageDialog: {
75
+ ariaLabel: 'Bild einfügen',
76
+ title: 'Bild einfügen',
77
+ imageUrl: 'Bild-URL',
78
+ urlPlaceholder: 'https://example.com/image.png',
79
+ altText: 'Alt-Text',
80
+ altPlaceholder: 'Bild beschreiben',
81
+ alignment: 'Ausrichtung',
82
+ alignNone: 'Keine',
83
+ alignLeft: 'Links',
84
+ alignCenter: 'Mitte',
85
+ alignRight: 'Rechts',
86
+ uploadLabel: 'Oder Datei hochladen',
87
+ insertBtn: 'Einfügen',
88
+ cancelBtn: 'Abbrechen',
89
+ },
90
+
91
+ videoDialog: {
92
+ ariaLabel: 'Video einfügen',
93
+ title: 'Video einfügen',
94
+ videoUrl: 'Video-URL',
95
+ urlPlaceholder: 'YouTube, Vimeo oder direkte .mp4-URL',
96
+ widthLabel: 'Breite (px)',
97
+ widthPlaceholder: '560',
98
+ insertBtn: 'Einfügen',
99
+ cancelBtn: 'Abbrechen',
100
+ detected: (type) => `Erkannt: ${type}`,
101
+ unknownFormat: 'Unbekanntes Format — direktes Video-Einbetten wird versucht',
102
+ invalidUrl: 'Ungültige URL — bitte geben Sie einen gültigen Videolink ein.',
103
+ },
104
+
105
+ emojiDialog: {
106
+ ariaLabel: 'Emoji einfügen',
107
+ title: 'Emoji einfügen',
108
+ searchPlaceholder: 'Emojis suchen…',
109
+ all: 'Alle',
110
+ cancelBtn: 'Abbrechen',
111
+ close: 'Schließen',
112
+ categories: {
113
+ smileys: 'Smileys',
114
+ people: 'Menschen',
115
+ animals: 'Tiere',
116
+ food: 'Essen',
117
+ travel: 'Reisen',
118
+ objects: 'Objekte',
119
+ symbols: 'Symbole',
120
+ },
121
+ },
122
+
123
+ iconDialog: {
124
+ ariaLabel: 'FA-Symbol einfügen',
125
+ title: 'FA-Symbol einfügen',
126
+ searchPlaceholder: 'Symbole suchen…',
127
+ all: 'Alle',
128
+ style: 'Stil',
129
+ size: 'Größe',
130
+ color: 'Farbe',
131
+ useColor: ' Farbe verwenden',
132
+ selectHint: 'Symbol auswählen',
133
+ insertBtn: 'FA-Symbol einfügen',
134
+ cancelBtn: 'Abbrechen',
135
+ close: 'Schließen',
136
+ categories: {
137
+ popular: 'Beliebt',
138
+ interface: 'Benutzeroberfläche',
139
+ navigation: 'Navigation',
140
+ media: 'Medien',
141
+ communication: 'Kommunikation',
142
+ files: 'Dateien',
143
+ people: 'Menschen',
144
+ objects: 'Objekte',
145
+ },
146
+ },
147
+
148
+ findReplace: {
149
+ findTitle: 'Suchen',
150
+ findReplaceTitle: 'Suchen & Ersetzen',
151
+ findPlaceholder: 'Suchen…',
152
+ searchAriaLabel: 'Suchtext',
153
+ caseSensitive: '\u00a0Groß-/Kleinschreibung',
154
+ prevBtn: '← Zurück',
155
+ nextBtn: 'Weiter →',
156
+ replacePlaceholder: 'Ersetzen durch…',
157
+ replaceAriaLabel: 'Ersetzen durch',
158
+ replaceBtn: 'Ersetzen',
159
+ replaceAllBtn: 'Alle ersetzen',
160
+ close: '×',
161
+ },
162
+
163
+ shortcutsDialog: {
164
+ title: 'Tastenkürzel',
165
+ ariaLabel: 'Tastenkürzel',
166
+ close: 'Schließen',
167
+ shortcuts: [
168
+ {
169
+ category: 'Textformatierung',
170
+ items: [
171
+ { keys: 'Ctrl + B', action: 'Fett' },
172
+ { keys: 'Ctrl + I', action: 'Kursiv' },
173
+ { keys: 'Ctrl + U', action: 'Unterstrichen' },
174
+ { keys: 'Ctrl + K', action: 'Link einfügen / bearbeiten' },
175
+ ],
176
+ },
177
+ {
178
+ category: 'Verlauf',
179
+ items: [
180
+ { keys: 'Ctrl + Z', action: 'Rückgängig' },
181
+ { keys: 'Ctrl + Y / Ctrl + Shift + Z', action: 'Wiederholen' },
182
+ ],
183
+ },
184
+ {
185
+ category: 'Auswahl & Navigation',
186
+ items: [
187
+ { keys: 'Ctrl + A', action: 'Alles auswählen' },
188
+ { keys: 'Tab', action: 'Listenebene erhöhen / Leerzeichen einfügen' },
189
+ { keys: 'Shift + Tab', action: 'Listenebene verringern' },
190
+ ],
191
+ },
192
+ {
193
+ category: 'Zwischenablage',
194
+ items: [
195
+ { keys: 'Ctrl + Shift + V', action: 'Als einfachen Text einfügen' },
196
+ ],
197
+ },
198
+ {
199
+ category: 'Suchen & Ersetzen',
200
+ items: [
201
+ { keys: 'Ctrl + F', action: 'Im Dokument suchen' },
202
+ { keys: 'Ctrl + H', action: 'Suchen & Ersetzen' },
203
+ ],
204
+ },
205
+ {
206
+ category: 'Editor',
207
+ items: [
208
+ { keys: 'Ctrl + Shift + /', action: 'Diesen Tastenkürzel-Dialog anzeigen' },
209
+ ],
210
+ },
211
+ ],
212
+ },
213
+
214
+ contextMenu: {
215
+ cut: 'Ausschneiden',
216
+ copy: 'Kopieren',
217
+ paste: 'Einfügen',
218
+ bold: 'Fett',
219
+ italic: 'Kursiv',
220
+ underline: 'Unterstrichen',
221
+ textColor: 'Textfarbe',
222
+ highlightColor: 'Hervorhebungsfarbe',
223
+ copyFormat: 'Format kopieren',
224
+ pasteFormat: 'Format einfügen',
225
+ removeFormat: 'Formatierung entfernen',
226
+ link: 'Link einfügen',
227
+ image: 'Bild einfügen',
228
+ video: 'Video einfügen',
229
+ table: 'Tabelle einfügen',
230
+ back: 'Zurück',
231
+ noHighlight: 'Keine Hervorhebung',
232
+ customColor: 'Benutzerdefinierte Farbe',
233
+ customColorLabel: 'Benutzerdefiniert…',
234
+ },
235
+
236
+ statusbar: {
237
+ resizeHandle: 'Editor-Größe ändern',
238
+ words: (n) => `Wörter: ${n}`,
239
+ wordsLimit: (n, max) => `Wörter: ${n}/${max}`,
240
+ chars: (n) => `Zeichen: ${n}`,
241
+ charsLimit: (n, max) => `Zeichen: ${n}/${max}`,
242
+ },
243
+
244
+ tooltips: {
245
+ link: {
246
+ ariaLabel: 'Link-Aktionen',
247
+ openLink: 'Link öffnen',
248
+ copyUrl: 'URL kopieren',
249
+ editLink: 'Link bearbeiten',
250
+ removeLink: 'Link entfernen',
251
+ },
252
+ image: {
253
+ ariaLabel: 'Bild-Aktionen',
254
+ label: 'Bild',
255
+ floatLeft: 'Links umfließen',
256
+ noFloat: 'Kein Umfluss',
257
+ alignCenter: 'Zentriert',
258
+ floatRight: 'Rechts umfließen',
259
+ originalSize: 'Originalgröße',
260
+ rotateLeft: 'Links drehen',
261
+ rotateRight: 'Rechts drehen',
262
+ cropImage: 'Bild zuschneiden',
263
+ addCaption: 'Beschriftung hinzufügen / bearbeiten',
264
+ deleteImage: 'Bild löschen',
265
+ },
266
+ code: {
267
+ ariaLabel: 'Codeblock-Aktionen',
268
+ label: 'Code',
269
+ syntaxLanguage: 'Syntaxsprache',
270
+ syntaxAriaLabel: 'Syntaxsprache',
271
+ copyCode: 'Code kopieren',
272
+ toggleWordWrap: 'Zeilenumbruch umschalten',
273
+ enableWordWrap: 'Zeilenumbruch aktivieren',
274
+ disableWordWrap: 'Zeilenumbruch deaktivieren',
275
+ convertToParagraph: 'In Absatz umwandeln',
276
+ deleteCodeBlock: 'Codeblock löschen',
277
+ },
278
+ table: {
279
+ ariaLabel: 'Tabellen-Aktionen',
280
+ label: 'Tabelle',
281
+ selectCells: 'Zellen auswählen',
282
+ addRowAbove: 'Zeile oben hinzufügen',
283
+ addRowBelow: 'Zeile unten hinzufügen',
284
+ deleteRow: 'Zeile löschen',
285
+ addColumnLeft: 'Spalte links hinzufügen',
286
+ addColumnRight: 'Spalte rechts hinzufügen',
287
+ deleteColumn: 'Spalte löschen',
288
+ mergeCells: 'Zellen zusammenführen',
289
+ unmergeCells: 'Zellen trennen',
290
+ columnWidth: 'Spaltenbreite',
291
+ rowHeight: 'Zeilenhöhe',
292
+ tableBorderWidth: 'Tabellenrahmenbreite',
293
+ deleteTable: 'Tabelle löschen',
294
+ columnWidthPx: 'Spaltenbreite (px)',
295
+ rowHeightPx: 'Zeilenhöhe (px)',
296
+ tableBorderWidthPx: 'Tabellenrahmenbreite (px)',
297
+ cancelBtn: 'Abbrechen',
298
+ applyBtn: 'Anwenden',
299
+ },
300
+ video: {
301
+ ariaLabel: 'Video-Aktionen',
302
+ label: 'Video',
303
+ floatLeft: 'Links umfließen',
304
+ noFloat: 'Kein Umfluss',
305
+ alignCenter: 'Zentriert',
306
+ floatRight: 'Rechts umfließen',
307
+ originalSize: 'Originalgröße',
308
+ previewVideo: 'Video-Vorschau',
309
+ exitPreview: 'Vorschau beenden',
310
+ deleteVideo: 'Video löschen',
311
+ },
312
+ },
313
+
314
+ errors: {
315
+ imageFormat: (type) =>
316
+ `Das Format „${type}" wird in Webbrowsern nicht unterstützt. Bitte konvertieren Sie es zuerst in JPEG, PNG oder WebP.`,
317
+ imageSize: (maxSize) =>
318
+ `Die Bilddatei ist zu groß. Die maximal zulässige Größe beträgt ${maxSize} MB.`,
319
+ },
320
+ };