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.
@@ -0,0 +1,229 @@
1
+ export type MarkType = 'bold' | 'italic' | 'underline' | 'code' | 'link' | 'strikethrough';
2
+ export interface LinkMark {
3
+ type: 'link';
4
+ href: string;
5
+ target?: '_blank' | '_self';
6
+ title?: string;
7
+ }
8
+ export interface SimpleMark {
9
+ type: Exclude<MarkType, 'link'>;
10
+ }
11
+ export type Mark = SimpleMark | LinkMark;
12
+ export interface TextNode {
13
+ type: 'text';
14
+ text: string;
15
+ marks: Mark[];
16
+ }
17
+ export interface HardBreakNode {
18
+ type: 'hardbreak';
19
+ }
20
+ export type InlineNode = TextNode | HardBreakNode;
21
+ export type CalloutVariant = 'info' | 'success' | 'warning' | 'danger';
22
+ export type BlockType = 'paragraph' | 'heading' | 'blockquote' | 'code_block' | 'bullet_list' | 'ordered_list' | 'list_item' | 'image' | 'hr' | 'callout';
23
+ export interface ParagraphNode {
24
+ type: 'paragraph';
25
+ align?: 'left' | 'center' | 'right' | 'justify';
26
+ children: InlineNode[];
27
+ }
28
+ export interface HeadingNode {
29
+ type: 'heading';
30
+ level: 1 | 2 | 3 | 4 | 5 | 6;
31
+ align?: 'left' | 'center' | 'right' | 'justify';
32
+ children: InlineNode[];
33
+ }
34
+ export interface BlockquoteNode {
35
+ type: 'blockquote';
36
+ children: BlockNode[];
37
+ }
38
+ export interface CodeBlockNode {
39
+ type: 'code_block';
40
+ lang?: string;
41
+ children: TextNode[];
42
+ }
43
+ export interface BulletListNode {
44
+ type: 'bullet_list';
45
+ children: ListItemNode[];
46
+ }
47
+ export interface OrderedListNode {
48
+ type: 'ordered_list';
49
+ start?: number;
50
+ children: ListItemNode[];
51
+ }
52
+ export interface ListItemNode {
53
+ type: 'list_item';
54
+ children: InlineNode[];
55
+ }
56
+ export interface ImageNode {
57
+ type: 'image';
58
+ src: string;
59
+ alt?: string;
60
+ width?: number;
61
+ height?: number;
62
+ }
63
+ export interface HrNode {
64
+ type: 'hr';
65
+ }
66
+ export interface CalloutNode {
67
+ type: 'callout';
68
+ variant: CalloutVariant;
69
+ children: InlineNode[];
70
+ }
71
+ export type BlockNode = ParagraphNode | HeadingNode | BlockquoteNode | CodeBlockNode | BulletListNode | OrderedListNode | ListItemNode | ImageNode | HrNode | CalloutNode;
72
+ export interface EditorDocument {
73
+ children: BlockNode[];
74
+ }
75
+ export interface ModelPosition {
76
+ /** Index path into document.children (and nested children) */
77
+ blockIndex: number;
78
+ /** For list items: the item index within the list */
79
+ itemIndex?: number;
80
+ /** Character offset within the inline content */
81
+ offset: number;
82
+ }
83
+ export interface ModelSelection {
84
+ anchor: ModelPosition;
85
+ focus: ModelPosition;
86
+ isCollapsed: boolean;
87
+ }
88
+ export interface EditorEventMap {
89
+ 'change': EditorDocument;
90
+ 'selectionchange': ModelSelection | null;
91
+ 'focus': void;
92
+ 'blur': void;
93
+ }
94
+ export type EditorEventListener<K extends keyof EditorEventMap> = (payload: EditorEventMap[K]) => void;
95
+ export interface EditorPlugin {
96
+ name: string;
97
+ onInit?: (editor: EditorInterface) => void;
98
+ onDestroy?: (editor: EditorInterface) => void;
99
+ commands?: Record<string, (...args: unknown[]) => void>;
100
+ toolbarItems?: ToolbarItemConfig[];
101
+ keymaps?: Record<string, (editor: EditorInterface) => boolean>;
102
+ }
103
+ export type ToolbarItemType = 'button' | 'separator' | 'select' | 'spacer';
104
+ export interface ToolbarButtonConfig {
105
+ type: 'button';
106
+ id: string;
107
+ icon: string;
108
+ title: string;
109
+ command: string;
110
+ commandArgs?: unknown[];
111
+ isActive?: (editor: EditorInterface) => boolean;
112
+ }
113
+ export interface ToolbarSeparatorConfig {
114
+ type: 'separator';
115
+ }
116
+ export interface ToolbarSpacerConfig {
117
+ type: 'spacer';
118
+ }
119
+ export interface ToolbarSelectConfig {
120
+ type: 'select';
121
+ id: string;
122
+ title: string;
123
+ options: Array<{
124
+ label: string;
125
+ value: string;
126
+ }>;
127
+ getValue: (editor: EditorInterface) => string;
128
+ command: string;
129
+ }
130
+ export type ToolbarItemConfig = ToolbarButtonConfig | ToolbarSeparatorConfig | ToolbarSpacerConfig | ToolbarSelectConfig;
131
+ export type { EditorLocale } from '../locales/types.js';
132
+ export interface StatusBarOptions {
133
+ /** Show word count. Default: true */
134
+ wordCount?: boolean;
135
+ /** Show character count. Default: true */
136
+ charCount?: boolean;
137
+ /** Show element path (e.g. "p › strong"). Default: true */
138
+ elementPath?: boolean;
139
+ /** Show HTML source toggle button. Default: true */
140
+ htmlToggle?: boolean;
141
+ }
142
+ export interface EditorOptions {
143
+ /** CSS selector or DOM element to mount the editor into */
144
+ element: string | HTMLElement;
145
+ /** Initial HTML content */
146
+ content?: string;
147
+ /** Placeholder text shown when editor is empty */
148
+ placeholder?: string;
149
+ /** 'light' | 'dark' | 'auto' */
150
+ theme?: 'light' | 'dark' | 'auto';
151
+ /** Whether editor is read-only */
152
+ readOnly?: boolean;
153
+ /** Full custom toolbar configuration (takes precedence over toolbarItems) */
154
+ toolbar?: ToolbarItemConfig[];
155
+ /**
156
+ * Filter which default toolbar buttons/selects to show by their ID.
157
+ * Orphaned separators are removed automatically.
158
+ * Has no effect if `toolbar` is set explicitly.
159
+ * Available IDs: 'undo', 'redo', 'blockType', 'bold', 'italic', 'underline',
160
+ * 'code', 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify',
161
+ * 'bulletList', 'orderedList', 'link', 'image', 'blockquote', 'hr', 'htmlToggle'
162
+ * @example ['bold', 'italic', 'underline', 'link']
163
+ */
164
+ toolbarItems?: string[];
165
+ /**
166
+ * Control the status bar.
167
+ * false = hide entirely. Object = show/hide individual parts.
168
+ * @example false
169
+ * @example { wordCount: true, charCount: false, elementPath: false, htmlToggle: false }
170
+ */
171
+ statusBar?: boolean | StatusBarOptions;
172
+ /**
173
+ * UI locale. Defaults to English.
174
+ * Pass a full locale object or override individual keys with Partial<EditorLocale>.
175
+ * @example import { de } from 'openedit'; // → locale: de
176
+ */
177
+ locale?: import('../locales/types.js').EditorLocale | Partial<import('../locales/types.js').EditorLocale>;
178
+ /** Called when content changes, receives clean HTML */
179
+ onChange?: (html: string) => void;
180
+ /** Hook for image uploads */
181
+ onImageUpload?: (file: File) => Promise<string>;
182
+ }
183
+ export interface EditorInterface {
184
+ /** Get current content as clean HTML */
185
+ getHTML(): string;
186
+ /** Set content from HTML string */
187
+ setHTML(html: string): void;
188
+ /** Get current content as Markdown */
189
+ getMarkdown(): string;
190
+ /** Set content from Markdown string */
191
+ setMarkdown(md: string): void;
192
+ /** Get current document model */
193
+ getDocument(): EditorDocument;
194
+ /** Execute a named command */
195
+ chain(): ChainInterface;
196
+ /** Register a plugin */
197
+ use(plugin: EditorPlugin): EditorInterface;
198
+ /** Add event listener */
199
+ on<K extends keyof EditorEventMap>(event: K, listener: EditorEventListener<K>): void;
200
+ /** Remove event listener */
201
+ off<K extends keyof EditorEventMap>(event: K, listener: EditorEventListener<K>): void;
202
+ /** Destroy the editor and clean up */
203
+ destroy(): void;
204
+ /** Focus the editor */
205
+ focus(): void;
206
+ /** Blur the editor */
207
+ blur(): void;
208
+ /** Is the editor focused? */
209
+ isFocused(): boolean;
210
+ /** Current selection (null if not focused) */
211
+ getSelection(): ModelSelection | null;
212
+ /** Whether a mark is active at current selection */
213
+ isMarkActive(type: MarkType): boolean;
214
+ /** Get active block type at cursor */
215
+ getActiveBlockType(): string;
216
+ /** Whether the document is empty */
217
+ isEmpty(): boolean;
218
+ }
219
+ export interface ChainInterface {
220
+ toggleMark(type: MarkType, attrs?: Record<string, unknown>): ChainInterface;
221
+ setBlock(type: BlockType, attrs?: Record<string, unknown>): ChainInterface;
222
+ setAlign(align: 'left' | 'center' | 'right' | 'justify'): ChainInterface;
223
+ insertImage(src: string, alt?: string): ChainInterface;
224
+ insertHr(): ChainInterface;
225
+ toggleList(type: 'bullet_list' | 'ordered_list'): ChainInterface;
226
+ undo(): ChainInterface;
227
+ redo(): ChainInterface;
228
+ run(): void;
229
+ }
@@ -0,0 +1,64 @@
1
+ import type { EditorDocument, EditorOptions, EditorInterface, EditorPlugin, EditorEventMap, EditorEventListener, ModelSelection, MarkType, ChainInterface } from './core/types.js';
2
+ import type { EditorLocale } from './locales/types.js';
3
+ export declare class Editor implements EditorInterface {
4
+ private doc;
5
+ private history;
6
+ private root;
7
+ private editorEl;
8
+ private containerEl;
9
+ private toolbar;
10
+ private bubbleToolbar;
11
+ private imageResizer;
12
+ private codeLangPicker;
13
+ private plugins;
14
+ private options;
15
+ readonly locale: EditorLocale;
16
+ private listeners;
17
+ private isComposing;
18
+ private _isFocused;
19
+ private isUpdating;
20
+ private syncTimer;
21
+ constructor(options: EditorOptions);
22
+ private buildContainer;
23
+ private buildEditorEl;
24
+ private attachEvents;
25
+ private readonly onInput;
26
+ private readonly onCompositionStart;
27
+ private readonly onCompositionEnd;
28
+ private readonly onHTMLToggleClick;
29
+ private scheduleSyncFromDOM;
30
+ /** Sync model from DOM without a full rerender (used after inline execCommands) */
31
+ syncModelFromDOM(): void;
32
+ private readonly onSelectionChange;
33
+ private readonly onFocus;
34
+ private readonly onBlur;
35
+ private readonly onKeydown;
36
+ private readonly onTabInCodeBlock;
37
+ private readonly onPaste;
38
+ private readonly onDrop;
39
+ private isHTMLMode;
40
+ toggleHTMLMode(): void;
41
+ chain(): ChainInterface;
42
+ private rerender;
43
+ private updateStatusBar;
44
+ private updateElementPath;
45
+ private updatePlaceholder;
46
+ getHTML(): string;
47
+ setHTML(html: string): void;
48
+ getMarkdown(): string;
49
+ setMarkdown(md: string): void;
50
+ getDocument(): EditorDocument;
51
+ use(plugin: EditorPlugin): EditorInterface;
52
+ on<K extends keyof EditorEventMap>(event: K, listener: EditorEventListener<K>): void;
53
+ off<K extends keyof EditorEventMap>(event: K, listener: EditorEventListener<K>): void;
54
+ private emit;
55
+ destroy(): void;
56
+ focus(): void;
57
+ blur(): void;
58
+ isFocused(): boolean;
59
+ getSelection(): ModelSelection | null;
60
+ isMarkActive(type: MarkType): boolean;
61
+ getActiveBlockType(): string;
62
+ isEmpty(): boolean;
63
+ private applyTheme;
64
+ }
@@ -0,0 +1,61 @@
1
+ import { Editor } from './editor.js';
2
+ import { en } from './locales/en.js';
3
+ import { de } from './locales/de.js';
4
+ import { serializeToMarkdown, deserializeMarkdown } from './io/markdown.js';
5
+ import { createHighlightPlugin } from './plugins/highlight.js';
6
+ import { createEmojiPlugin } from './plugins/emoji.js';
7
+ import { createTemplateTagPlugin } from './plugins/template-tags.js';
8
+ import { createAIPlugin } from './plugins/ai.js';
9
+ import { createCalloutPlugin } from './plugins/callout.js';
10
+ import { createSlashCommandsPlugin } from './plugins/slash-commands.js';
11
+ import type { EditorOptions, EditorInterface, EditorPlugin, EditorDocument, EditorEventMap, ToolbarItemConfig, StatusBarOptions, Mark, MarkType, BlockNode, InlineNode, TextNode, ModelSelection, CalloutVariant, CalloutNode } from './core/types.js';
12
+ /**
13
+ * Create a new OpenEdit editor instance.
14
+ *
15
+ * @example
16
+ * // Script tag usage
17
+ * const editor = OpenEdit.create('#my-editor', {
18
+ * content: '<p>Hello world</p>',
19
+ * placeholder: 'Start typing...',
20
+ * theme: 'auto',
21
+ * onChange: (html) => console.log(html),
22
+ * });
23
+ *
24
+ * @example
25
+ * // With image upload hook
26
+ * const editor = OpenEdit.create('#editor', {
27
+ * onImageUpload: async (file) => {
28
+ * const formData = new FormData();
29
+ * formData.append('file', file);
30
+ * const res = await fetch('/upload', { method: 'POST', body: formData });
31
+ * const { url } = await res.json();
32
+ * return url;
33
+ * }
34
+ * });
35
+ */
36
+ declare function create(element: string | HTMLElement, options?: Omit<EditorOptions, 'element'>): EditorInterface;
37
+ export declare const OpenEdit: {
38
+ create: typeof create;
39
+ version: string;
40
+ locales: {
41
+ en: import("./index.js").EditorLocale;
42
+ de: import("./index.js").EditorLocale;
43
+ };
44
+ plugins: {
45
+ highlight: typeof createHighlightPlugin;
46
+ emoji: typeof createEmojiPlugin;
47
+ templateTags: typeof createTemplateTagPlugin;
48
+ ai: typeof createAIPlugin;
49
+ callout: typeof createCalloutPlugin;
50
+ slashCommands: typeof createSlashCommandsPlugin;
51
+ };
52
+ markdown: {
53
+ serialize: typeof serializeToMarkdown;
54
+ deserialize: typeof deserializeMarkdown;
55
+ };
56
+ };
57
+ export { Editor, serializeToMarkdown, deserializeMarkdown, createHighlightPlugin, createEmojiPlugin, createTemplateTagPlugin, createAIPlugin, createCalloutPlugin, createSlashCommandsPlugin, en, de };
58
+ export type { EditorOptions, EditorInterface, EditorPlugin, EditorDocument, EditorEventMap, ToolbarItemConfig, StatusBarOptions, Mark, MarkType, BlockNode, InlineNode, TextNode, ModelSelection, CalloutVariant, CalloutNode, };
59
+ export type { SlashCommand, SlashCommandsOptions } from './plugins/slash-commands.js';
60
+ export type { EditorLocale } from './locales/types.js';
61
+ export default OpenEdit;
@@ -0,0 +1,3 @@
1
+ import type { EditorDocument, TextNode } from '../core/types.js';
2
+ export declare function deserializeHTML(html: string): EditorDocument;
3
+ export type { TextNode };
@@ -0,0 +1,3 @@
1
+ import type { EditorDocument } from '../core/types.js';
2
+ export declare function serializeToMarkdown(doc: EditorDocument): string;
3
+ export declare function deserializeMarkdown(md: string): EditorDocument;
@@ -0,0 +1,4 @@
1
+ import type { EditorDocument } from '../core/types.js';
2
+ export declare function serializeToHTML(doc: EditorDocument): string;
3
+ export declare function escapeHTML(str: string): string;
4
+ export declare function escapeAttr(str: string): string;
@@ -0,0 +1,2 @@
1
+ import type { EditorLocale } from './types.js';
2
+ export declare const de: EditorLocale;
@@ -0,0 +1,2 @@
1
+ import type { EditorLocale } from './types.js';
2
+ export declare const en: EditorLocale;
@@ -0,0 +1,77 @@
1
+ export interface EditorLocale {
2
+ toolbar: {
3
+ undo: string;
4
+ redo: string;
5
+ textFormat: string;
6
+ paragraph: string;
7
+ heading: (level: number) => string;
8
+ quote: string;
9
+ codeBlock: string;
10
+ bold: string;
11
+ italic: string;
12
+ underline: string;
13
+ inlineCode: string;
14
+ alignLeft: string;
15
+ alignCenter: string;
16
+ alignRight: string;
17
+ justify: string;
18
+ bulletList: string;
19
+ orderedList: string;
20
+ insertLink: string;
21
+ insertImage: string;
22
+ blockquote: string;
23
+ horizontalRule: string;
24
+ htmlSource: string;
25
+ calloutInfo: string;
26
+ calloutSuccess: string;
27
+ calloutWarning: string;
28
+ calloutDanger: string;
29
+ insertCallout: string;
30
+ };
31
+ statusBar: {
32
+ /** Label prefix for word count, e.g. "Words" */
33
+ words: string;
34
+ /** Label prefix for character count, e.g. "Characters" */
35
+ characters: string;
36
+ /** Label/text on the HTML source toggle button */
37
+ htmlSource: string;
38
+ };
39
+ dialogs: {
40
+ linkUrl: string;
41
+ openInNewTab: string;
42
+ imageUrl: string;
43
+ imageAlt: string;
44
+ };
45
+ plugins: {
46
+ ai: {
47
+ panelTitle: string;
48
+ noSelection: string;
49
+ customPromptPlaceholder: string;
50
+ runButton: string;
51
+ applyButton: string;
52
+ generating: string;
53
+ noApiKey: string;
54
+ errorPrefix: string;
55
+ actions: {
56
+ improve: string;
57
+ shorten: string;
58
+ expand: string;
59
+ summarize: string;
60
+ toGerman: string;
61
+ toEnglish: string;
62
+ };
63
+ };
64
+ emoji: {
65
+ buttonTitle: string;
66
+ categories: {
67
+ faces: string;
68
+ hearts: string;
69
+ gestures: string;
70
+ nature: string;
71
+ food: string;
72
+ objects: string;
73
+ symbols: string;
74
+ };
75
+ };
76
+ };
77
+ }