open-edit 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Christian Nagel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,595 @@
1
+ # open-edit — Lightweight JavaScript WYSIWYG Editor
2
+
3
+ [![npm version](https://img.shields.io/npm/v/open-edit.svg)](https://www.npmjs.com/package/open-edit)
4
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/open-edit)](https://bundlephobia.com/package/open-edit)
5
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](#)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7
+ [![Build](https://github.com/cnagel08/open-edit/actions/workflows/ci.yml/badge.svg)](https://github.com/cnagel08/open-edit/actions/workflows/ci.yml)
8
+
9
+ **A lightweight, open source rich text editor for the web — zero dependencies, ~31 kB gzipped.**
10
+
11
+ open-edit is a modern JavaScript WYSIWYG editor built on the `contenteditable` API. It works as a drop-in for any web project — plain HTML, React, Vue, or Svelte — and produces clean, semantic HTML5 output. No registration, no paid plans, MIT licensed.
12
+
13
+ [**Live Demo →**](https://cnagel08.github.io/open-edit/)
14
+
15
+ ### Key Highlights
16
+
17
+ - **~31 kB** gzipped — fraction of TinyMCE or CKEditor
18
+ - **Zero runtime dependencies** — no bloat, no supply chain risk
19
+ - **Open source, MIT licensed** — free for personal and commercial use
20
+ - **Plugin system** — highlight, emoji, callout blocks, slash commands, AI assistant
21
+ - **Framework-agnostic** — React, Vue, Svelte, vanilla JS
22
+ - **Light / Dark / Auto theme** via CSS custom properties
23
+ - **Full TypeScript support**
24
+
25
+ <img src=".github/assets/screenshot.png" alt="open-edit lightweight javascript wysiwyg editor" width="900" />
26
+
27
+ ---
28
+
29
+ ## Why open-edit?
30
+
31
+ TinyMCE and CKEditor are powerful — but they ship hundreds of kilobytes, require registration or licensing, and pull in external dependencies. open-edit is different:
32
+
33
+ | | open-edit | TinyMCE | CKEditor 5 |
34
+ |---|---|---|---|
35
+ | Runtime dependencies | **0** | ~0 (self-hosted) | dozens |
36
+ | License | **MIT** | MIT / Commercial | GPL / Commercial |
37
+ | Self-hosted | **always** | yes | yes |
38
+ | Plugin system | **yes** | yes | yes |
39
+
40
+
41
+ ---
42
+
43
+ ## Quick Start
44
+
45
+ **Via CDN**
46
+
47
+ ```html
48
+ <div id="editor"></div>
49
+ <script src="https://unpkg.com/open-edit/dist/open-edit.umd.js"></script>
50
+ <script>
51
+ const editor = OpenEdit.create('#editor', {
52
+ content: '<p>Hello <strong>world</strong>!</p>',
53
+ placeholder: 'Start typing…',
54
+ theme: 'auto',
55
+ onChange: (html) => console.log(html),
56
+ });
57
+ </script>
58
+ ```
59
+
60
+ **Via npm**
61
+
62
+ ```bash
63
+ npm install open-edit
64
+ ```
65
+
66
+ ```ts
67
+ import { OpenEdit } from 'open-edit';
68
+
69
+ const editor = OpenEdit.create('#editor', {
70
+ placeholder: 'Start typing…',
71
+ onChange: (html) => console.log(html),
72
+ });
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Features
78
+
79
+ **Core**
80
+
81
+ | Feature | Details |
82
+ |---|---|
83
+ | Text formatting | Bold, Italic, Underline, Strikethrough, Inline Code |
84
+ | Block elements | Paragraphs, Headings H1–H6, Lists, Blockquote, Code block, HR |
85
+ | Media | Images with drag-to-resize, upload hook |
86
+ | Editing | Text alignment, Links, Undo/Redo (50 steps) |
87
+ | I/O | HTML & Markdown import/export, smart clipboard paste |
88
+ | UI | Configurable toolbar, floating bubble toolbar, HTML source view, status bar |
89
+ | Theming | Light / Dark / Auto via CSS custom properties |
90
+
91
+ **Plugins** (all optional)
92
+
93
+ | Plugin | Description |
94
+ |---|---|
95
+ | `highlight` | Syntax highlighting for code blocks via highlight.js |
96
+ | `emoji` | Emoji picker in the toolbar |
97
+ | `templateTags` | Highlight and manage `{{variable}}` placeholders |
98
+ | `callout` | Info / Success / Warning / Danger callout blocks |
99
+ | `slashCommands` | Notion-style `/` command menu — 15+ block types |
100
+
101
+ ---
102
+
103
+ ## API Reference
104
+
105
+ ### `OpenEdit.create(element, options?)`
106
+
107
+ Creates and mounts a new editor instance.
108
+
109
+ ```ts
110
+ const editor = OpenEdit.create('#my-editor', options);
111
+ ```
112
+
113
+ **Parameters:**
114
+
115
+ | Parameter | Type | Description |
116
+ |-----------|------|-------------|
117
+ | `element` | `string \| HTMLElement` | CSS selector or DOM element |
118
+ | `options` | `EditorOptions` | Optional configuration |
119
+
120
+ **`EditorOptions`:**
121
+
122
+ | Option | Type | Default | Description |
123
+ |--------|------|---------|-------------|
124
+ | `content` | `string` | `''` | Initial HTML content |
125
+ | `placeholder` | `string` | `''` | Placeholder text when empty |
126
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Color theme |
127
+ | `readOnly` | `boolean` | `false` | Disable editing |
128
+ | `toolbar` | `ToolbarItemConfig[]` | full toolbar | Full custom toolbar (takes precedence over `toolbarItems`) |
129
+ | `toolbarItems` | `string[]` | all items | Filter default toolbar by item ID — see [Toolbar Items](#toolbar-items) |
130
+ | `statusBar` | `boolean \| StatusBarOptions` | `true` | Show/hide the status bar or individual parts |
131
+ | `onChange` | `(html: string) => void` | — | Called on every content change |
132
+ | `onImageUpload` | `(file: File) => Promise<string>` | — | Handle image uploads |
133
+
134
+ #### Toolbar Items
135
+
136
+ Available IDs for `toolbarItems`:
137
+
138
+ | ID | Element |
139
+ |----|---------|
140
+ | `undo`, `redo` | Undo / Redo buttons |
141
+ | `blockType` | Block format dropdown (Paragraph, H1–H4, …) |
142
+ | `bold`, `italic`, `underline`, `code` | Inline formatting |
143
+ | `alignLeft`, `alignCenter`, `alignRight`, `alignJustify` | Text alignment |
144
+ | `bulletList`, `orderedList` | Lists |
145
+ | `link`, `image`, `blockquote`, `hr` | Insert elements |
146
+ | `callout` | Insert callout button (Info variant) |
147
+ | `htmlToggle` | HTML source view button |
148
+
149
+ ```ts
150
+ // Minimal editor — only essential formatting
151
+ const editor = OpenEdit.create('#editor', {
152
+ toolbarItems: ['bold', 'italic', 'underline', 'link'],
153
+ });
154
+
155
+ // Rich text editor without alignment and HTML toggle
156
+ const editor = OpenEdit.create('#editor', {
157
+ toolbarItems: ['undo', 'redo', 'blockType', 'bold', 'italic', 'underline',
158
+ 'bulletList', 'orderedList', 'link', 'image'],
159
+ });
160
+ ```
161
+
162
+ #### Status Bar Options
163
+
164
+ ```ts
165
+ // Hide the status bar entirely
166
+ OpenEdit.create('#editor', { statusBar: false });
167
+
168
+ // Show only word count, no HTML toggle
169
+ OpenEdit.create('#editor', {
170
+ statusBar: { wordCount: true, charCount: false, elementPath: false, htmlToggle: false },
171
+ });
172
+ ```
173
+
174
+ | Option | Default | Description |
175
+ |--------|---------|-------------|
176
+ | `wordCount` | `true` | Word count |
177
+ | `charCount` | `true` | Character count |
178
+ | `elementPath` | `true` | Element path (e.g. `p › strong`) |
179
+ | `htmlToggle` | `true` | HTML source toggle button |
180
+
181
+ ---
182
+
183
+ ### Instance Methods
184
+
185
+ ```ts
186
+ // Content
187
+ editor.getHTML() // → string (clean HTML)
188
+ editor.setHTML(html) // set content from HTML
189
+ editor.getMarkdown() // → string (Markdown)
190
+ editor.setMarkdown(md) // set content from Markdown
191
+ editor.getDocument() // → EditorDocument (internal model)
192
+
193
+ // Editor state
194
+ editor.isEmpty() // → boolean
195
+ editor.isFocused() // → boolean
196
+ editor.isMarkActive(type) // → boolean ('bold' | 'italic' | …)
197
+ editor.getActiveBlockType()// → string ('paragraph' | 'heading' | …)
198
+ editor.getSelection() // → ModelSelection | null
199
+
200
+ // Focus
201
+ editor.focus()
202
+ editor.blur()
203
+
204
+ // Events
205
+ editor.on('change', (doc) => { })
206
+ editor.on('selectionchange', (sel) => { })
207
+ editor.on('focus', () => { })
208
+ editor.on('blur', () => { })
209
+ editor.off('change', listener)
210
+
211
+ // Commands (chainable)
212
+ editor.chain()
213
+ .toggleMark('bold')
214
+ .setBlock('heading', { level: 2 })
215
+ .setBlock('callout', { variant: 'warning' }) // insert/convert to callout
216
+ .setAlign('center')
217
+ .insertImage('https://example.com/img.png', 'alt text')
218
+ .insertHr()
219
+ .toggleList('bullet_list')
220
+ .undo()
221
+ .redo()
222
+ .run()
223
+
224
+ // Plugins
225
+ editor.use(myPlugin)
226
+
227
+ // Cleanup
228
+ editor.destroy()
229
+ ```
230
+
231
+ ---
232
+
233
+ ### Image Upload
234
+
235
+ ```ts
236
+ const editor = OpenEdit.create('#editor', {
237
+ onImageUpload: async (file) => {
238
+ const formData = new FormData();
239
+ formData.append('file', file);
240
+ const res = await fetch('/api/upload', { method: 'POST', body: formData });
241
+ const { url } = await res.json();
242
+ return url; // must return the public URL string
243
+ },
244
+ });
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Plugins
250
+
251
+ ### Code Syntax Highlighting
252
+
253
+ Requires [highlight.js](https://highlightjs.org/) to be loaded.
254
+
255
+ ```ts
256
+ import { OpenEdit } from 'open-edit';
257
+
258
+ const editor = OpenEdit.create('#editor');
259
+ editor.use(OpenEdit.plugins.highlight());
260
+ ```
261
+
262
+ ### Emoji Picker
263
+
264
+ ```ts
265
+ editor.use(OpenEdit.plugins.emoji());
266
+ ```
267
+
268
+ ### Template Variables
269
+
270
+ Highlight `{{variable}}` patterns in the editor content.
271
+
272
+ ```ts
273
+ editor.use(OpenEdit.plugins.templateTags({
274
+ variables: ['name', 'email', 'company'],
275
+ }));
276
+ ```
277
+
278
+ ### Callout Blocks
279
+
280
+ Adds styled callout/notice blocks in four variants: **Info**, **Success**, **Warning**, **Danger**.
281
+
282
+ ```ts
283
+ editor.use(OpenEdit.plugins.callout());
284
+ ```
285
+
286
+ **Insert via toolbar:** Use the block-type dropdown or the ⓘ toolbar button (inserts Info callout).
287
+
288
+ **Insert via slash commands:** With the `slashCommands` plugin, type `/callout`, `/success`, `/warning`, or `/danger` to get a live-filtered menu. Without the slash commands plugin, the callout plugin also recognises these exact strings typed on a blank line followed by `Enter` (legacy behaviour).
289
+
290
+ | Slash query | Variant |
291
+ |---|---|
292
+ | `/callout`, `/info` | Info |
293
+ | `/success` | Success |
294
+ | `/warning` | Warning |
295
+ | `/danger` | Danger |
296
+
297
+ **Keyboard shortcut:** `Ctrl+Shift+I` inserts an Info callout.
298
+
299
+ **Change variant:** Click inside a callout and select a different variant from the block-type dropdown.
300
+
301
+ **Via the chain API:**
302
+
303
+ ```ts
304
+ // Insert / convert current block to a callout
305
+ editor.chain().setBlock('callout', { variant: 'warning' }).run();
306
+
307
+ // Supported variants: 'info' | 'success' | 'warning' | 'danger'
308
+ ```
309
+
310
+ **HTML output:**
311
+
312
+ ```html
313
+ <div class="oe-callout oe-callout-warning" data-callout-variant="warning">
314
+ Watch out for breaking changes!
315
+ </div>
316
+ ```
317
+
318
+ **Adding a new variant** requires changes in three places:
319
+ 1. `CalloutVariant` type in `src/core/types.ts`
320
+ 2. CSS block in `src/view/styles.ts`
321
+ 3. Locale strings in `src/locales/types.ts`, `en.ts`, `de.ts` and a new option in `src/view/toolbar.ts`
322
+
323
+ ---
324
+
325
+ ### Slash Commands
326
+
327
+ A Notion-style `/` command menu that lets users quickly insert any block type by typing a slash at the start of a line.
328
+
329
+ ```ts
330
+ editor.use(OpenEdit.plugins.slashCommands());
331
+ ```
332
+
333
+ **How it works:** Type `/` at the start of any block. A floating dropdown appears, filtered in real-time as you continue typing. Navigate with `↑`/`↓`, confirm with `Enter` or `Tab`, dismiss with `Escape`.
334
+
335
+ **Built-in commands (15+):**
336
+
337
+ | Query examples | Inserts |
338
+ |---|---|
339
+ | `/p`, `/paragraph` | Paragraph |
340
+ | `/h1` – `/h6`, `/heading1` | Heading 1–6 |
341
+ | `/quote`, `/bq` | Blockquote |
342
+ | `/code`, `/codeblock` | Code Block |
343
+ | `/bullet`, `/ul` | Bullet List |
344
+ | `/numbered`, `/ol` | Numbered List |
345
+ | `/hr`, `/divider`, `/---` | Horizontal Rule |
346
+ | `/callout`, `/info` | Callout: Info |
347
+ | `/success` | Callout: Success |
348
+ | `/warning` | Callout: Warning |
349
+ | `/danger`, `/error` | Callout: Danger |
350
+
351
+ **Adding extra commands:**
352
+
353
+ ```ts
354
+ editor.use(OpenEdit.plugins.slashCommands({
355
+ extraCommands: [
356
+ {
357
+ id: 'my-block',
358
+ title: 'My Custom Block',
359
+ description: 'Inserts something special',
360
+ icon: '✦', // SVG string or text
361
+ keywords: ['my', 'custom', 'special'],
362
+ execute: (editor) => editor.chain().setBlock('paragraph').run(),
363
+ },
364
+ ],
365
+ }));
366
+ ```
367
+
368
+ **Replacing the command list entirely:**
369
+
370
+ ```ts
371
+ editor.use(OpenEdit.plugins.slashCommands({ commands: myCommands }));
372
+ ```
373
+
374
+ The `SlashCommand` and `SlashCommandsOptions` types are exported for TypeScript consumers.
375
+
376
+ ---
377
+
378
+ ### Writing a Custom Plugin
379
+
380
+ ```ts
381
+ import type { EditorPlugin } from 'open-edit';
382
+
383
+ const wordCountPlugin: EditorPlugin = {
384
+ name: 'word-count',
385
+ onInit(editor) {
386
+ editor.on('change', () => {
387
+ const text = editor.getHTML().replace(/<[^>]+>/g, ' ');
388
+ const words = text.trim().split(/\s+/).filter(Boolean).length;
389
+ console.log(`Word count: ${words}`);
390
+ });
391
+ },
392
+ onDestroy(editor) {
393
+ editor.off('change', () => {});
394
+ },
395
+ };
396
+
397
+ editor.use(wordCountPlugin);
398
+ ```
399
+
400
+ ---
401
+
402
+ ## Markdown Support
403
+
404
+ ```ts
405
+ // Export as Markdown
406
+ const md = editor.getMarkdown();
407
+
408
+ // Import from Markdown
409
+ editor.setMarkdown('# Hello\n\nThis is **bold** text.');
410
+
411
+ // Utility functions
412
+ import { serializeToMarkdown, deserializeMarkdown } from 'open-edit';
413
+ ```
414
+
415
+ ---
416
+
417
+ ## Internationalization (i18n)
418
+
419
+ OpenEdit ships with **English** and **German**. The UI language is detected automatically from the browser — no configuration needed.
420
+
421
+ ### Auto-detection (default)
422
+
423
+ OpenEdit reads `navigator.language` and picks the matching built-in locale automatically. If the browser language is not supported yet, it falls back to English.
424
+
425
+ ```ts
426
+ // Browser set to German → German UI automatically
427
+ // Browser set to French → English fallback
428
+ const editor = OpenEdit.create('#editor');
429
+ ```
430
+
431
+ ### Explicit locale
432
+
433
+ Override the auto-detection by passing a locale object:
434
+
435
+ ```ts
436
+ import { OpenEdit, de } from 'open-edit';
437
+
438
+ const editor = OpenEdit.create('#editor', {
439
+ locale: de, // always German, regardless of browser language
440
+ });
441
+ ```
442
+
443
+ Via CDN / `test.html` (UMD build — all locales are included):
444
+ ```js
445
+ const editor = OpenEdit.create('#editor', {
446
+ locale: OpenEdit.locales.de,
447
+ });
448
+ ```
449
+
450
+ ### Partial override
451
+
452
+ Override only individual strings, keep everything else as-is:
453
+
454
+ ```ts
455
+ const editor = OpenEdit.create('#editor', {
456
+ locale: {
457
+ statusBar: { words: 'Mots', characters: 'Caractères', htmlSource: 'HTML' },
458
+ },
459
+ });
460
+ ```
461
+
462
+ ### Available locales
463
+
464
+ | Import | Language | Auto-detected for |
465
+ |--------|----------|-------------------|
466
+ | `en` | English | default / fallback |
467
+ | `de` | German | `de`, `de-AT`, `de-CH`, … |
468
+
469
+ ### Adding a new language
470
+
471
+ 1. Create `src/locales/fr.ts` implementing `EditorLocale`
472
+ 2. Add it to `BUILT_IN_LOCALES` in `src/editor.ts` for auto-detection
473
+ 3. Export it from `src/index.ts`
474
+
475
+ TypeScript enforces completeness — a compile error is thrown for any missing key, so incomplete translations cannot be published.
476
+
477
+ ```ts
478
+ import type { EditorLocale } from 'open-edit';
479
+
480
+ export const fr: EditorLocale = {
481
+ toolbar: {
482
+ undo: 'Annuler (Ctrl+Z)',
483
+ // ... all keys required — TypeScript will tell you which ones are missing
484
+ },
485
+ // ...
486
+ };
487
+ ```
488
+
489
+ ---
490
+
491
+ ## Theming
492
+
493
+ OpenEdit uses CSS custom properties. Override them to match your brand:
494
+
495
+ ```css
496
+ :root {
497
+ --oe-primary: #2563eb;
498
+ --oe-bg: #ffffff;
499
+ --oe-bg-toolbar: #f8fafc;
500
+ --oe-border: #e2e8f0;
501
+ --oe-text: #1e293b;
502
+ --oe-radius: 8px;
503
+ --oe-font: system-ui, sans-serif;
504
+ }
505
+ ```
506
+
507
+ For dark mode, set `theme: 'dark'` or use `theme: 'auto'` to follow the OS preference.
508
+
509
+ ---
510
+
511
+ ## Framework Integration
512
+
513
+ ### React
514
+
515
+ ```tsx
516
+ import { useEffect, useRef } from 'react';
517
+ import { OpenEdit, EditorInterface } from 'open-edit';
518
+
519
+ export function Editor({ onChange }: { onChange: (html: string) => void }) {
520
+ const ref = useRef<HTMLDivElement>(null);
521
+ const editorRef = useRef<EditorInterface | null>(null);
522
+
523
+ useEffect(() => {
524
+ if (!ref.current) return;
525
+ editorRef.current = OpenEdit.create(ref.current, { onChange });
526
+ return () => editorRef.current?.destroy();
527
+ }, []);
528
+
529
+ return <div ref={ref} />;
530
+ }
531
+ ```
532
+
533
+ ### Vue 3
534
+
535
+ ```vue
536
+ <script setup lang="ts">
537
+ import { onMounted, onUnmounted, ref } from 'vue';
538
+ import { OpenEdit, EditorInterface } from 'open-edit';
539
+
540
+ const container = ref<HTMLDivElement>();
541
+ let editor: EditorInterface;
542
+
543
+ onMounted(() => {
544
+ editor = OpenEdit.create(container.value!, {
545
+ onChange: (html) => emit('update:modelValue', html),
546
+ });
547
+ });
548
+
549
+ onUnmounted(() => editor?.destroy());
550
+ </script>
551
+
552
+ <template>
553
+ <div ref="container" />
554
+ </template>
555
+ ```
556
+
557
+ ---
558
+
559
+ ## Local Development
560
+
561
+ ```bash
562
+ git clone https://github.com/cnagel08/open-edit.git
563
+ cd open-edit
564
+ npm install
565
+ npm run dev # build in watch mode
566
+ # open test.html in your browser to test the editor
567
+ ```
568
+
569
+ ```bash
570
+ npm run build # production build → dist/
571
+ npm run lint # ESLint
572
+ npm test # minimal regression suite
573
+ ```
574
+
575
+ ---
576
+
577
+ ## Repository Structure Notes
578
+
579
+ - `src/` is the OpenEdit library source and the only package code shipped to npm.
580
+ - `d35cb609-ad74-48a3-a8b4-26879657ff81/` is a standalone playground/reference app kept for comparison and experiments.
581
+ - `designs/preview/` is design/prototype material and not part of the published library bundle.
582
+
583
+ ---
584
+
585
+ ## Contributing
586
+
587
+ Contributions are welcome! Please read [CONTRIBUTING.md](.github/CONTRIBUTING.md) before opening a pull request.
588
+
589
+ ---
590
+
591
+ ## License
592
+
593
+ [MIT](LICENSE) © 2026 Christian Nagel
594
+
595
+ Free for personal and commercial use.
@@ -0,0 +1,33 @@
1
+ import type { EditorDocument, MarkType } from './types.js';
2
+ /**
3
+ * Re-sync the document model from the current DOM state.
4
+ * Called after browser-native text input events.
5
+ */
6
+ export declare function syncFromDOM(root: HTMLElement): EditorDocument;
7
+ export declare function execInlineCommand(command: string, value?: string): void;
8
+ /**
9
+ * Set the block type of the block containing the cursor.
10
+ * Returns the updated document.
11
+ */
12
+ export declare function setBlockType(doc: EditorDocument, blockIndex: number, newType: string, attrs?: Record<string, unknown>): EditorDocument;
13
+ /**
14
+ * Toggle a list type on the block at blockIndex.
15
+ * If already that list type, convert back to paragraph.
16
+ */
17
+ export declare function toggleList(doc: EditorDocument, blockIndex: number, listType: 'bullet_list' | 'ordered_list'): EditorDocument;
18
+ /**
19
+ * Set text alignment on the block at blockIndex.
20
+ */
21
+ export declare function setAlignment(doc: EditorDocument, blockIndex: number, align: 'left' | 'center' | 'right' | 'justify'): EditorDocument;
22
+ /**
23
+ * Insert an image node after the block at blockIndex.
24
+ */
25
+ export declare function insertImage(doc: EditorDocument, blockIndex: number, src: string, alt?: string): EditorDocument;
26
+ /**
27
+ * Insert a horizontal rule after the block at blockIndex.
28
+ */
29
+ export declare function insertHr(doc: EditorDocument, blockIndex: number): EditorDocument;
30
+ export declare function isMarkActiveInDOM(markType: MarkType): boolean;
31
+ export declare function getBlockTypeFromDOM(root: HTMLElement): string;
32
+ export declare function getAlignmentFromDOM(root: HTMLElement): string;
33
+ export declare function insertLink(href: string, target: string): void;
@@ -0,0 +1,18 @@
1
+ import type { EditorDocument } from './types.js';
2
+ export declare class History {
3
+ private undoStack;
4
+ private redoStack;
5
+ private _paused;
6
+ /** Push a snapshot onto the undo stack (clears redo) */
7
+ push(doc: EditorDocument): void;
8
+ /** Undo: returns the previous state (or null if nothing to undo) */
9
+ undo(current: EditorDocument): EditorDocument | null;
10
+ /** Redo: returns the next state (or null if nothing to redo) */
11
+ redo(current: EditorDocument): EditorDocument | null;
12
+ canUndo(): boolean;
13
+ canRedo(): boolean;
14
+ /** Pause recording (e.g. during undo/redo itself) */
15
+ pause(): void;
16
+ resume(): void;
17
+ clear(): void;
18
+ }
@@ -0,0 +1,25 @@
1
+ import type { EditorDocument, BlockNode, InlineNode, TextNode, Mark, MarkType, ParagraphNode, HeadingNode, ListItemNode, BulletListNode, OrderedListNode, BlockquoteNode, CodeBlockNode, CalloutNode, CalloutVariant } from './types.js';
2
+ export declare function createDocument(children?: BlockNode[]): EditorDocument;
3
+ export declare function createParagraph(text?: string): ParagraphNode;
4
+ export declare function createHeading(level: 1 | 2 | 3 | 4 | 5 | 6, text?: string): HeadingNode;
5
+ export declare function createText(text: string, marks?: Mark[]): TextNode;
6
+ export declare function createListItem(text?: string): ListItemNode;
7
+ export declare function createBulletList(items?: string[]): BulletListNode;
8
+ export declare function createOrderedList(items?: string[]): OrderedListNode;
9
+ export declare function createBlockquote(text?: string): BlockquoteNode;
10
+ export declare function createCodeBlock(code?: string, lang?: string): CodeBlockNode;
11
+ export declare function createCallout(text?: string, variant?: CalloutVariant): CalloutNode;
12
+ export declare function hasMark(node: TextNode, type: MarkType): boolean;
13
+ export declare function addMark(node: TextNode, mark: Mark): TextNode;
14
+ export declare function removeMark(node: TextNode, type: MarkType): TextNode;
15
+ export declare function toggleMark(node: TextNode, mark: Mark): TextNode;
16
+ /** Get plain text from inline nodes */
17
+ export declare function getPlainText(inlines: InlineNode[]): string;
18
+ /** Split inline content at a character offset, returns [before, after] */
19
+ export declare function splitInlinesAt(inlines: InlineNode[], offset: number): [InlineNode[], InlineNode[]];
20
+ /** Merge adjacent text nodes with identical marks */
21
+ export declare function normalizeInlines(inlines: InlineNode[]): InlineNode[];
22
+ /** Get total character length of a block's inline content */
23
+ export declare function blockLength(block: BlockNode): number;
24
+ /** Create a fresh empty document */
25
+ export declare function emptyDocument(): EditorDocument;