autumnnote 2.7.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "autumnnote",
3
- "version": "2.7.0",
4
- "description": "WYSIWYG rich-text editor built with vanilla JavaScript \u2014 zero dependencies, no jQuery. Dark mode, @mention, markdown shortcuts, bubble toolbar. React and Vue\u00a03 wrappers included.",
3
+ "version": "3.0.0",
4
+ "description": "WYSIWYG rich-text editor built with vanilla JavaScript zero dependencies, no jQuery. Dark mode, @mention, markdown shortcuts, bubble toolbar. React and Vue 3 wrappers included.",
5
5
  "type": "module",
6
6
  "main": "dist/autumnnote.cjs",
7
7
  "module": "dist/autumnnote.es.js",
@@ -119,18 +119,18 @@
119
119
  "@vitest/browser-playwright": "^4.1.10",
120
120
  "@vitest/coverage-v8": "^4.1.8",
121
121
  "cross-env": "^10.1.0",
122
- "eslint": "^10.7.0",
123
- "jsdom": "^25.0.1",
124
- "playwright": "^1.61.1",
125
- "rollup-plugin-visualizer": "^7.0.1",
126
- "sass": "^1.101.0",
127
- "typescript": "^6.0.2",
128
- "vite": "^8.1.5",
122
+ "eslint": "^10.8.1",
123
+ "jsdom": "^30.0.1",
124
+ "playwright": "^1.62.1",
125
+ "rollup-plugin-visualizer": "^7.1.1",
126
+ "sass": "^1.102.0",
127
+ "typescript": "^7.0.2",
128
+ "vite": "^8.2.1",
129
129
  "vitest": "^4.1.8"
130
130
  },
131
131
  "packageManager": "pnpm@11.1.3",
132
132
  "engines": {
133
- "node": ">=20.19.0"
133
+ "node": "^22.22.2 || >=24.15.0"
134
134
  },
135
135
  "browserslist": [
136
136
  "last 2 versions",
@@ -108,9 +108,14 @@ export function isFunction(val) {
108
108
  * mutations to the merged result do not bleed back into the source object
109
109
  * (e.g. mutating `instance.options.fontFamilies` should not affect
110
110
  * `AutumnNote.defaults.fontFamilies`).
111
- * @param {object} target
112
- * @param {object} source
113
- * @returns {object}
111
+ *
112
+ * The result starts as a copy of `target`, so it carries `target`'s shape —
113
+ * `source` only overwrites values. Typing the return as `T` rather than
114
+ * `object` is what lets callers read properties off the merged result.
115
+ * @template {object} T
116
+ * @param {T} target
117
+ * @param {object} [source]
118
+ * @returns {T}
114
119
  */
115
120
  export function mergeDeep(target, source) {
116
121
  // Start with a shallow copy of target; clone any arrays to avoid shared refs
@@ -133,16 +138,43 @@ export function mergeDeep(target, source) {
133
138
  }
134
139
  }
135
140
  }
136
- return output;
141
+ // `output` is built key by key from `target`, which no inference can follow.
142
+ return /** @type {T} */ (output);
143
+ }
144
+
145
+ /**
146
+ * Structural equality for option values: plain objects and arrays compare by
147
+ * content, everything else (functions, DOM nodes, class instances) by identity.
148
+ * @param {*} a
149
+ * @param {*} b
150
+ * @returns {boolean}
151
+ */
152
+ export function deepEqual(a, b) {
153
+ if (Object.is(a, b)) return true;
154
+ if (Array.isArray(a) && Array.isArray(b)) {
155
+ return a.length === b.length && a.every((item, i) => deepEqual(item, b[i]));
156
+ }
157
+ if (isPlainObject(a) && isPlainObject(b)) {
158
+ const keys = Object.keys(a);
159
+ return keys.length === Object.keys(b).length
160
+ && keys.every((key) => Object.hasOwn(b, key) && deepEqual(a[key], b[key]));
161
+ }
162
+ return false;
137
163
  }
138
164
 
139
165
  /**
140
- * Checks if value is a plain object.
166
+ * Checks if value is a plain object (an object literal or Object.create(null)).
167
+ *
168
+ * DOM nodes and class instances are not: mergeDeep() used to recurse into
169
+ * them, which turned a `popupContainer` element into a detached copy and
170
+ * stripped the prototype methods off class-based adapters.
141
171
  * @param {*} val
142
172
  * @returns {boolean}
143
173
  */
144
174
  export function isPlainObject(val) {
145
- return val !== null && typeof val === 'object' && !Array.isArray(val);
175
+ if (val === null || typeof val !== 'object') return false;
176
+ const proto = Object.getPrototypeOf(val);
177
+ return proto === Object.prototype || proto === null;
146
178
  }
147
179
 
148
180
  /**
package/src/js/i18n/de.js CHANGED
@@ -159,6 +159,8 @@ export const de = {
159
159
  replaceBtn: 'Ersetzen',
160
160
  noResults: 'Keine Ergebnisse',
161
161
  useRegex: 'Regulären Ausdruck verwenden',
162
+ prevMatch: 'Zurück (Umschalt+Eingabe)',
163
+ nextMatch: 'Weiter (Eingabe)',
162
164
  replaceAllBtn: 'Alle ersetzen',
163
165
  close: '×',
164
166
  },
@@ -167,6 +169,7 @@ export const de = {
167
169
  title: 'Tastenkürzel',
168
170
  ariaLabel: 'Tastenkürzel',
169
171
  close: 'Schließen',
172
+ customCategory: 'Benutzerdefiniert',
170
173
  shortcuts: [
171
174
  {
172
175
  category: 'Textformatierung',
@@ -244,6 +247,13 @@ export const de = {
244
247
  charsLimit: (n, max) => `Zeichen: ${n}/${max}`,
245
248
  },
246
249
 
250
+ a11y: {
251
+ editor: 'Rich-Text-Editor',
252
+ toolbar: 'Editor-Werkzeugleiste',
253
+ statistics: 'Editor-Statistik',
254
+ tableSize: 'Tabellengröße wählen',
255
+ },
256
+
247
257
  tooltips: {
248
258
  link: {
249
259
  ariaLabel: 'Link-Aktionen',
package/src/js/i18n/en.js CHANGED
@@ -162,6 +162,8 @@ export const en = {
162
162
  replaceAllBtn: 'Replace All',
163
163
  noResults: 'No results',
164
164
  useRegex: 'Use Regular Expression',
165
+ prevMatch: 'Previous (Shift+Enter)',
166
+ nextMatch: 'Next (Enter)',
165
167
  close: '\u00d7',
166
168
  },
167
169
 
@@ -176,6 +178,7 @@ export const en = {
176
178
  title: 'Keyboard Shortcuts',
177
179
  ariaLabel: 'Keyboard Shortcuts',
178
180
  close: 'Close',
181
+ customCategory: 'Custom',
179
182
  shortcuts: [
180
183
  {
181
184
  category: 'Text Formatting',
@@ -257,6 +260,13 @@ export const en = {
257
260
  charsLimit: (n, max) => `Chars: ${n}/${max}`,
258
261
  },
259
262
 
263
+ a11y: {
264
+ editor: 'Rich text editor',
265
+ toolbar: 'Editor toolbar',
266
+ statistics: 'Editor statistics',
267
+ tableSize: 'Select table size',
268
+ },
269
+
260
270
  tooltips: {
261
271
  link: {
262
272
  ariaLabel: 'Link actions',
package/src/js/i18n/es.js CHANGED
@@ -159,6 +159,8 @@ export const es = {
159
159
  replaceBtn: 'Reemplazar',
160
160
  noResults: 'Sin resultados',
161
161
  useRegex: 'Usar expresión regular',
162
+ prevMatch: 'Anterior (Mayús+Intro)',
163
+ nextMatch: 'Siguiente (Intro)',
162
164
  replaceAllBtn: 'Reemplazar todo',
163
165
  close: '×',
164
166
  },
@@ -167,6 +169,7 @@ export const es = {
167
169
  title: 'Atajos de teclado',
168
170
  ariaLabel: 'Atajos de teclado',
169
171
  close: 'Cerrar',
172
+ customCategory: 'Personalizados',
170
173
  shortcuts: [
171
174
  {
172
175
  category: 'Formato de texto',
@@ -244,6 +247,13 @@ export const es = {
244
247
  charsLimit: (n, max) => `Caracteres: ${n}/${max}`,
245
248
  },
246
249
 
250
+ a11y: {
251
+ editor: 'Editor de texto enriquecido',
252
+ toolbar: 'Barra de herramientas del editor',
253
+ statistics: 'Estadísticas del editor',
254
+ tableSize: 'Seleccionar tamaño de tabla',
255
+ },
256
+
247
257
  tooltips: {
248
258
  link: {
249
259
  ariaLabel: 'Acciones de enlace',
package/src/js/i18n/fr.js CHANGED
@@ -160,6 +160,8 @@ export const fr = {
160
160
  replaceBtn: 'Remplacer',
161
161
  noResults: 'Aucun résultat',
162
162
  useRegex: 'Utiliser une expression régulière',
163
+ prevMatch: 'Précédent (Maj+Entrée)',
164
+ nextMatch: 'Suivant (Entrée)',
163
165
  replaceAllBtn: 'Tout remplacer',
164
166
  close: '\u00d7',
165
167
  },
@@ -168,6 +170,7 @@ export const fr = {
168
170
  title: 'Raccourcis clavier',
169
171
  ariaLabel: 'Raccourcis clavier',
170
172
  close: 'Fermer',
173
+ customCategory: 'Personnalisés',
171
174
  shortcuts: [
172
175
  {
173
176
  category: 'Mise en forme du texte',
@@ -245,6 +248,13 @@ export const fr = {
245
248
  charsLimit: (n, max) => `Caractères\u00a0: ${n}/${max}`,
246
249
  },
247
250
 
251
+ a11y: {
252
+ editor: 'Éditeur de texte enrichi',
253
+ toolbar: 'Barre d\'outils de l\'éditeur',
254
+ statistics: 'Statistiques de l\'éditeur',
255
+ tableSize: 'Choisir la taille du tableau',
256
+ },
257
+
248
258
  tooltips: {
249
259
  link: {
250
260
  ariaLabel: 'Actions du lien',
package/src/js/i18n/ja.js CHANGED
@@ -160,6 +160,8 @@ export const ja = {
160
160
  replaceBtn: '置換',
161
161
  noResults: '結果なし',
162
162
  useRegex: '正規表現を使用',
163
+ prevMatch: '前へ (Shift+Enter)',
164
+ nextMatch: '次へ (Enter)',
163
165
  replaceAllBtn: 'すべて置換',
164
166
  close: '\u00d7',
165
167
  },
@@ -168,6 +170,7 @@ export const ja = {
168
170
  title: 'キーボードショートカット',
169
171
  ariaLabel: 'キーボードショートカット',
170
172
  close: '閉じる',
173
+ customCategory: 'カスタム',
171
174
  shortcuts: [
172
175
  {
173
176
  category: 'テキスト書式',
@@ -245,6 +248,13 @@ export const ja = {
245
248
  charsLimit: (n, max) => `文字数: ${n}/${max}`,
246
249
  },
247
250
 
251
+ a11y: {
252
+ editor: 'リッチテキストエディター',
253
+ toolbar: 'エディターツールバー',
254
+ statistics: 'エディターの統計',
255
+ tableSize: '表のサイズを選択',
256
+ },
257
+
248
258
  tooltips: {
249
259
  link: {
250
260
  ariaLabel: 'リンク操作',
package/src/js/i18n/ko.js CHANGED
@@ -159,6 +159,8 @@ export const ko = {
159
159
  replaceBtn: '바꾸기',
160
160
  noResults: '결과 없음',
161
161
  useRegex: '정규식 사용',
162
+ prevMatch: '이전 (Shift+Enter)',
163
+ nextMatch: '다음 (Enter)',
162
164
  replaceAllBtn: '모두 바꾸기',
163
165
  close: '×',
164
166
  },
@@ -167,6 +169,7 @@ export const ko = {
167
169
  title: '키보드 단축키',
168
170
  ariaLabel: '키보드 단축키',
169
171
  close: '닫기',
172
+ customCategory: '사용자 지정',
170
173
  shortcuts: [
171
174
  {
172
175
  category: '텍스트 서식',
@@ -244,6 +247,13 @@ export const ko = {
244
247
  charsLimit: (n, max) => `글자: ${n}/${max}`,
245
248
  },
246
249
 
250
+ a11y: {
251
+ editor: '서식 있는 텍스트 편집기',
252
+ toolbar: '편집기 도구 모음',
253
+ statistics: '편집기 통계',
254
+ tableSize: '표 크기 선택',
255
+ },
256
+
247
257
  tooltips: {
248
258
  link: {
249
259
  ariaLabel: '링크 작업',
package/src/js/i18n/vi.js CHANGED
@@ -161,6 +161,8 @@ export const vi = {
161
161
  replaceAllBtn: 'Thay thế tất cả',
162
162
  noResults: 'Không có kết quả',
163
163
  useRegex: 'Dùng biểu thức chính quy',
164
+ prevMatch: 'Trước (Shift+Enter)',
165
+ nextMatch: 'Tiếp (Enter)',
164
166
  close: '\u00d7',
165
167
  },
166
168
 
@@ -168,6 +170,7 @@ export const vi = {
168
170
  title: 'Phím tắt bàn phím',
169
171
  ariaLabel: 'Phím tắt bàn phím',
170
172
  close: 'Đóng',
173
+ customCategory: 'Tùy chỉnh',
171
174
  shortcuts: [
172
175
  {
173
176
  category: 'Định dạng văn bản',
@@ -245,6 +248,13 @@ export const vi = {
245
248
  charsLimit: (n, max) => `Ký tự: ${n}/${max}`,
246
249
  },
247
250
 
251
+ a11y: {
252
+ editor: 'Trình soạn thảo văn bản',
253
+ toolbar: 'Thanh công cụ soạn thảo',
254
+ statistics: 'Thống kê văn bản',
255
+ tableSize: 'Chọn kích thước bảng',
256
+ },
257
+
248
258
  tooltips: {
249
259
  link: {
250
260
  ariaLabel: 'Hành động liên kết',
package/src/js/i18n/zh.js CHANGED
@@ -160,6 +160,8 @@ export const zh = {
160
160
  replaceBtn: '替换',
161
161
  noResults: '没有结果',
162
162
  useRegex: '使用正则表达式',
163
+ prevMatch: '上一个 (Shift+Enter)',
164
+ nextMatch: '下一个 (Enter)',
163
165
  replaceAllBtn: '全部替换',
164
166
  close: '\u00d7',
165
167
  },
@@ -168,6 +170,7 @@ export const zh = {
168
170
  title: '键盘快捷键',
169
171
  ariaLabel: '键盘快捷键',
170
172
  close: '关闭',
173
+ customCategory: '自定义',
171
174
  shortcuts: [
172
175
  {
173
176
  category: '文字格式',
@@ -245,6 +248,13 @@ export const zh = {
245
248
  charsLimit: (n, max) => `字符数: ${n}/${max}`,
246
249
  },
247
250
 
251
+ a11y: {
252
+ editor: '富文本编辑器',
253
+ toolbar: '编辑器工具栏',
254
+ statistics: '编辑器统计',
255
+ tableSize: '选择表格大小',
256
+ },
257
+
248
258
  tooltips: {
249
259
  link: {
250
260
  ariaLabel: '链接操作',
package/types/index.d.ts CHANGED
@@ -29,6 +29,40 @@ export interface AutoSaveAdapter {
29
29
  remove?(payload: { key: string; context: Context }): void | Promise<void>;
30
30
  }
31
31
 
32
+ /**
33
+ * One entry in the right-click menu. Most entries are a labelled row with an
34
+ * `action`; the remaining fields each select a different kind of row, and an
35
+ * entry that sets one of them ignores `action`.
36
+ */
37
+ export interface ContextMenuItem {
38
+ /** Unique identifier. Omitted on separators and other non-interactive rows. */
39
+ name?: string;
40
+ label?: string;
41
+ /** Icon markup (SVG or HTML). */
42
+ icon?: string;
43
+ /** Key into `locale.contextMenu`; preferred over `label` when it resolves. */
44
+ localeKey?: string;
45
+ action?: (context: Context) => void;
46
+ /** Greys the row out and blocks the click. A function is evaluated per open. */
47
+ disabled?: boolean | ((context: Context) => boolean);
48
+ /** Opens a submenu instead of firing `action`. */
49
+ navigate?: ContextMenuItem[] | ((context: Context) => ContextMenuItem[]);
50
+ /** A horizontal rule. `sep` is an accepted alias. */
51
+ separator?: boolean;
52
+ /** @see separator */
53
+ sep?: boolean;
54
+ /** Renders as the submenu's "back" row rather than a command. */
55
+ back?: boolean;
56
+ /** Renders a row of quick colour swatches for `colorType`. */
57
+ colorStrip?: 'foreColor' | 'hiliteColor';
58
+ /** Renders the full palette plus a native colour input, for `colorType`. */
59
+ colorPalette?: boolean;
60
+ /** Which command `colorStrip` / `colorPalette` applies. */
61
+ colorType?: 'foreColor' | 'hiliteColor';
62
+ /** Renders the drag-to-size table picker. */
63
+ tableGrid?: boolean;
64
+ }
65
+
32
66
  export interface SlashCommand {
33
67
  id: string;
34
68
  label?: string;
@@ -45,6 +79,38 @@ export interface CollaborationAdapter {
45
79
  onLocalChange?(html: string, context: Context): void | Promise<void>;
46
80
  }
47
81
 
82
+ /** Built-in design tokens accepted by `themeVars` (any `--an-*` key also passes through). */
83
+ export interface AsnThemeVars {
84
+ primary?: string;
85
+ 'primary-hover'?: string;
86
+ border?: string;
87
+ bg?: string;
88
+ 'bg-toolbar'?: string;
89
+ 'bg-btn-hover'?: string;
90
+ 'bg-btn-active'?: string;
91
+ text?: string;
92
+ muted?: string;
93
+ 'statusbar-bg'?: string;
94
+ radius?: string;
95
+ 'radius-sm'?: string;
96
+ 'font-family'?: string;
97
+ 'font-size'?: string;
98
+ 'line-height'?: string;
99
+ [customProperty: `--an-${string}`]: string | number | undefined;
100
+ }
101
+
102
+ /**
103
+ * A keyMap value. Built-in commands: undo, redo, bold, italic, underline,
104
+ * inlineCode, link, find, findReplace, shortcuts, pastePlainText — or any
105
+ * toolbar button name. A handler returning `false` lets the key through.
106
+ */
107
+ export type AsnKeyBinding =
108
+ | false
109
+ | null
110
+ | string
111
+ | ((context: Context, event: KeyboardEvent) => void | false)
112
+ | { run: (context: Context, event: KeyboardEvent) => void | false; description?: string };
113
+
48
114
  export interface AsnOptions {
49
115
  /** Placeholder text when the editor is empty. */
50
116
  placeholder?: string;
@@ -58,6 +124,12 @@ export interface AsnOptions {
58
124
  focus?: boolean;
59
125
  /** Show the resize handle in the statusbar. */
60
126
  resizable?: boolean;
127
+ /**
128
+ * Show the statusbar (word/char count and resize handle). Default `true`.
129
+ * When `false` the bar is hidden, but `getWordCount()`/`getCharCount()`
130
+ * keep working. Can be toggled at runtime with `updateOptions()`.
131
+ */
132
+ statusbar?: boolean;
61
133
  /** Toolbar button group configuration. */
62
134
  toolbar?: Array<Array<ToolbarItemDef | string>>;
63
135
  /** Use Bootstrap button classes on toolbar buttons. */
@@ -98,6 +170,16 @@ export interface AsnOptions {
98
170
  onInit?: (context: Context) => void;
99
171
  /** Callback fired just before the editor instance is destroyed. */
100
172
  onDestroy?: (context: Context) => void;
173
+ /**
174
+ * Fired before a command runs from the toolbar, a keyboard shortcut, the
175
+ * context menu or the bubble toolbar. Return `false` to cancel it.
176
+ */
177
+ onBeforeCommand?: (data: {
178
+ name: string;
179
+ value?: unknown;
180
+ source: 'toolbar' | 'shortcut' | 'contextMenu' | 'bubbleToolbar';
181
+ event?: KeyboardEvent;
182
+ }) => void | false;
101
183
  /** Callback fired whenever the selection changes inside the editor. */
102
184
  onSelectionChange?: (context: Context) => void;
103
185
  /** Callback fired when the character limit is reached. */
@@ -140,8 +222,29 @@ export interface AsnOptions {
140
222
  stickyToolbar?: boolean;
141
223
  /** Top offset in px for sticky toolbar (e.g. height of a fixed nav bar). */
142
224
  stickyToolbarOffset?: number;
143
- /** Colour theme: 'light' (default) | 'dark'. */
225
+ /**
226
+ * Colour theme: 'light' (default), 'dark', or 'auto' (follows the OS
227
+ * `prefers-color-scheme`). Can be changed at runtime with `updateOptions()`.
228
+ */
144
229
  theme?: 'light' | 'dark' | 'auto';
230
+ /**
231
+ * Design-token overrides applied to this editor and its floating UI, e.g.
232
+ * `{ primary: '#f97316', radius: '10px' }`. Keys are token names (`primary`)
233
+ * or full custom properties (`--an-primary`). See README "Theming".
234
+ */
235
+ themeVars?: AsnThemeVars | null;
236
+ /**
237
+ * Added to the z-index of every floating layer (tooltips, popovers, dialogs,
238
+ * fullscreen). Use it to lift the editor's UI above a host modal. Default 0.
239
+ */
240
+ zIndexOffset?: number;
241
+ /**
242
+ * Where the editor mounts its floating UI: a selector, an element or a
243
+ * ShadowRoot. Default `document.body`. Use it inside a modal that traps
244
+ * focus, or a shadow root. The element must not have `transform`, `filter`
245
+ * or `contain` set, because floating UI is positioned against the viewport.
246
+ */
247
+ popupContainer?: string | Element | ShadowRoot | null;
145
248
  /** Auto-load Prism.js for syntax highlighting inside code blocks. */
146
249
  codeHighlight?: boolean;
147
250
  /** CDN base URL for Prism assets. */
@@ -165,6 +268,56 @@ export interface AsnOptions {
165
268
  defaultFontSize?: string;
166
269
  /** Font families shown in the font-family toolbar dropdown. */
167
270
  fontFamilies?: string[];
271
+ /** Sizes offered by the font-size dropdown (CSS lengths). Default 8px–72px. */
272
+ fontSizes?: string[];
273
+ /** Values offered by the line-height dropdown. Default '1.0'–'3.0'. */
274
+ lineHeights?: string[];
275
+ /** Block formats offered by the paragraph-style dropdown; `value` is a block tag name. */
276
+ paragraphStyles?: Array<{ value: string; label: string }>;
277
+ /** Replaces the built-in colour swatches (toolbar, bubble toolbar, context menu). `colorSwatches` are still prepended. */
278
+ colorPalette?: string[] | null;
279
+ /**
280
+ * Per-editor icon overrides keyed by button name (`bold`) or icon id
281
+ * (`list-ul`). A value starting with `<` is markup (e.g. an SVG); anything
282
+ * else is a CSS class list rendered as `<i class="...">`.
283
+ */
284
+ icons?: Record<string, string> | null;
285
+ /**
286
+ * Per-editor button definitions, usable by name in `toolbar` without the
287
+ * global registry: `{ save: { icon: 'save', tooltip: 'Save', action } }`.
288
+ */
289
+ buttons?: Record<string, Omit<ButtonDef, 'name'> | Omit<DropdownDef, 'name'>> | ToolbarItemDef[] | null;
290
+ /**
291
+ * Keyboard shortcuts merged over the defaults. Keys are combos (`Mod+B`,
292
+ * `Ctrl+Alt+1`; `Mod` = Ctrl or Cmd). Values: `false` to disable, a
293
+ * built-in command or toolbar button name, a handler, or `{ run, description }`.
294
+ */
295
+ keyMap?: Record<string, AsnKeyBinding> | null;
296
+ /** Link dialog defaults. */
297
+ linkDefaults?: {
298
+ /** Pre-tick "open in new tab" for new links. Default false. */
299
+ openInNewTab?: boolean;
300
+ /** `rel` for new-tab links; `noopener` is always added. Default 'noopener noreferrer'. */
301
+ rel?: string;
302
+ /** Prefix for bare domains ('example.com'); '' disables. Relative links are never prefixed. Default 'https://'. */
303
+ defaultProtocol?: string;
304
+ } | null;
305
+ /**
306
+ * Extra video providers for the video dialog, tried before the built-in
307
+ * YouTube/Vimeo rules. The embed URL's host must be built in or listed in
308
+ * `iframeHosts`, otherwise the provider is ignored.
309
+ */
310
+ videoProviders?: Array<{
311
+ name: string;
312
+ match: RegExp;
313
+ embed: (match: RegExpExecArray, url: string) => string;
314
+ }> | null;
315
+ /**
316
+ * Extra hostnames whose iframes survive sanitisation (exact hostnames,
317
+ * HTTPS only), e.g. `['player.twitch.tv']`. Every listed host can render
318
+ * arbitrary content inside the document — list only hosts you trust.
319
+ */
320
+ iframeHosts?: string[] | null;
168
321
  /** Start the editor in read-only (non-editable) mode. */
169
322
  readOnly?: boolean;
170
323
  /** Enable browser spellcheck in the editable area (default: true). */
@@ -188,7 +341,11 @@ export interface AsnOptions {
188
341
  /** Insert a header row (<thead>) when creating new tables. */
189
342
  tableHeaderRow?: boolean;
190
343
  /** Callback fired after every paste event. */
191
- onPaste?: (data: { text: string; html: string | null }) => void;
344
+ /**
345
+ * Fired on every paste before anything is inserted. Return `false` to cancel
346
+ * the paste, or an HTML string to insert instead (it is sanitised).
347
+ */
348
+ onPaste?: (data: { text: string; html: string | null }) => void | false | string;
192
349
  /** Callback fired when pasted or dropped content cannot be processed. */
193
350
  onPasteError?: (error: PasteErrorData) => void;
194
351
  /** Additional color swatches shown at the top of the color picker. */
@@ -215,7 +372,7 @@ export interface AsnOptions {
215
372
  autoSaveRestoreTimeout?: number;
216
373
  /** Callback fired after the user chooses to restore a draft. */
217
374
  onAutoSaveRestore?: (html: string, context: Context) => void;
218
- /** Maximum paste size in MB before paste is silently dropped (0 = unlimited). Default: 5. */
375
+ /** Maximum paste size in bytes; larger pastes are dropped and `pasteError` fires (0 = unlimited). Default: 5242880 (5 MB). */
219
376
  maxPasteSize?: number;
220
377
  /** Minimum image dimension in px during resize (width and height). Default: 20. */
221
378
  minImageSize?: number;
@@ -237,6 +394,14 @@ export interface AsnOptions {
237
394
  /** @mention autocomplete configuration. Activated when mention.onSearch is provided. */
238
395
  mention?: MentionOptions | null;
239
396
 
397
+ // ---- Right-click menu ------------------------------------------------------
398
+
399
+ /**
400
+ * Replaces the built-in right-click menu. Omit it, or omit `items`, to keep
401
+ * the default list.
402
+ */
403
+ contextMenu?: { items?: ContextMenuItem[] } | null;
404
+
240
405
  // ---- Slash command menu ----------------------------------------------------
241
406
 
242
407
  /** Show a "/" command palette for quick block insertion (headings, lists, table, image, ...). Default: true. */
@@ -247,6 +412,7 @@ export interface AsnOptions {
247
412
  documentAdapters?: Record<string, DocumentAdapter>;
248
413
  /** Optional external image processor, such as a Web Worker bridge. */
249
414
  imageProcessor?: (file: File, helpers: { context: Context }) => string | Promise<string>;
415
+ /** Bridge notified with local HTML changes. */
250
416
  collaborationAdapter?: CollaborationAdapter | null;
251
417
  /** Adds stable IDs to top-level blocks for external collaboration adapters. */
252
418
  blockIds?: boolean;
@@ -404,6 +570,8 @@ export interface AsnLocale {
404
570
  title: string;
405
571
  ariaLabel: string;
406
572
  close: string;
573
+ /** Heading of the section listing `keyMap` shortcuts. */
574
+ customCategory: string;
407
575
  shortcuts: Array<{ category: string; items: Array<{ keys: string; action: string }> }>;
408
576
  };
409
577
  contextMenu: Record<string, string>;
@@ -414,6 +582,13 @@ export interface AsnLocale {
414
582
  chars: (n: number) => string;
415
583
  charsLimit: (n: number, max: number) => string;
416
584
  };
585
+ /** Accessible names for landmarks that have no visible label. */
586
+ a11y: {
587
+ editor: string;
588
+ toolbar: string;
589
+ statistics: string;
590
+ tableSize: string;
591
+ };
417
592
  tooltips: {
418
593
  link: Record<string, string>;
419
594
  image: Record<string, string>;
@@ -507,13 +682,14 @@ export declare class Context {
507
682
  invoke(path: string, ...args: unknown[]): unknown;
508
683
 
509
684
  /** Subscribes to an editor event. Returns an unsubscribe function. */
510
- on(eventName: string, handler: (...args: unknown[]) => void): () => void;
685
+ on(eventName: string, handler: (...args: any[]) => unknown): () => void;
511
686
 
512
687
  /** Unsubscribes from an editor event. */
513
688
  off(eventName: string, handler: (...args: unknown[]) => void): void;
514
689
 
515
690
  /** Triggers an editor event. */
516
- triggerEvent(eventName: string, ...args: unknown[]): void;
691
+ /** Fires an event; returns the last value a handler returned (other than undefined). */
692
+ triggerEvent(eventName: string, ...args: unknown[]): unknown;
517
693
 
518
694
  /** Applies runtime-safe option changes without recreating the editor. */
519
695
  updateOptions(overrides: Partial<AsnOptions>): this;
@@ -776,6 +952,13 @@ export interface AutumnNoteStatic {
776
952
  * referenced by string name in toolbar configuration.
777
953
  */
778
954
  registerButton(btnDef: ToolbarItemDef): this;
955
+
956
+ /**
957
+ * Registers an icon for every editor, keyed by button name or icon id.
958
+ * `icon` is SVG/HTML markup, or a CSS class list such as `'bi bi-type-bold'`.
959
+ * The per-editor `icons` option takes precedence.
960
+ */
961
+ registerIcon(name: string, icon: string): this;
779
962
  registerSlashCommand(command: SlashCommand): this;
780
963
 
781
964
  /**