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/README.md +20 -15
- package/dist/autumnnote.cjs +61 -0
- package/dist/autumnnote.css +2 -2579
- package/dist/autumnnote.es.js +4449 -9977
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +39 -17545
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +23 -14
- package/src/js/core/sanitise.js +47 -11
- package/src/js/index.js +1 -1
- package/src/js/module/Clipboard.js +5 -1
- package/src/js/module/Editor.js +10 -4
- package/src/js/module/ImageCropOverlay.js +5 -0
- package/src/js/module/TableTooltip.js +2 -1
- package/src/js/module/VideoDialog.js +3 -13
- package/src/js/settings.js +3 -0
- package/types/core/dom.d.ts +32 -0
- package/types/core/env.d.ts +10 -0
- package/types/core/func.d.ts +11 -0
- package/types/core/key.d.ts +3 -0
- package/types/core/lists.d.ts +10 -0
- package/types/core/range.d.ts +22 -0
- package/types/core/sanitise.d.ts +9 -0
- package/types/index.d.ts +19 -8
- package/dist/_headers +0 -5
- package/dist/image/banner.png +0 -0
- package/dist/image/favicon.ico +0 -0
- package/dist/image/og-banner.png +0 -0
- package/dist/robots.txt +0 -4
- package/dist/sitemap.xml +0 -58
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autumnnote",
|
|
3
|
-
"version": "1.
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
94
|
-
"@vitest/
|
|
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": "
|
|
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.
|
|
102
|
-
"vitest": "^4.1.
|
|
109
|
+
"vite": "^8.0.16",
|
|
110
|
+
"vitest": "^4.1.8"
|
|
103
111
|
},
|
|
112
|
+
"packageManager": "pnpm@11.1.3",
|
|
104
113
|
"engines": {
|
|
105
|
-
"node": ">=
|
|
114
|
+
"node": ">=20.19.0"
|
|
106
115
|
},
|
|
107
116
|
"browserslist": [
|
|
108
117
|
"last 2 versions",
|
package/src/js/core/sanitise.js
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
193
|
-
|
|
194
|
-
|
|
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
|
@@ -371,7 +371,11 @@ export class Clipboard {
|
|
|
371
371
|
execCommand('insertHTML', html);
|
|
372
372
|
this.context.invoke('editor.afterCommand');
|
|
373
373
|
};
|
|
374
|
-
reader.onerror = () =>
|
|
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
|
|
package/src/js/module/Editor.js
CHANGED
|
@@ -497,8 +497,7 @@ export class Editor {
|
|
|
497
497
|
|
|
498
498
|
undo() {
|
|
499
499
|
if (this._history) {
|
|
500
|
-
|
|
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
|
-
|
|
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
|
|
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');
|
package/src/js/settings.js
CHANGED
|
@@ -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,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.
|
|
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 '
|
|
341
|
-
export * from '
|
|
342
|
-
export * from '
|
|
343
|
-
export * from '
|
|
344
|
-
export * from '
|
|
345
|
-
export * from '
|
|
346
|
-
export * from '
|
|
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
package/dist/image/banner.png
DELETED
|
Binary file
|
package/dist/image/favicon.ico
DELETED
|
Binary file
|
package/dist/image/og-banner.png
DELETED
|
Binary file
|
package/dist/robots.txt
DELETED
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>
|