autumnnote 1.13.0 → 1.15.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,16 +1,17 @@
1
1
  {
2
2
  "name": "autumnnote",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
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
- "main": "dist/autumnnote.umd.js",
5
+ "type": "module",
6
+ "main": "dist/autumnnote.cjs",
6
7
  "module": "dist/autumnnote.es.js",
7
8
  "types": "types/index.d.ts",
8
9
  "style": "dist/autumnnote.css",
9
10
  "exports": {
10
11
  ".": {
12
+ "types": "./types/index.d.ts",
11
13
  "import": "./dist/autumnnote.es.js",
12
- "require": "./dist/autumnnote.umd.js",
13
- "types": "./types/index.d.ts"
14
+ "require": "./dist/autumnnote.cjs"
14
15
  },
15
16
  "./dist/autumnnote.css": "./dist/autumnnote.css"
16
17
  },
@@ -21,18 +22,23 @@
21
22
  ],
22
23
  "scripts": {
23
24
  "dev": "vite",
24
- "build": "vite build && vite build --config vite.umd.config.js",
25
+ "build": "vite build && vite build --config vite.umd.config.js && node scripts/create-cjs-alias.mjs",
25
26
  "build:demo": "vite build --config vite.demo.config.js",
26
27
  "preview": "vite preview",
27
28
  "prepublishOnly": "npm run build",
28
29
  "test": "vitest run",
30
+ "test:browser": "vitest run --config vitest.browser.config.js",
29
31
  "test:watch": "vitest",
30
- "lint": "eslint src",
31
- "typecheck": "tsc --noEmit",
32
+ "lint": "eslint src test",
33
+ "typecheck": "tsc --noEmit && pnpm --filter autumnnote-react typecheck && pnpm --filter autumnnote-vue typecheck",
32
34
  "build:cdn": "vite build --config vite.cdn.config.js",
33
35
  "analyze": "cross-env ANALYZE=1 vite build",
34
36
  "bench": "vitest bench",
35
- "test:coverage": "vitest run --coverage"
37
+ "test:coverage": "vitest run --coverage",
38
+ "test:wrappers": "pnpm --filter autumnnote-react test && pnpm --filter autumnnote-vue test",
39
+ "build:wrappers": "pnpm --filter autumnnote-react build && pnpm --filter autumnnote-vue build",
40
+ "check:bundle": "node scripts/check-bundle-size.mjs",
41
+ "check": "pnpm lint && pnpm typecheck && pnpm test:coverage && pnpm build && pnpm test:wrappers && pnpm check:bundle && pnpm build:demo && pnpm build:wrappers"
36
42
  },
37
43
  "keywords": [
38
44
  "wysiwyg",
@@ -90,19 +96,22 @@
90
96
  "author": "Minh Pham",
91
97
  "license": "MIT",
92
98
  "devDependencies": {
93
- "@vitest/browser": "^4.1.2",
94
- "@vitest/coverage-v8": "^4.1.2",
99
+ "@vitest/browser": "^4.1.8",
100
+ "@vitest/browser-playwright": "^4.1.10",
101
+ "@vitest/coverage-v8": "^4.1.8",
95
102
  "cross-env": "^10.1.0",
96
- "eslint": "^10.2.0",
103
+ "eslint": "10.3.0",
97
104
  "jsdom": "^25.0.1",
105
+ "playwright": "^1.61.1",
98
106
  "rollup-plugin-visualizer": "^7.0.1",
99
107
  "sass": "^1.99.0",
100
108
  "typescript": "^6.0.2",
101
- "vite": "^8.0.3",
102
- "vitest": "^4.1.2"
109
+ "vite": "^8.0.16",
110
+ "vitest": "^4.1.8"
103
111
  },
112
+ "packageManager": "pnpm@11.1.3",
104
113
  "engines": {
105
- "node": ">=18.0.0"
114
+ "node": ">=20.19.0"
106
115
  },
107
116
  "browserslist": [
108
117
  "last 2 versions",
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  /** Tags that are unconditionally removed from editor content. */
9
- const PROHIBITED_TAGS = ['script', 'style', 'iframe', 'object', 'embed', 'form', 'base', 'template', 'link', 'meta'];
9
+ const PROHIBITED_TAGS = ['script', 'style', 'iframe', 'object', 'embed', 'form', 'base', 'template', 'link', 'meta', 'noscript', 'portal', 'frame', 'frameset', 'applet'];
10
10
 
11
11
  /** Tags whose element wrapper is stripped but content (child nodes) is preserved. */
12
12
  const UNWRAP_TAGS = new Set(['button']);
@@ -35,6 +35,11 @@ const TRUSTED_IFRAME_HOSTS = new Set([
35
35
  'player.vimeo.com',
36
36
  ]);
37
37
 
38
+ const SAFE_LINK_PROTOCOLS = new Set(['http:', 'https:', 'mailto:', 'tel:']);
39
+ const SAFE_MEDIA_PROTOCOLS = new Set(['http:', 'https:', 'blob:']);
40
+ const SAFE_RASTER_DATA_RE = /^data:image\/(?:png|jpe?g|gif|webp|avif|bmp);base64,[a-z0-9+/=\s]+$/i;
41
+ const URL_BASE = 'https://autumnnote.invalid/';
42
+
38
43
  /**
39
44
  * Produce a sanitized HTML string with dangerous elements and attributes removed.
40
45
  *
@@ -74,6 +79,15 @@ export function sanitiseHTML(html, { allowIframes = false } = {}) {
74
79
  continue;
75
80
  }
76
81
 
82
+ // Invalid embeds are removed rather than retained as empty iframes.
83
+ if (tag === 'iframe') {
84
+ const src = el.getAttribute('src');
85
+ if (!src || !isTrustedIframeSrc(src)) {
86
+ el.remove();
87
+ continue;
88
+ }
89
+ }
90
+
77
91
  // Strip dangerous attributes
78
92
  for (const attr of Array.from(el.attributes)) {
79
93
  // Remove all event handlers (onclick, onload, onerror, …)
@@ -95,14 +109,12 @@ export function sanitiseHTML(html, { allowIframes = false } = {}) {
95
109
  // Sanitise URL attributes
96
110
  if (URL_ATTRS.includes(attr.name)) {
97
111
  const val = attr.value.trim();
98
- if (/^(javascript|vbscript):/i.test(val)) {
112
+ const isMediaSource = attr.name === 'src' &&
113
+ ['IMG', 'VIDEO', 'AUDIO', 'SOURCE'].includes(el.tagName);
114
+ if (!isSafeUrl(val, { media: isMediaSource, allowData: el.tagName === 'IMG' })) {
99
115
  el.removeAttribute(attr.name);
100
116
  continue;
101
117
  }
102
- // Allow data: URIs only on img[src] (base64 image uploads); block elsewhere
103
- if (/^data:/i.test(val) && !(attr.name === 'src' && el.tagName === 'IMG')) {
104
- el.removeAttribute(attr.name);
105
- }
106
118
  }
107
119
  // Strip iframe HTML-injection vectors; limit src to trusted hosts
108
120
  if (el.tagName === 'IFRAME') {
@@ -116,6 +128,10 @@ export function sanitiseHTML(html, { allowIframes = false } = {}) {
116
128
  }
117
129
  }
118
130
 
131
+ if (tag === 'a' && el.getAttribute('target') === '_blank') {
132
+ el.setAttribute('rel', 'noopener noreferrer');
133
+ }
134
+
119
135
  // Allow only input[type="checkbox"] inside ul.an-checklist li
120
136
  if (tag === 'input') {
121
137
  const inChecklist = el.closest('ul.an-checklist') !== null &&
@@ -184,12 +200,32 @@ function isTrustedIframeSrc(src) {
184
200
  * Optionally blocked: data: (safe to allow for img/src base64 embeds)
185
201
  *
186
202
  * @param {string} url
187
- * @param {{ allowData?: boolean }} [opts]
203
+ * @param {{ allowData?: boolean, media?: boolean }} [opts]
188
204
  * @returns {string|null} The original URL if safe, null if rejected.
189
205
  */
190
- export function sanitiseUrl(url, { allowData = false } = {}) {
206
+ export function sanitiseUrl(url, { allowData = false, media = allowData } = {}) {
191
207
  const trimmed = (url || '').trim();
192
- if (/^(javascript|vbscript):/i.test(trimmed)) return null;
193
- if (!allowData && /^data:/i.test(trimmed)) return null;
194
- return url;
208
+ if (!trimmed && url == null) return null;
209
+ return isSafeUrl(trimmed, { media, allowData }) ? url : null;
210
+ }
211
+
212
+ /**
213
+ * Validate a URL using the browser URL parser so ASCII whitespace/control
214
+ * characters cannot disguise a dangerous protocol (for example java\nscript:).
215
+ * @param {string} value
216
+ * @param {{media?: boolean, allowData?: boolean}} [options]
217
+ * @returns {boolean}
218
+ */
219
+ function isSafeUrl(value, { media = false, allowData = false } = {}) {
220
+ const trimmed = (value || '').trim();
221
+ if (!trimmed) return true;
222
+ if (allowData && SAFE_RASTER_DATA_RE.test(trimmed)) return true;
223
+
224
+ try {
225
+ const parsed = new URL(trimmed, URL_BASE);
226
+ const protocols = media ? SAFE_MEDIA_PROTOCOLS : SAFE_LINK_PROTOCOLS;
227
+ return protocols.has(parsed.protocol);
228
+ } catch {
229
+ return false;
230
+ }
195
231
  }
package/src/js/index.js CHANGED
@@ -144,7 +144,7 @@ const AutumnNote = {
144
144
  buttons,
145
145
 
146
146
  /** Library version */
147
- version: '1.13.0',
147
+ version: '1.15.0',
148
148
  };
149
149
 
150
150
  // ---------------------------------------------------------------------------
@@ -371,7 +371,11 @@ export class Clipboard {
371
371
  execCommand('insertHTML', html);
372
372
  this.context.invoke('editor.afterCommand');
373
373
  };
374
- reader.onerror = () => console.warn('[AutumnNote] Failed to read dropped markdown file', file.name);
374
+ reader.onerror = () => {
375
+ const message = `Failed to read dropped markdown file "${file.name}".`;
376
+ console.warn(`[AutumnNote] ${message}`);
377
+ this.context.triggerEvent('pasteError', { message });
378
+ };
375
379
  reader.readAsText(file);
376
380
  }
377
381
 
@@ -497,8 +497,7 @@ export class Editor {
497
497
 
498
498
  undo() {
499
499
  if (this._history) {
500
- clearTimeout(this._snapshotTimer);
501
- this._snapshotTimer = null;
500
+ this._flushPendingSnapshot();
502
501
  this._history.undo();
503
502
  this.context.invoke('toolbar.refresh');
504
503
  this.context.invoke('statusbar.update');
@@ -508,8 +507,7 @@ export class Editor {
508
507
 
509
508
  redo() {
510
509
  if (this._history) {
511
- clearTimeout(this._snapshotTimer);
512
- this._snapshotTimer = null;
510
+ this._flushPendingSnapshot();
513
511
  this._history.redo();
514
512
  this.context.invoke('toolbar.refresh');
515
513
  this.context.invoke('statusbar.update');
@@ -517,6 +515,14 @@ export class Editor {
517
515
  }
518
516
  }
519
517
 
518
+ /** Records a debounced change before an immediate undo/redo command. */
519
+ _flushPendingSnapshot() {
520
+ if (this._snapshotTimer === null) return;
521
+ clearTimeout(this._snapshotTimer);
522
+ this._snapshotTimer = null;
523
+ this._history?.recordUndo();
524
+ }
525
+
520
526
  canUndo() {
521
527
  return this._history ? this._history.canUndo() : false;
522
528
  }
@@ -444,6 +444,7 @@ export class ImageCropOverlay {
444
444
  * @param {(e: MouseEvent) => void} onMove
445
445
  */
446
446
  _attachDocDrag(onMove) {
447
+ this._dragDisposers?.();
447
448
  const onTouchMove = (e) => {
448
449
  e.preventDefault();
449
450
  const te3 = /** @type {TouchEvent} */ (e); onMove(/** @type {MouseEvent} */ (/** @type {unknown} */ ({ clientX: te3.touches[0].clientX, clientY: te3.touches[0].clientY })));
@@ -455,7 +456,9 @@ export class ImageCropOverlay {
455
456
  document.removeEventListener('touchend', cleanup);
456
457
  document.body.style.userSelect = '';
457
458
  document.body.style.cursor = '';
459
+ this._dragDisposers = null;
458
460
  };
461
+ this._dragDisposers = cleanup;
459
462
  document.body.style.userSelect = 'none';
460
463
  document.addEventListener('mousemove', onMove);
461
464
  document.addEventListener('mouseup', cleanup, { once: true });
@@ -561,6 +564,8 @@ export class ImageCropOverlay {
561
564
  * @param {boolean} _committed - reserved for future use
562
565
  */
563
566
  _close(_committed) {
567
+ this._dragDisposers?.();
568
+ this._dragDisposers = null;
564
569
  this._disposers.forEach((d) => d());
565
570
  this._disposers = [];
566
571
 
@@ -239,6 +239,7 @@ export class TableTooltip {
239
239
  !this._activeTable.contains(et) &&
240
240
  !this._el.contains(et) &&
241
241
  !this._sizePopover?.contains(et) &&
242
+ !this._shadePopover?.contains(et) &&
242
243
  !this._borderColorPopover?.contains(et)) {
243
244
  this._hide();
244
245
  }
@@ -1058,7 +1059,7 @@ export class TableTooltip {
1058
1059
 
1059
1060
  const d1 = on(applyBtn, 'click', () => {
1060
1061
  const val = Number.parseInt(this._sizeInputEl.value, 10);
1061
- if (val > 0 && typeof this._sizeApply === 'function') this._sizeApply(val);
1062
+ if (val >= 0 && typeof this._sizeApply === 'function') this._sizeApply(val);
1062
1063
  this._hideSizePopover();
1063
1064
  });
1064
1065
  const d2 = on(cancelBtn, 'click', () => this._hideSizePopover());
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { createElement, on } from '../core/dom.js';
11
+ import { sanitiseUrl } from '../core/sanitise.js';
11
12
  import { BaseDialog } from './BaseDialog.js';
12
13
 
13
14
  const ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2"/></svg>`;
@@ -115,12 +116,7 @@ export class VideoDialog extends BaseDialog {
115
116
  */
116
117
  _parseVideoUrl(url) {
117
118
  if (!url) return null;
118
-
119
- // Validate — block javascript: and other dangerous protocols
120
- try {
121
- const parsed = new URL(url);
122
- if (/^javascript:/i.test(parsed.protocol) || /^vbscript:/i.test(parsed.protocol)) return null;
123
- } catch { return null; }
119
+ if (!sanitiseUrl(url, { media: true })) return null;
124
120
 
125
121
  // YouTube watch: https://www.youtube.com/watch?v=ID
126
122
  const ytWatch = /(?:youtube\.com\/watch\?(?:.*&)?v=|youtube\.com\/embed\/)([a-zA-Z0-9_-]{11})/.exec(url);
@@ -182,13 +178,7 @@ export class VideoDialog extends BaseDialog {
182
178
  }
183
179
 
184
180
  // Unknown URL — let the user try as a direct video
185
- const safeSrc = (() => {
186
- try {
187
- const p = new URL(url);
188
- if (/^javascript:/i.test(p.protocol) || /^vbscript:/i.test(p.protocol)) return null;
189
- return url;
190
- } catch { return null; }
191
- })();
181
+ const safeSrc = sanitiseUrl(url, { media: true });
192
182
  if (!safeSrc) return null;
193
183
 
194
184
  const escapedSrc = safeSrc.replaceAll('"', '%22');
@@ -50,6 +50,7 @@ import { defaultToolbar } from './module/Buttons.js';
50
50
  * @property {number} [maxWords] - Maximum word count (0 = unlimited). Shows warning in statusbar.
51
51
  * @property {boolean} [tableHeaderRow] - Insert a header row (<thead><th>) when creating tables
52
52
  * @property {Function} [onPaste] - Callback fired on every paste: ({ text, html }) => void
53
+ * @property {Function} [onPasteError] - Callback fired when pasted or dropped content cannot be processed
53
54
  * @property {Function} [onSelectionChange] - Callback fired on cursor/selection change: (context) => void
54
55
  * @property {string[]} [colorSwatches] - Custom brand colour swatches prepended to the colour-picker palette
55
56
  * @property {Function} [onDestroy] - Callback fired when the editor is destroyed: (context) => void
@@ -143,6 +144,8 @@ export const defaultOptions = {
143
144
  colorSwatches: [],
144
145
  // Callback fired after a paste event: function({ text, html })
145
146
  onPaste: null,
147
+ // Callback fired for rejected/failed paste and drop payloads: function({ message, size?, maxBytes? })
148
+ onPasteError: null,
146
149
  // Callback fired just before the editor instance is destroyed
147
150
  onDestroy: null,
148
151
  // Callback fired when the character limit is reached: function(context)
@@ -0,0 +1,32 @@
1
+ export const ELEMENT_NODE: 1;
2
+ export const TEXT_NODE: 3;
3
+ export function isElement(node: Node): boolean;
4
+ export function isText(node: Node): boolean;
5
+ export function isVoid(node: Node): boolean;
6
+ export function isPara(node: Node): boolean;
7
+ export function isLi(node: Node): boolean;
8
+ export function isList(node: Node): boolean;
9
+ export function isTable(node: Node): boolean;
10
+ export function isInline(node: Node): boolean;
11
+ export function isEditable(node: Node): boolean;
12
+ export function isAnchor(node: Node): boolean;
13
+ export function isImage(node: Node): boolean;
14
+ export function closest(node: Node, predicate: (node: Node) => boolean, stopAt?: Node): Node | null;
15
+ export function closestPara(node: Node, editable?: Node): Node | null;
16
+ export function ancestors(node: Node, stopAt?: Node): Node[];
17
+ export function children(node: Node): Node[];
18
+ export function prevElement(node: Node): Element | null;
19
+ export function nextElement(node: Node): Element | null;
20
+ export function createElement(tag: string, attrs?: Record<string, string>, childNodes?: Array<Node | string>): HTMLElement;
21
+ export function remove(node: Node): void;
22
+ export function unwrap(node: Node): void;
23
+ export function wrap(node: Node, wrapper: HTMLElement): HTMLElement;
24
+ export function insertAfter(newNode: Node, refNode: Node): void;
25
+ export function nodeValue(node: Node): string;
26
+ export function isEmpty(node: Node): boolean;
27
+ export function outerHtml(el: Element): string;
28
+ export function placeCaret(el: HTMLElement): void;
29
+ export function isInsideEditable(node: Node): boolean;
30
+ export function on(target: EventTarget, type: string, handler: EventListener, options?: AddEventListenerOptions): () => void;
31
+ export function trapFocus(container: HTMLElement, onEscape: () => void): () => void;
32
+ export function makeDraggable(handle: HTMLElement, box: HTMLElement): () => void;
@@ -0,0 +1,10 @@
1
+ export const env: {
2
+ isChrome: boolean;
3
+ isFF: boolean;
4
+ isSafari: boolean;
5
+ isEdge: boolean;
6
+ isMac: boolean;
7
+ isMobile: boolean;
8
+ isTouch: boolean;
9
+ modifierKey: string;
10
+ };
@@ -0,0 +1,11 @@
1
+ export function clamp(val: number, min: number, max: number): number;
2
+ export function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void;
3
+ export function throttle<T extends (...args: any[]) => any>(fn: T, limit: number): (...args: Parameters<T>) => void;
4
+ export function compose(...fns: Function[]): Function;
5
+ export function identity<T>(x: T): T;
6
+ export function isNil(val: unknown): val is null | undefined;
7
+ export function isString(val: unknown): val is string;
8
+ export function isFunction(val: unknown): val is Function;
9
+ export function mergeDeep<T extends object, U extends object>(target: T, source: U): T & U;
10
+ export function isPlainObject(val: unknown): val is Record<string, unknown>;
11
+ export function rect2bnd(rect: DOMRect | null | undefined): Pick<DOMRect, 'top' | 'left' | 'width' | 'height' | 'bottom' | 'right'> | null;
@@ -0,0 +1,3 @@
1
+ export const key: Readonly<Record<'BACKSPACE' | 'TAB' | 'ENTER' | 'ESCAPE' | 'SPACE' | 'PAGE_UP' | 'PAGE_DOWN' | 'END' | 'HOME' | 'LEFT' | 'UP' | 'RIGHT' | 'DOWN' | 'DELETE' | 'NUM0' | 'NUM1' | 'NUM2' | 'NUM3' | 'NUM4' | 'NUM5' | 'NUM6' | 'NUM7' | 'NUM8' | 'B' | 'E' | 'I' | 'J' | 'K' | 'L' | 'R' | 'S' | 'U' | 'V' | 'Y' | 'Z' | 'SLASH' | 'PERIOD', string>>;
2
+ export function isKey(event: KeyboardEvent, keyName: string): boolean;
3
+ export function isModifier(event: KeyboardEvent, keyName: string): boolean;
@@ -0,0 +1,10 @@
1
+ export function last<T>(arr: T[]): T | undefined;
2
+ export function first<T>(arr: T[]): T | undefined;
3
+ export function initial<T>(arr: T[], n?: number): T[];
4
+ export function tail<T>(arr: T[], n?: number): T[];
5
+ export function flatten<T>(arr: T[][]): T[];
6
+ export function unique<T>(arr: T[]): T[];
7
+ export function chunk<T>(arr: T[], n: number): T[][];
8
+ export function groupBy<T>(arr: T[], keyFn: (item: T) => string): Record<string, T[]>;
9
+ export function all<T>(arr: T[], predicate: (item: T) => boolean): boolean;
10
+ export function any<T>(arr: T[], predicate: (item: T) => boolean): boolean;
@@ -0,0 +1,22 @@
1
+ export class WrappedRange {
2
+ constructor(sc: Node, so: number, ec: Node, eo: number);
3
+ sc: Node;
4
+ so: number;
5
+ ec: Node;
6
+ eo: number;
7
+ isCollapsed(): boolean;
8
+ toNativeRange(): Range;
9
+ select(): void;
10
+ commonAncestor(): Element | null;
11
+ blockNode(editable: HTMLElement): Element | null;
12
+ toString(): string;
13
+ getClientRects(): DOMRect | null;
14
+ insertNode(node: Node): void;
15
+ }
16
+ export function fromNativeRange(range: Range): WrappedRange;
17
+ export function currentRange(editable?: HTMLElement): WrappedRange | null;
18
+ export function rangeFromElement(el: HTMLElement): WrappedRange;
19
+ export function collapsedRange(node: Node, offset?: number): WrappedRange;
20
+ export function isSelectionInside(el: HTMLElement): boolean;
21
+ export function withSavedRange(fn: () => void): void;
22
+ export function splitText(textNode: Text, offset: number): [Text, Text];
@@ -0,0 +1,9 @@
1
+ export interface SanitiseHTMLOptions {
2
+ allowIframes?: boolean;
3
+ }
4
+ export interface SanitiseURLOptions {
5
+ allowData?: boolean;
6
+ media?: boolean;
7
+ }
8
+ export function sanitiseHTML(html: string, options?: SanitiseHTMLOptions): string;
9
+ export function sanitiseUrl(url: string | null | undefined, options?: SanitiseURLOptions): string | null;
package/types/index.d.ts CHANGED
@@ -1,12 +1,21 @@
1
1
  /**
2
2
  * AutumnNote – TypeScript declarations
3
- * @version 1.13.0
3
+ * @version 1.15.0
4
4
  */
5
5
 
6
6
  // ---------------------------------------------------------------------------
7
7
  // Options
8
8
  // ---------------------------------------------------------------------------
9
9
 
10
+ export interface PasteErrorData {
11
+ /** Human-readable description of the rejected paste or drop. */
12
+ message: string;
13
+ /** Payload size in bytes, when the failure is caused by a configured limit. */
14
+ size?: number;
15
+ /** Maximum permitted payload size in bytes, when applicable. */
16
+ maxBytes?: number;
17
+ }
18
+
10
19
  export interface AsnOptions {
11
20
  /** Placeholder text when the editor is empty. */
12
21
  placeholder?: string;
@@ -102,6 +111,8 @@ export interface AsnOptions {
102
111
  tableHeaderRow?: boolean;
103
112
  /** Callback fired after every paste event. */
104
113
  onPaste?: (data: { text: string; html: string | null }) => void;
114
+ /** Callback fired when pasted or dropped content cannot be processed. */
115
+ onPasteError?: (error: PasteErrorData) => void;
105
116
  /** Additional color swatches shown at the top of the color picker. */
106
117
  colorSwatches?: string[];
107
118
  /** Custom focus ring colour — overrides the default blue. Accepts any valid CSS colour string, e.g. '#f97316'. */
@@ -337,13 +348,13 @@ export declare const locales: Record<string, AsnLocale>;
337
348
  export declare function resolveLocale(lang: string | Partial<AsnLocale> | null | undefined): AsnLocale;
338
349
 
339
350
  // Re-export core utility modules exposed by the package entry-point.
340
- export * from '../src/js/core/dom.js';
341
- export * from '../src/js/core/range.js';
342
- export * from '../src/js/core/func.js';
343
- export * from '../src/js/core/key.js';
344
- export * from '../src/js/core/lists.js';
345
- export * from '../src/js/core/env.js';
346
- export * from '../src/js/core/sanitise.js';
351
+ export * from './core/dom.js';
352
+ export * from './core/range.js';
353
+ export * from './core/func.js';
354
+ export * from './core/key.js';
355
+ export * from './core/lists.js';
356
+ export * from './core/env.js';
357
+ export * from './core/sanitise.js';
347
358
 
348
359
  // ---------------------------------------------------------------------------
349
360
  // Context — per-instance editor hub
package/dist/_headers DELETED
@@ -1,5 +0,0 @@
1
- /*.html
2
- Cache-Control: public, max-age=0, must-revalidate
3
-
4
- /assets/*
5
- Cache-Control: public, max-age=31536000, immutable
Binary file
Binary file
Binary file
package/dist/robots.txt DELETED
@@ -1,4 +0,0 @@
1
- User-agent: *
2
- Allow: /
3
-
4
- Sitemap: https://autumn.konexforge.com/sitemap.xml
package/dist/sitemap.xml DELETED
@@ -1,58 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
3
- xmlns:xhtml="http://www.w3.org/1999/xhtml">
4
- <url>
5
- <loc>https://autumn.konexforge.com/</loc>
6
- <lastmod>2026-07-12</lastmod>
7
- <changefreq>monthly</changefreq>
8
- <priority>1.0</priority>
9
- <xhtml:link rel="alternate" hreflang="en" href="https://autumn.konexforge.com/" />
10
- <xhtml:link rel="alternate" hreflang="vi" href="https://autumn.konexforge.com/vi/" />
11
- <xhtml:link rel="alternate" hreflang="x-default" href="https://autumn.konexforge.com/" />
12
- </url>
13
- <url>
14
- <loc>https://autumn.konexforge.com/docs.html</loc>
15
- <lastmod>2026-07-12</lastmod>
16
- <changefreq>monthly</changefreq>
17
- <priority>0.9</priority>
18
- <xhtml:link rel="alternate" hreflang="en" href="https://autumn.konexforge.com/docs.html" />
19
- <xhtml:link rel="alternate" hreflang="vi" href="https://autumn.konexforge.com/vi/docs.html" />
20
- <xhtml:link rel="alternate" hreflang="x-default" href="https://autumn.konexforge.com/docs.html" />
21
- </url>
22
- <url>
23
- <loc>https://autumn.konexforge.com/playground.html</loc>
24
- <lastmod>2026-07-12</lastmod>
25
- <changefreq>monthly</changefreq>
26
- <priority>0.7</priority>
27
- <xhtml:link rel="alternate" hreflang="en" href="https://autumn.konexforge.com/playground.html" />
28
- <xhtml:link rel="alternate" hreflang="vi" href="https://autumn.konexforge.com/vi/playground.html" />
29
- <xhtml:link rel="alternate" hreflang="x-default" href="https://autumn.konexforge.com/playground.html" />
30
- </url>
31
- <url>
32
- <loc>https://autumn.konexforge.com/vi/</loc>
33
- <lastmod>2026-07-12</lastmod>
34
- <changefreq>monthly</changefreq>
35
- <priority>0.9</priority>
36
- <xhtml:link rel="alternate" hreflang="en" href="https://autumn.konexforge.com/" />
37
- <xhtml:link rel="alternate" hreflang="vi" href="https://autumn.konexforge.com/vi/" />
38
- <xhtml:link rel="alternate" hreflang="x-default" href="https://autumn.konexforge.com/" />
39
- </url>
40
- <url>
41
- <loc>https://autumn.konexforge.com/vi/docs.html</loc>
42
- <lastmod>2026-07-12</lastmod>
43
- <changefreq>monthly</changefreq>
44
- <priority>0.8</priority>
45
- <xhtml:link rel="alternate" hreflang="en" href="https://autumn.konexforge.com/docs.html" />
46
- <xhtml:link rel="alternate" hreflang="vi" href="https://autumn.konexforge.com/vi/docs.html" />
47
- <xhtml:link rel="alternate" hreflang="x-default" href="https://autumn.konexforge.com/docs.html" />
48
- </url>
49
- <url>
50
- <loc>https://autumn.konexforge.com/vi/playground.html</loc>
51
- <lastmod>2026-07-12</lastmod>
52
- <changefreq>monthly</changefreq>
53
- <priority>0.6</priority>
54
- <xhtml:link rel="alternate" hreflang="en" href="https://autumn.konexforge.com/playground.html" />
55
- <xhtml:link rel="alternate" hreflang="vi" href="https://autumn.konexforge.com/vi/playground.html" />
56
- <xhtml:link rel="alternate" hreflang="x-default" href="https://autumn.konexforge.com/playground.html" />
57
- </url>
58
- </urlset>