autumnnote 1.4.1 → 1.5.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 +96 -17
- package/dist/autumnnote.es.js +76 -8
- package/dist/autumnnote.es.js.map +1 -1
- package/dist/autumnnote.umd.js +68 -16
- package/dist/autumnnote.umd.js.map +1 -1
- package/package.json +8 -1
- package/src/js/core/markdown.js +5 -5
- package/src/js/editing/Style.js +9 -7
- package/src/js/index.js +1 -1
package/README.md
CHANGED
|
@@ -26,16 +26,17 @@ A modern WYSIWYG rich-text editor built with vanilla JavaScript (ES2022+) — no
|
|
|
26
26
|
|
|
27
27
|
1. [Features](#features)
|
|
28
28
|
2. [Installation](#installation)
|
|
29
|
-
3. [
|
|
30
|
-
4. [
|
|
31
|
-
5. [API](#api)
|
|
32
|
-
6. [
|
|
33
|
-
7. [
|
|
34
|
-
8. [
|
|
35
|
-
9. [
|
|
36
|
-
10. [
|
|
37
|
-
11. [
|
|
38
|
-
12. [
|
|
29
|
+
3. [Framework Wrappers](#framework-wrappers)
|
|
30
|
+
4. [Quick Start](#quick-start)
|
|
31
|
+
5. [Plugin API](#plugin-api)
|
|
32
|
+
6. [API](#api)
|
|
33
|
+
7. [Options](#options)
|
|
34
|
+
8. [Toolbar Customisation](#toolbar-customisation)
|
|
35
|
+
9. [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
36
|
+
10. [Mentions](#mentions)
|
|
37
|
+
11. [Project Structure](#project-structure)
|
|
38
|
+
12. [Comparison](#comparison)
|
|
39
|
+
13. [License](#license)
|
|
39
40
|
|
|
40
41
|
---
|
|
41
42
|
|
|
@@ -166,6 +167,66 @@ import 'autumnnote/dist/autumnnote.css';
|
|
|
166
167
|
|
|
167
168
|
---
|
|
168
169
|
|
|
170
|
+
## Framework Wrappers
|
|
171
|
+
|
|
172
|
+
Official React and Vue 3 wrappers are available as separate packages in this monorepo (managed with pnpm workspaces).
|
|
173
|
+
|
|
174
|
+
### React
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
npm install autumnnote autumnnote-react
|
|
178
|
+
import 'autumnnote/dist/autumnnote.css';
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```jsx
|
|
182
|
+
import { useRef } from 'react';
|
|
183
|
+
import AutumnNoteEditor from 'autumnnote-react';
|
|
184
|
+
|
|
185
|
+
function MyEditor() {
|
|
186
|
+
const editorRef = useRef(null);
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<AutumnNoteEditor
|
|
190
|
+
ref={editorRef}
|
|
191
|
+
options={{ placeholder: 'Start typing…', height: 300, bubbleToolbar: true }}
|
|
192
|
+
/>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Access the editor instance:
|
|
197
|
+
editorRef.current.getHTML();
|
|
198
|
+
editorRef.current.invoke('editor.setHTML', '<p>Hello!</p>');
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The `ref` is forwarded to the underlying `Context` instance via `useImperativeHandle`. Pass a `key` prop to force remount when options change.
|
|
202
|
+
|
|
203
|
+
### Vue 3
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm install autumnnote autumnnote-vue
|
|
207
|
+
import 'autumnnote/dist/autumnnote.css';
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```vue
|
|
211
|
+
<script setup>
|
|
212
|
+
import { ref } from 'vue';
|
|
213
|
+
import AutumnNoteEditor from 'autumnnote-vue';
|
|
214
|
+
|
|
215
|
+
const editorRef = ref(null);
|
|
216
|
+
</script>
|
|
217
|
+
|
|
218
|
+
<template>
|
|
219
|
+
<AutumnNoteEditor
|
|
220
|
+
ref="editorRef"
|
|
221
|
+
:options="{ placeholder: 'Start typing…', height: 300 }"
|
|
222
|
+
/>
|
|
223
|
+
</template>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Access the editor instance via `editorRef.value.editor.value` (the `editor` reactive ref exposed by `defineExpose`).
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
169
230
|
## Quick Start
|
|
170
231
|
|
|
171
232
|
### ES Module
|
|
@@ -641,16 +702,34 @@ src/
|
|
|
641
702
|
└── autumnnote.scss Main stylesheet
|
|
642
703
|
```
|
|
643
704
|
|
|
705
|
+
### Monorepo structure
|
|
706
|
+
|
|
707
|
+
This project uses **pnpm workspaces** to manage the core library alongside official framework wrappers:
|
|
708
|
+
|
|
709
|
+
```
|
|
710
|
+
autumn-note-ce/
|
|
711
|
+
├── pnpm-workspace.yaml # workspace root
|
|
712
|
+
├── src/ # core library source
|
|
713
|
+
├── packages/
|
|
714
|
+
│ ├── react/ # autumnnote-react
|
|
715
|
+
│ │ └── src/index.jsx
|
|
716
|
+
│ └── vue/ # autumnnote-vue
|
|
717
|
+
│ └── src/AutumnNote.vue
|
|
718
|
+
└── test/ # Vitest test suite
|
|
719
|
+
```
|
|
720
|
+
|
|
644
721
|
### Development commands
|
|
645
722
|
|
|
646
723
|
```bash
|
|
647
|
-
|
|
648
|
-
npm run dev
|
|
649
|
-
npm run build
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
npm run
|
|
653
|
-
npm run
|
|
724
|
+
pnpm install # install all workspace packages
|
|
725
|
+
npm run dev # start Vite dev server with HMR
|
|
726
|
+
npm run build # build core ES + UMD + CSS to dist/
|
|
727
|
+
pnpm --filter autumnnote-react build # build React wrapper
|
|
728
|
+
pnpm --filter autumnnote-vue build # build Vue wrapper
|
|
729
|
+
npm test # run Vitest test suite once
|
|
730
|
+
npm run test:watch # run tests in watch mode
|
|
731
|
+
npm run lint # ESLint
|
|
732
|
+
npm run typecheck # TypeScript type check (tsconfig.json)
|
|
654
733
|
```
|
|
655
734
|
|
|
656
735
|
Build output in `dist/`:
|
package/dist/autumnnote.es.js
CHANGED
|
@@ -919,9 +919,16 @@ function toggleChecklist() {
|
|
|
919
919
|
if (selectedLis.length > 0) {
|
|
920
920
|
let firstP = null;
|
|
921
921
|
selectedLis.forEach((li) => {
|
|
922
|
-
const text = Array.from(li.childNodes).filter((n) => !(n.nodeType === 1 && n.tagName === "INPUT")).map((n) => n.textContent).join("").replace(/\u00a0/g, " ").trim();
|
|
923
922
|
const p = document.createElement("p");
|
|
924
|
-
|
|
923
|
+
for (const child of li.childNodes) {
|
|
924
|
+
if (child.nodeType === 1 && child.tagName === "INPUT") continue;
|
|
925
|
+
p.appendChild(child.cloneNode(true));
|
|
926
|
+
}
|
|
927
|
+
p.innerHTML = p.innerHTML.replace(/\u200b/g, "");
|
|
928
|
+
if (!p.hasChildNodes() || !p.textContent.trim()) {
|
|
929
|
+
p.innerHTML = "";
|
|
930
|
+
p.appendChild(document.createTextNode("\xA0"));
|
|
931
|
+
}
|
|
925
932
|
ul.parentNode.insertBefore(p, ul);
|
|
926
933
|
if (!firstP) firstP = p;
|
|
927
934
|
ul.removeChild(li);
|
|
@@ -1583,6 +1590,7 @@ var en = {
|
|
|
1583
1590
|
chooseHighlightColor: "Choose highlight color",
|
|
1584
1591
|
customColor: "Custom color",
|
|
1585
1592
|
insertTableLabel: "Insert Table",
|
|
1593
|
+
/** Map of paragraph-style value → label (only values needing translation) */
|
|
1586
1594
|
paragraphItems: {
|
|
1587
1595
|
p: "Normal",
|
|
1588
1596
|
blockquote: "Quote",
|
|
@@ -1625,6 +1633,7 @@ var en = {
|
|
|
1625
1633
|
widthPlaceholder: "560",
|
|
1626
1634
|
insertBtn: "Insert",
|
|
1627
1635
|
cancelBtn: "Cancel",
|
|
1636
|
+
/** @param {string} type */
|
|
1628
1637
|
detected: (type) => `Detected: ${type}`,
|
|
1629
1638
|
unknownFormat: "Unknown format — will try direct video embed",
|
|
1630
1639
|
invalidUrl: "Invalid URL — please enter a valid video link."
|
|
@@ -1787,9 +1796,13 @@ var en = {
|
|
|
1787
1796
|
},
|
|
1788
1797
|
statusbar: {
|
|
1789
1798
|
resizeHandle: "Resize editor",
|
|
1799
|
+
/** @param {number} n */
|
|
1790
1800
|
words: (n) => `Words: ${n}`,
|
|
1801
|
+
/** @param {number} n @param {number} max */
|
|
1791
1802
|
wordsLimit: (n, max) => `Words: ${n}/${max}`,
|
|
1803
|
+
/** @param {number} n */
|
|
1792
1804
|
chars: (n) => `Chars: ${n}`,
|
|
1805
|
+
/** @param {number} n @param {number} max */
|
|
1793
1806
|
charsLimit: (n, max) => `Chars: ${n}/${max}`
|
|
1794
1807
|
},
|
|
1795
1808
|
tooltips: {
|
|
@@ -1862,7 +1875,9 @@ var en = {
|
|
|
1862
1875
|
}
|
|
1863
1876
|
},
|
|
1864
1877
|
errors: {
|
|
1878
|
+
/** @param {string} type */
|
|
1865
1879
|
imageFormat: (type) => `Format "${type}" is not supported for display in web browsers. Please convert to JPEG, PNG, or WebP first.`,
|
|
1880
|
+
/** @param {number} maxSize */
|
|
1866
1881
|
imageSize: (maxSize) => `Image file is too large. Maximum allowed size is ${maxSize} MB.`
|
|
1867
1882
|
}
|
|
1868
1883
|
};
|
|
@@ -5255,13 +5270,13 @@ function _parseTableRow(row) {
|
|
|
5255
5270
|
function _inline(text) {
|
|
5256
5271
|
text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => `<img src="${_escAttr(src)}" alt="${_escAttr(alt)}" class="an-image">`);
|
|
5257
5272
|
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, label, href) => `<a href="${_escAttr(href)}">${_esc(label)}</a>`);
|
|
5258
|
-
text = text.replace(/\*{3}(
|
|
5259
|
-
text = text.replace(/_{3}(
|
|
5260
|
-
text = text.replace(/\*{2}(
|
|
5261
|
-
text = text.replace(/_{2}(
|
|
5273
|
+
text = text.replace(/\*{3}([^*\n]+?)\*{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
|
|
5274
|
+
text = text.replace(/_{3}([^_\n]+?)_{3}/g, (_, c) => `<strong><em>${_esc(c)}</em></strong>`);
|
|
5275
|
+
text = text.replace(/\*{2}([^*\n]+?)\*{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
|
|
5276
|
+
text = text.replace(/_{2}([^_\n]+?)_{2}/g, (_, c) => `<strong>${_esc(c)}</strong>`);
|
|
5262
5277
|
text = text.replace(/\*([^*\n]+?)\*/g, (_, c) => `<em>${_esc(c)}</em>`);
|
|
5263
5278
|
text = text.replace(/_([^_\n]+?)_/g, (_, c) => `<em>${_esc(c)}</em>`);
|
|
5264
|
-
text = text.replace(/~~(
|
|
5279
|
+
text = text.replace(/~~([^\n]+?)~~/g, (_, c) => `<del>${_esc(c)}</del>`);
|
|
5265
5280
|
text = text.replace(/`([^`]+)`/g, (_, c) => `<code>${_esc(c)}</code>`);
|
|
5266
5281
|
return text;
|
|
5267
5282
|
}
|
|
@@ -16352,13 +16367,21 @@ function any(arr, predicate) {
|
|
|
16352
16367
|
*/
|
|
16353
16368
|
var userAgent = navigator.userAgent;
|
|
16354
16369
|
var env = {
|
|
16370
|
+
/** True if browser is Chrome */
|
|
16355
16371
|
isChrome: /Chrome\//.test(userAgent),
|
|
16372
|
+
/** True if browser is Firefox */
|
|
16356
16373
|
isFF: /Firefox\//.test(userAgent),
|
|
16374
|
+
/** True if browser is Safari (not Chrome) */
|
|
16357
16375
|
isSafari: /^((?!chrome|android).)*safari/i.test(userAgent),
|
|
16376
|
+
/** True if browser is Edge (Chromium) */
|
|
16358
16377
|
isEdge: /Edg\//.test(userAgent),
|
|
16378
|
+
/** True if running on macOS */
|
|
16359
16379
|
isMac: /Macintosh/.test(userAgent),
|
|
16380
|
+
/** True if running on mobile */
|
|
16360
16381
|
isMobile: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent),
|
|
16382
|
+
/** True if touch is supported */
|
|
16361
16383
|
isTouch: "ontouchstart" in window || navigator.maxTouchPoints > 0,
|
|
16384
|
+
/** Modifier key name depending on platform */
|
|
16362
16385
|
modifierKey: /Macintosh/.test(userAgent) ? "metaKey" : "ctrlKey"
|
|
16363
16386
|
};
|
|
16364
16387
|
//#endregion
|
|
@@ -16367,6 +16390,13 @@ var _originalDefaults = { ...defaultOptions };
|
|
|
16367
16390
|
/** @type {WeakMap<Element, Context>} */
|
|
16368
16391
|
var instances = /* @__PURE__ */ new WeakMap();
|
|
16369
16392
|
var AutumnNote = {
|
|
16393
|
+
/**
|
|
16394
|
+
* Creates (or returns existing) editor instance on one or more elements.
|
|
16395
|
+
*
|
|
16396
|
+
* @param {string|Element|NodeList|Element[]} selector
|
|
16397
|
+
* @param {import('./settings.js').AsnOptions} [options]
|
|
16398
|
+
* @returns {Context|Context[]} single Context or array of Contexts
|
|
16399
|
+
*/
|
|
16370
16400
|
create(selector, options = {}) {
|
|
16371
16401
|
const ctxs = resolveElements(selector).map((el) => {
|
|
16372
16402
|
if (instances.has(el)) return instances.get(el);
|
|
@@ -16377,6 +16407,10 @@ var AutumnNote = {
|
|
|
16377
16407
|
});
|
|
16378
16408
|
return ctxs.length === 1 ? ctxs[0] : ctxs;
|
|
16379
16409
|
},
|
|
16410
|
+
/**
|
|
16411
|
+
* Destroys the editor(s) on the given selector.
|
|
16412
|
+
* @param {string|Element|NodeList|Element[]} selector
|
|
16413
|
+
*/
|
|
16380
16414
|
destroy(selector) {
|
|
16381
16415
|
resolveElements(selector).forEach((el) => {
|
|
16382
16416
|
const ctx = instances.get(el);
|
|
@@ -16386,23 +16420,45 @@ var AutumnNote = {
|
|
|
16386
16420
|
}
|
|
16387
16421
|
});
|
|
16388
16422
|
},
|
|
16423
|
+
/**
|
|
16424
|
+
* Returns the Context instance for a given element (or null).
|
|
16425
|
+
* @param {string|Element} selector
|
|
16426
|
+
* @returns {Context|null}
|
|
16427
|
+
*/
|
|
16389
16428
|
getInstance(selector) {
|
|
16390
16429
|
const el = typeof selector === "string" ? document.querySelector(selector) : selector;
|
|
16391
16430
|
return el ? instances.get(el) || null : null;
|
|
16392
16431
|
},
|
|
16432
|
+
/** Returns a shallow copy of the default options (read-only snapshot). */
|
|
16393
16433
|
get defaults() {
|
|
16394
16434
|
return { ...defaultOptions };
|
|
16395
16435
|
},
|
|
16436
|
+
/** Merges properties into the global defaults, applied to all future instances. */
|
|
16396
16437
|
setDefaults(overrides) {
|
|
16397
16438
|
Object.assign(defaultOptions, overrides);
|
|
16398
16439
|
},
|
|
16440
|
+
/** Restores global defaults to their original factory values. */
|
|
16399
16441
|
resetDefaults() {
|
|
16400
16442
|
Object.keys(defaultOptions).forEach((k) => delete defaultOptions[k]);
|
|
16401
16443
|
Object.assign(defaultOptions, _originalDefaults);
|
|
16402
16444
|
},
|
|
16445
|
+
/**
|
|
16446
|
+
* Registers a custom module to be included in every new editor instance.
|
|
16447
|
+
* @param {string} name - unique module key used for ctx.invoke() calls
|
|
16448
|
+
* @param {Function} ModuleClass - class with initialize() and optional destroy()
|
|
16449
|
+
*/
|
|
16403
16450
|
registerModule(name, ModuleClass) {
|
|
16404
16451
|
_customModules.set(name, ModuleClass);
|
|
16405
16452
|
},
|
|
16453
|
+
/**
|
|
16454
|
+
* Installs a plugin globally — applied to every future editor instance.
|
|
16455
|
+
* Plugin `buttons` are registered to the global button registry immediately
|
|
16456
|
+
* so they are available when Toolbar initialises inside create().
|
|
16457
|
+
* Plugin `install()` is called after all built-in modules have initialised.
|
|
16458
|
+
* @param {object} plugin - { name, version?, buttons?, install?, uninstall? }
|
|
16459
|
+
* @param {object} [options] - Forwarded to plugin.install(context, options)
|
|
16460
|
+
* @returns {typeof AutumnNote}
|
|
16461
|
+
*/
|
|
16406
16462
|
use(plugin, options = {}) {
|
|
16407
16463
|
if (!plugin || typeof plugin.name !== "string") throw new TypeError("[AutumnNote] AutumnNote.use: plugin must have a string `name` property.");
|
|
16408
16464
|
if (_globalPlugins.has(plugin.name)) {
|
|
@@ -16416,14 +16472,26 @@ var AutumnNote = {
|
|
|
16416
16472
|
});
|
|
16417
16473
|
return this;
|
|
16418
16474
|
},
|
|
16475
|
+
/**
|
|
16476
|
+
* Returns true if a plugin with the given name has been registered globally.
|
|
16477
|
+
* @param {string} name
|
|
16478
|
+
* @returns {boolean}
|
|
16479
|
+
*/
|
|
16419
16480
|
hasPlugin(name) {
|
|
16420
16481
|
return _globalPlugins.has(name);
|
|
16421
16482
|
},
|
|
16483
|
+
/**
|
|
16484
|
+
* Registers a single button definition in the global button registry.
|
|
16485
|
+
* After create(), call ctx.invoke('toolbar.rebuild') to render new buttons.
|
|
16486
|
+
* @param {object} btnDef - ButtonDef-compatible object with a `name` string
|
|
16487
|
+
* @returns {typeof AutumnNote}
|
|
16488
|
+
*/
|
|
16422
16489
|
registerButton(btnDef) {
|
|
16423
16490
|
registerButton(btnDef);
|
|
16424
16491
|
return this;
|
|
16425
16492
|
},
|
|
16426
|
-
version
|
|
16493
|
+
/** Library version */
|
|
16494
|
+
version: "1.5.0"
|
|
16427
16495
|
};
|
|
16428
16496
|
/**
|
|
16429
16497
|
* @param {string|Element|NodeList|Element[]} selector
|