@notectl/core 0.0.6 → 0.0.7
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 +246 -0
- package/package.json +24 -2
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# notectl
|
|
4
|
+
|
|
5
|
+
**A modern rich text editor, shipped as a single Web Component.**
|
|
6
|
+
|
|
7
|
+
Built on immutable state, a transaction-based architecture, and a plugin system that powers every feature — from bold text to full table editing.
|
|
8
|
+
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
[](https://developer.mozilla.org/en-US/docs/Web/API/Web_components)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](https://www.npmjs.com/package/@notectl/core)
|
|
13
|
+
|
|
14
|
+
<br />
|
|
15
|
+
|
|
16
|
+
<img src="docs-site/src/assets/screenshots/hero-editor-rich.png" alt="notectl editor with rich content" width="720" />
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<br />
|
|
21
|
+
|
|
22
|
+
## Why notectl?
|
|
23
|
+
|
|
24
|
+
Most editors bolt formatting on top of `contenteditable` and hope for the best. notectl takes a different approach: every keystroke produces an immutable transaction, every feature is a plugin, and the DOM is a projection of state — never the source of truth.
|
|
25
|
+
|
|
26
|
+
- **Web Component** — drop `<notectl-editor>` into any framework or vanilla HTML
|
|
27
|
+
- **Plugin architecture** — every feature (bold, tables, lists, ...) is a plugin; add only what you need
|
|
28
|
+
- **Immutable state** — predictable updates, time-travel undo/redo, zero mutation bugs
|
|
29
|
+
- **Transaction system** — atomic, invertible steps with middleware support
|
|
30
|
+
- **Zero framework lock-in** — works with React, Vue, Svelte, Angular, or plain JS
|
|
31
|
+
- **Tiny dependency footprint** — single runtime dependency (DOMPurify)
|
|
32
|
+
|
|
33
|
+
<br />
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @notectl/core
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Use
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import {
|
|
47
|
+
createEditor,
|
|
48
|
+
TextFormattingPlugin,
|
|
49
|
+
HeadingPlugin,
|
|
50
|
+
ListPlugin,
|
|
51
|
+
LinkPlugin,
|
|
52
|
+
TablePlugin,
|
|
53
|
+
ToolbarPlugin,
|
|
54
|
+
} from '@notectl/core';
|
|
55
|
+
|
|
56
|
+
const editor = await createEditor({
|
|
57
|
+
toolbar: [
|
|
58
|
+
[new TextFormattingPlugin({ bold: true, italic: true, underline: true })],
|
|
59
|
+
[new HeadingPlugin()],
|
|
60
|
+
[new ListPlugin()],
|
|
61
|
+
[new LinkPlugin(), new TablePlugin()],
|
|
62
|
+
],
|
|
63
|
+
placeholder: 'Start typing...',
|
|
64
|
+
autofocus: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
document.body.appendChild(editor);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
That's it. A full-featured editor in 15 lines.
|
|
71
|
+
|
|
72
|
+
<br />
|
|
73
|
+
|
|
74
|
+
## Plugins
|
|
75
|
+
|
|
76
|
+
Every capability is a plugin. Compose exactly the editor you need.
|
|
77
|
+
|
|
78
|
+
| Plugin | What it does |
|
|
79
|
+
|---|---|
|
|
80
|
+
| **TextFormattingPlugin** | Bold, italic, underline — individually toggleable |
|
|
81
|
+
| **StrikethroughPlugin** | ~~Strikethrough~~ text |
|
|
82
|
+
| **HeadingPlugin** | H1 – H6 headings |
|
|
83
|
+
| **BlockquotePlugin** | Block quotes |
|
|
84
|
+
| **ListPlugin** | Bullet and ordered lists |
|
|
85
|
+
| **LinkPlugin** | Hyperlink insertion and editing |
|
|
86
|
+
| **TablePlugin** | Full table support with row/column controls |
|
|
87
|
+
| **TextColorPlugin** | Text color picker |
|
|
88
|
+
| **TextAlignmentPlugin** | Left, center, right, justify |
|
|
89
|
+
| **FontPlugin** | Font family selection with custom font support |
|
|
90
|
+
| **FontSizePlugin** | Configurable font sizes |
|
|
91
|
+
| **HorizontalRulePlugin** | Horizontal dividers |
|
|
92
|
+
| **SuperSubPlugin** | Superscript and subscript |
|
|
93
|
+
| **HighlightPlugin** | Text highlighting / background color |
|
|
94
|
+
| **ToolbarPlugin** | Visual toolbar with grouped items |
|
|
95
|
+
|
|
96
|
+
### Tables
|
|
97
|
+
|
|
98
|
+
Full table editing — add/remove rows and columns, navigate with Tab, resize, and select.
|
|
99
|
+
|
|
100
|
+
<div align="center">
|
|
101
|
+
<img src="docs-site/src/assets/screenshots/editor-table-showcase.png" alt="Table editing in notectl" width="720" />
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<br />
|
|
105
|
+
|
|
106
|
+
## Content API
|
|
107
|
+
|
|
108
|
+
Read and write content in multiple formats:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// JSON (immutable Document)
|
|
112
|
+
const doc = editor.getJSON();
|
|
113
|
+
editor.setJSON(doc);
|
|
114
|
+
|
|
115
|
+
// HTML (sanitized via DOMPurify)
|
|
116
|
+
const html = editor.getHTML();
|
|
117
|
+
editor.setHTML('<p>Hello <strong>world</strong></p>');
|
|
118
|
+
|
|
119
|
+
// Plain text
|
|
120
|
+
const text = editor.getText();
|
|
121
|
+
|
|
122
|
+
// State
|
|
123
|
+
editor.isEmpty(); // true | false
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Command API
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
editor.commands.toggleBold();
|
|
130
|
+
editor.commands.toggleItalic();
|
|
131
|
+
editor.commands.toggleUnderline();
|
|
132
|
+
editor.commands.undo();
|
|
133
|
+
editor.commands.redo();
|
|
134
|
+
editor.commands.selectAll();
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Events
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
editor.on('stateChange', ({ oldState, newState, transaction }) => { /* ... */ });
|
|
141
|
+
editor.on('selectionChange', ({ selection }) => { /* ... */ });
|
|
142
|
+
editor.on('ready', () => { /* ... */ });
|
|
143
|
+
editor.on('focus', () => { /* ... */ });
|
|
144
|
+
editor.on('blur', () => { /* ... */ });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
<br />
|
|
148
|
+
|
|
149
|
+
## Custom Fonts
|
|
150
|
+
|
|
151
|
+
Bring your own fonts — notectl handles `@font-face` injection automatically.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { FontPlugin, STARTER_FONTS } from '@notectl/core';
|
|
155
|
+
|
|
156
|
+
const Inter = {
|
|
157
|
+
name: 'Inter',
|
|
158
|
+
family: "'Inter', sans-serif",
|
|
159
|
+
category: 'sans-serif',
|
|
160
|
+
fontFaces: [
|
|
161
|
+
{
|
|
162
|
+
src: "url('/fonts/Inter-Variable.ttf') format('truetype')",
|
|
163
|
+
weight: '100 900',
|
|
164
|
+
style: 'normal',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
new FontPlugin({ fonts: [...STARTER_FONTS, Inter] });
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
<br />
|
|
173
|
+
|
|
174
|
+
## Toolbar Configuration
|
|
175
|
+
|
|
176
|
+
Group plugins into toolbar sections for a clean UI:
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const editor = await createEditor({
|
|
180
|
+
toolbar: [
|
|
181
|
+
[new FontPlugin(), new FontSizePlugin()],
|
|
182
|
+
[new TextFormattingPlugin(), new StrikethroughPlugin(), new TextColorPlugin()],
|
|
183
|
+
[new HeadingPlugin(), new BlockquotePlugin()],
|
|
184
|
+
[new TextAlignmentPlugin()],
|
|
185
|
+
[new ListPlugin()],
|
|
186
|
+
[new LinkPlugin(), new TablePlugin(), new HorizontalRulePlugin()],
|
|
187
|
+
],
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Each inner array becomes a visually separated group in the toolbar.
|
|
192
|
+
|
|
193
|
+
<br />
|
|
194
|
+
|
|
195
|
+
## Examples
|
|
196
|
+
|
|
197
|
+
Check out the full working example in [`examples/vanillajs`](examples/vanillajs) — it demonstrates every plugin, custom font loading, toolbar grouping, and the complete content API.
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
git clone https://github.com/Samyssmile/notectl.git
|
|
201
|
+
cd notectl
|
|
202
|
+
pnpm install
|
|
203
|
+
pnpm dev
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
<br />
|
|
207
|
+
|
|
208
|
+
## Architecture
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
Input Event → InputHandler / KeyboardHandler
|
|
212
|
+
→ Transaction with atomic Steps
|
|
213
|
+
→ Middleware chain (priority-ordered)
|
|
214
|
+
→ EditorState.apply(tr) → new immutable EditorState
|
|
215
|
+
→ Reconciler patches DOM (block-level diffing)
|
|
216
|
+
→ Plugins notified via onStateChange()
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
| Layer | Responsibility |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `model/` | Immutable data — Document, BlockNode, TextNode, Mark, Selection |
|
|
222
|
+
| `state/` | EditorState, Transaction, StepApplication, History |
|
|
223
|
+
| `view/` | DOM rendering, Reconciler, SelectionSync |
|
|
224
|
+
| `input/` | Keyboard/input handling, paste, input rules |
|
|
225
|
+
| `commands/` | High-level operations (toggleMark, splitBlock, ...) |
|
|
226
|
+
| `plugins/` | All features — every capability is a plugin |
|
|
227
|
+
| `editor/` | `<notectl-editor>` Web Component public API |
|
|
228
|
+
|
|
229
|
+
<br />
|
|
230
|
+
|
|
231
|
+
## Development
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pnpm install # install dependencies
|
|
235
|
+
pnpm build # build all packages
|
|
236
|
+
pnpm test # run unit tests (vitest + happy-dom)
|
|
237
|
+
pnpm test:e2e # run e2e tests (playwright)
|
|
238
|
+
pnpm lint # lint (biome)
|
|
239
|
+
pnpm typecheck # type check
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
<br />
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
[MIT](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@notectl/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
|
+
"description": "A modern rich text editor shipped as a single Web Component. Immutable state, transaction-based architecture, and a plugin system that powers every feature.",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"type": "module",
|
|
5
7
|
"main": "./dist/notectl-core.js",
|
|
6
8
|
"module": "./dist/notectl-core.mjs",
|
|
@@ -12,7 +14,27 @@
|
|
|
12
14
|
"require": "./dist/notectl-core.js"
|
|
13
15
|
}
|
|
14
16
|
},
|
|
15
|
-
"files": ["dist"],
|
|
17
|
+
"files": ["dist", "README.md"],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"rich-text-editor",
|
|
20
|
+
"web-component",
|
|
21
|
+
"contenteditable",
|
|
22
|
+
"editor",
|
|
23
|
+
"wysiwyg",
|
|
24
|
+
"typescript",
|
|
25
|
+
"plugin-system",
|
|
26
|
+
"immutable-state",
|
|
27
|
+
"notectl"
|
|
28
|
+
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/Samyssmile/notectl.git",
|
|
32
|
+
"directory": "packages/core"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/Samyssmile/notectl",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/Samyssmile/notectl/issues"
|
|
37
|
+
},
|
|
16
38
|
"scripts": {
|
|
17
39
|
"build": "vite build",
|
|
18
40
|
"test": "vitest run",
|