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
@@ -78,24 +78,25 @@ export class VideoTooltip {
78
78
  // ---------------------------------------------------------------------------
79
79
 
80
80
  _buildTooltip() {
81
+ const L = this.context.locale.tooltips.video;
81
82
  const el = createElement('div', {
82
83
  class: 'an-link-tooltip an-video-tooltip',
83
84
  role: 'toolbar',
84
- 'aria-label': 'Video actions',
85
+ 'aria-label': L.ariaLabel,
85
86
  });
86
87
  el.style.display = 'none';
87
88
 
88
89
  // Label
89
90
  this._label = createElement('span', { class: 'an-link-tooltip-url' });
90
- this._label.textContent = 'Video';
91
+ this._label.textContent = L.label;
91
92
  el.appendChild(this._label);
92
93
 
93
94
  el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
94
95
 
95
- this._floatLeftBtn = this._makeBtn(ICONS.floatLeft, 'Float Left', () => this._setFloat('left'));
96
- this._floatNoneBtn = this._makeBtn(ICONS.floatNone, 'No Float', () => this._setFloat(''));
97
- this._alignCenterBtn = this._makeBtn(ICONS.alignCenter, 'Align Center', () => this._setCenter());
98
- this._floatRightBtn = this._makeBtn(ICONS.floatRight, 'Float Right', () => this._setFloat('right'));
96
+ this._floatLeftBtn = this._makeBtn(ICONS.floatLeft, L.floatLeft, () => this._setFloat('left'));
97
+ this._floatNoneBtn = this._makeBtn(ICONS.floatNone, L.noFloat, () => this._setFloat(''));
98
+ this._alignCenterBtn = this._makeBtn(ICONS.alignCenter, L.alignCenter, () => this._setCenter());
99
+ this._floatRightBtn = this._makeBtn(ICONS.floatRight, L.floatRight, () => this._setFloat('right'));
99
100
 
100
101
  el.appendChild(this._floatLeftBtn);
101
102
  el.appendChild(this._floatNoneBtn);
@@ -104,18 +105,18 @@ export class VideoTooltip {
104
105
 
105
106
  el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
106
107
 
107
- this._originalBtn = this._makeBtn(ICONS.originalSize, 'Original Size', () => this._resetSize());
108
+ this._originalBtn = this._makeBtn(ICONS.originalSize, L.originalSize, () => this._resetSize());
108
109
 
109
110
  el.appendChild(this._originalBtn);
110
111
 
111
112
  el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
112
113
 
113
- this._previewBtn = this._makeBtn(ICONS.preview, 'Preview Video', () => this._togglePreview());
114
+ this._previewBtn = this._makeBtn(ICONS.preview, L.previewVideo, () => this._togglePreview());
114
115
  el.appendChild(this._previewBtn);
115
116
 
116
117
  el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
117
118
 
118
- this._deleteBtn = this._makeBtn(ICONS.deleteVideo, 'Delete Video', () => this._delete(), true);
119
+ this._deleteBtn = this._makeBtn(ICONS.deleteVideo, L.deleteVideo, () => this._delete(), true);
119
120
 
120
121
  el.appendChild(this._deleteBtn);
121
122
 
@@ -292,7 +293,7 @@ export class VideoTooltip {
292
293
 
293
294
  // Visual feedback: button turns primary-coloured
294
295
  this._previewBtn.classList.add('an-link-tooltip-btn--copied');
295
- this._previewBtn.title = 'Exit Preview';
296
+ this._previewBtn.title = this.context.locale.tooltips.video.exitPreview;
296
297
 
297
298
  // Exit preview on mousedown outside the wrapper.
298
299
  // Using 'mousedown' (not 'click') so clicks inside an iframe
@@ -319,7 +320,7 @@ export class VideoTooltip {
319
320
 
320
321
  // Reset button appearance
321
322
  this._previewBtn.classList.remove('an-link-tooltip-btn--copied');
322
- this._previewBtn.title = 'Preview Video';
323
+ this._previewBtn.title = this.context.locale.tooltips.video.previewVideo;
323
324
 
324
325
  if (this._previewClickOff) {
325
326
  document.removeEventListener('mousedown', this._previewClickOff, true);
@@ -135,4 +135,27 @@ export const defaultOptions = {
135
135
  // Custom focus ring colour — overrides the default blue when set.
136
136
  // Accepts any valid CSS colour string, e.g. '#f97316', 'hsl(25,90%,55%)'.
137
137
  focusColor: null,
138
+ // Display language for the editor UI.
139
+ // Built-in values: 'en' (default), 'vi', 'ja', 'zh', 'fr'.
140
+ // Pass a partial or full locale object to override individual strings.
141
+ lang: 'en',
142
+
143
+ // Auto-save restore: show a banner when a draft exists in localStorage.
144
+ // Requires autoSave: true. Set autoSaveRestoreTimeout to the max age in days
145
+ // (0 = no expiry). onAutoSaveRestore(html, context) fires after restore.
146
+ autoSaveRestore: false,
147
+ autoSaveRestoreTimeout: 7,
148
+ onAutoSaveRestore: null,
149
+
150
+ // Markdown input shortcuts: convert markdown syntax typed inline to HTML.
151
+ // e.g. "## " at line start → <h2>, "**bold**" → <strong>
152
+ markdownShortcuts: true,
153
+
154
+ // Bubble toolbar: show a mini floating toolbar above text selections.
155
+ bubbleToolbar: false,
156
+ bubbleToolbarItems: ['bold', 'italic', 'underline', 'link', 'foreColor', 'removeFormat'],
157
+
158
+ // @mention support. mention.onSearch(query, callback) must be provided to activate.
159
+ // mention.minChars defaults to 0 — dropdown opens immediately on trigger character.
160
+ mention: null,
138
161
  };
@@ -1999,3 +1999,194 @@ $an-video-accent: #8b5cf6; // violet-500
1999
1999
  display: none !important;
2000
2000
  }
2001
2001
  }
2002
+
2003
+ // =============================================================================
2004
+ // Auto-save restore banner (#2)
2005
+ // =============================================================================
2006
+
2007
+ .an-asr-banner {
2008
+ display: flex;
2009
+ align-items: center;
2010
+ gap: 8px;
2011
+ padding: 8px 12px;
2012
+ background: #eff6ff;
2013
+ border-bottom: 1px solid #bfdbfe;
2014
+ font-size: 13px;
2015
+ color: #1e40af;
2016
+ flex-wrap: wrap;
2017
+
2018
+ .an-theme-dark & {
2019
+ background: #1e3a5f;
2020
+ border-bottom-color: #1e4d8c;
2021
+ color: #93c5fd;
2022
+ }
2023
+ }
2024
+
2025
+ .an-asr-msg {
2026
+ flex: 1;
2027
+ min-width: 0;
2028
+ }
2029
+
2030
+ .an-asr-btn-restore {
2031
+ padding: 3px 10px;
2032
+ font-size: 12px;
2033
+ font-weight: 600;
2034
+ background: $an-primary;
2035
+ color: #fff;
2036
+ border: none;
2037
+ border-radius: $an-radius-sm;
2038
+ cursor: pointer;
2039
+ line-height: 1.5;
2040
+
2041
+ &:hover {
2042
+ background: $an-primary-hover;
2043
+ }
2044
+ }
2045
+
2046
+ .an-asr-btn-discard {
2047
+ padding: 3px 10px;
2048
+ font-size: 12px;
2049
+ background: transparent;
2050
+ color: $an-muted;
2051
+ border: 1px solid $an-border;
2052
+ border-radius: $an-radius-sm;
2053
+ cursor: pointer;
2054
+ line-height: 1.5;
2055
+
2056
+ &:hover {
2057
+ background: $an-bg-btn-hover;
2058
+ }
2059
+ }
2060
+
2061
+ // =============================================================================
2062
+ // Bubble toolbar (#4)
2063
+ // =============================================================================
2064
+
2065
+ .an-bubble-toolbar {
2066
+ display: none;
2067
+ position: fixed;
2068
+ z-index: 10040;
2069
+ background: $an-bg;
2070
+ border: 1px solid $an-border;
2071
+ border-radius: 8px;
2072
+ padding: 4px 6px;
2073
+ gap: 2px;
2074
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.14);
2075
+ align-items: center;
2076
+ animation: an-bubble-in 0.1s ease;
2077
+ pointer-events: auto;
2078
+ user-select: none;
2079
+
2080
+ .an-theme-dark & {
2081
+ background: #24273a;
2082
+ border-color: #3f3f5f;
2083
+ }
2084
+ }
2085
+
2086
+ @keyframes an-bubble-in {
2087
+ from { opacity: 0; transform: translateY(4px); }
2088
+ to { opacity: 1; transform: translateY(0); }
2089
+ }
2090
+
2091
+ .an-bubble-btn {
2092
+ display: flex;
2093
+ align-items: center;
2094
+ justify-content: center;
2095
+ width: 28px;
2096
+ height: 28px;
2097
+ padding: 0;
2098
+ background: transparent;
2099
+ border: none;
2100
+ border-radius: 5px;
2101
+ color: $an-text;
2102
+ cursor: pointer;
2103
+ transition: background $an-transition, color $an-transition;
2104
+
2105
+ svg {
2106
+ pointer-events: none;
2107
+ stroke: currentColor;
2108
+ }
2109
+
2110
+ &:hover {
2111
+ background: $an-bg-btn-hover;
2112
+ color: $an-text;
2113
+ }
2114
+
2115
+ &.an-active {
2116
+ background: $an-bg-btn-active;
2117
+ color: $an-primary;
2118
+ }
2119
+
2120
+ .an-theme-dark & {
2121
+ color: #cdd6f4;
2122
+
2123
+ &:hover {
2124
+ background: #313244;
2125
+ }
2126
+
2127
+ &.an-active {
2128
+ background: #1e3a5f;
2129
+ color: #93c5fd;
2130
+ }
2131
+ }
2132
+ }
2133
+
2134
+ // =============================================================================
2135
+ // @mention dropdown + chip (#5)
2136
+ // =============================================================================
2137
+
2138
+ .an-mention-dropdown {
2139
+ display: none;
2140
+ position: fixed;
2141
+ z-index: 9998;
2142
+ background: $an-bg;
2143
+ border: 1px solid $an-border;
2144
+ border-radius: $an-radius;
2145
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
2146
+ min-width: 180px;
2147
+ max-width: 280px;
2148
+ max-height: 240px;
2149
+ overflow-y: auto;
2150
+ padding: 4px 0;
2151
+ }
2152
+
2153
+ .an-mention-item {
2154
+ display: flex;
2155
+ align-items: center;
2156
+ gap: 8px;
2157
+ padding: 6px 12px;
2158
+ cursor: pointer;
2159
+ font-size: 13px;
2160
+ color: $an-text;
2161
+ transition: background $an-transition;
2162
+
2163
+ &:hover,
2164
+ &.an-mention-active {
2165
+ background: $an-bg-btn-hover;
2166
+ }
2167
+ }
2168
+
2169
+ .an-mention-avatar {
2170
+ width: 24px;
2171
+ height: 24px;
2172
+ border-radius: 50%;
2173
+ object-fit: cover;
2174
+ flex-shrink: 0;
2175
+ }
2176
+
2177
+ // Mention chip — inserted inline in the editor content
2178
+ .an-mention {
2179
+ display: inline;
2180
+ background: #eff6ff;
2181
+ color: $an-primary;
2182
+ border-radius: 3px;
2183
+ padding: 0 3px;
2184
+ font-weight: 500;
2185
+ cursor: default;
2186
+ user-select: all;
2187
+
2188
+ .an-theme-dark & {
2189
+ background: #1e3a5f;
2190
+ color: #93c5fd;
2191
+ }
2192
+ }
package/types/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * AutumnNote – TypeScript declarations
3
- * @version 1.0.4
3
+ * @version 1.0.9
4
4
  */
5
5
 
6
6
  // ---------------------------------------------------------------------------
@@ -104,6 +104,68 @@ export interface AsnOptions {
104
104
  onPaste?: (data: { text: string; html: string | null }) => void;
105
105
  /** Additional color swatches shown at the top of the color picker. */
106
106
  colorSwatches?: string[];
107
+ /**
108
+ * Display language for the editor UI.
109
+ * Built-in: 'en' (default) | 'vi' | 'ja' | 'zh' | 'fr'.
110
+ * Pass a partial locale object to override individual strings.
111
+ */
112
+ lang?: string | Partial<AsnLocale>;
113
+
114
+ // ---- Auto-save restore (#2) -----------------------------------------------
115
+
116
+ /** Show a restore banner when a previously auto-saved draft exists. Requires autoSave: true. */
117
+ autoSaveRestore?: boolean;
118
+ /** Maximum age in days for a draft to be offered for restore (0 = no expiry). Default: 7. */
119
+ autoSaveRestoreTimeout?: number;
120
+ /** Callback fired after the user chooses to restore a draft. */
121
+ onAutoSaveRestore?: (html: string, context: Context) => void;
122
+
123
+ // ---- Markdown input shortcuts (#3) ----------------------------------------
124
+
125
+ /** Convert Markdown-style syntax typed inline (e.g. "## ", "**bold**") to rich HTML. Default: true. */
126
+ markdownShortcuts?: boolean;
127
+
128
+ // ---- Bubble toolbar (#4) --------------------------------------------------
129
+
130
+ /** Show a mini floating toolbar above the text selection. Default: false. */
131
+ bubbleToolbar?: boolean;
132
+ /** Button names shown in the bubble toolbar. */
133
+ bubbleToolbarItems?: Array<'bold' | 'italic' | 'underline' | 'strikethrough' | 'link' | 'foreColor' | 'removeFormat' | 'inlineCode'>;
134
+
135
+ // ---- @mention (#5) --------------------------------------------------------
136
+
137
+ /** @mention autocomplete configuration. Activated when mention.onSearch is provided. */
138
+ mention?: MentionOptions | null;
139
+ }
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Mention types (#5)
143
+ // ---------------------------------------------------------------------------
144
+
145
+ export interface MentionItem {
146
+ id: string | number;
147
+ label: string;
148
+ avatar?: string;
149
+ [key: string]: unknown;
150
+ }
151
+
152
+ export interface MentionOptions {
153
+ /** Trigger character. Default: '@'. */
154
+ trigger?: string;
155
+ /** Minimum characters to type before searching. Default: 0 (show on trigger character). */
156
+ minChars?: number;
157
+ /** Maximum results shown in the dropdown. Default: 8. */
158
+ maxResults?: number;
159
+ /** Debounce delay in ms before calling onSearch. Default: 200. */
160
+ debounce?: number;
161
+ /** Required. Called with (query, callback) to fetch matching items. */
162
+ onSearch: (query: string, callback: (items: MentionItem[]) => void) => void;
163
+ /** Optional. Return custom HTML string for the inserted chip, or null to use the default. */
164
+ onInsert?: (item: MentionItem) => string | null;
165
+ /** CSS class added to the inserted mention chip. Default: 'an-mention'. */
166
+ mentionClass?: string;
167
+ /** Allow spaces in the query string. Default: false. */
168
+ allowSpaces?: boolean;
107
169
  }
108
170
 
109
171
  // ---------------------------------------------------------------------------
@@ -179,6 +241,57 @@ export type ToolbarItemDef = ButtonDef | DropdownDef | GridButtonDef | ColorPick
179
241
  /** Runtime default options object (same export as `src/js/index.js`). */
180
242
  export declare const defaultOptions: Required<AsnOptions>;
181
243
 
244
+ // ---------------------------------------------------------------------------
245
+ // Locale / i18n
246
+ // ---------------------------------------------------------------------------
247
+
248
+ /** Full locale object shape used by the editor. All leaf values are strings or template functions. */
249
+ export interface AsnLocale {
250
+ toolbar: Record<string, string | ((n: string | number) => string) | Record<string, string>>;
251
+ linkDialog: Record<string, string>;
252
+ imageDialog: Record<string, string>;
253
+ videoDialog: Record<string, string | ((type: string) => string)>;
254
+ emojiDialog: Record<string, string | Record<string, string>>;
255
+ iconDialog: Record<string, string | Record<string, string>>;
256
+ findReplace: Record<string, string>;
257
+ shortcutsDialog: {
258
+ title: string;
259
+ ariaLabel: string;
260
+ close: string;
261
+ shortcuts: Array<{ category: string; items: Array<{ keys: string; action: string }> }>;
262
+ };
263
+ contextMenu: Record<string, string>;
264
+ statusbar: {
265
+ resizeHandle: string;
266
+ words: (n: number) => string;
267
+ wordsLimit: (n: number, max: number) => string;
268
+ chars: (n: number) => string;
269
+ charsLimit: (n: number, max: number) => string;
270
+ };
271
+ tooltips: {
272
+ link: Record<string, string>;
273
+ image: Record<string, string>;
274
+ code: Record<string, string>;
275
+ table: Record<string, string>;
276
+ video: Record<string, string>;
277
+ };
278
+ errors: {
279
+ imageFormat: (type: string) => string;
280
+ imageSize: (maxMb: number) => string;
281
+ };
282
+ }
283
+
284
+ /** Registry of all built-in locales (keys: 'en', 'vi', 'ja', 'zh', 'fr'). */
285
+ export declare const locales: Record<string, AsnLocale>;
286
+
287
+ /**
288
+ * Resolves a lang value to a full AsnLocale.
289
+ * - string key → looks up locales registry, deep-merges with 'en' fallback.
290
+ * - object → deep-merges with 'en' as base.
291
+ * - falsy / 'en' → returns the canonical English locale.
292
+ */
293
+ export declare function resolveLocale(lang: string | Partial<AsnLocale> | null | undefined): AsnLocale;
294
+
182
295
  // Re-export core utility modules exposed by the package entry-point.
183
296
  export * from '../src/js/core/dom.js';
184
297
  export * from '../src/js/core/range.js';