@railway/inkwell 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,375 @@
1
+ import { ReactNode, JSX, RefObject, KeyboardEvent } from 'react';
2
+ import { Plugin } from 'unified';
3
+ import { Awareness } from 'y-protocols/awareness';
4
+ import { XmlText } from 'yjs';
5
+ import { BaseEditor, BaseElement, BaseText } from 'slate';
6
+ import { HistoryEditor } from 'slate-history';
7
+ import { ReactEditor } from 'slate-react';
8
+
9
+ type RehypePlugin$1 = Plugin<any[], any>;
10
+ type RehypePluginConfig$1 = RehypePlugin$1 | [RehypePlugin$1, Record<string, unknown>];
11
+ /**
12
+ * Props for the InkwellEditor component.
13
+ */
14
+ interface InkwellEditorProps {
15
+ /**
16
+ * Markdown content string
17
+ */
18
+ content: string;
19
+ /**
20
+ * Called with serialized markdown on every document change
21
+ */
22
+ onChange?: (content: string) => void;
23
+ /**
24
+ * Additional CSS class for the wrapper element
25
+ */
26
+ className?: string;
27
+ /**
28
+ * Placeholder text shown when editor is empty
29
+ */
30
+ placeholder?: string;
31
+ /**
32
+ * Editor plugins (bubble toolbar, snippets, custom)
33
+ */
34
+ plugins?: InkwellPlugin[];
35
+ /**
36
+ * Custom rehype plugins for the syntax highlighting pipeline
37
+ */
38
+ rehypePlugins?: RehypePluginConfig$1[];
39
+ /**
40
+ * Configure which block-level decorations the editor recognizes. All enabled by default.
41
+ */
42
+ decorations?: InkwellDecorations;
43
+ /**
44
+ * Enable real-time collaborative editing via Yjs
45
+ */
46
+ collaboration?: CollaborationConfig;
47
+ /**
48
+ * Include the built-in bubble menu plugin (default: true). Pass `false` to
49
+ * disable the built-in toolbar; consumers can still add their own via `plugins`.
50
+ */
51
+ bubbleMenu?: boolean;
52
+ }
53
+ /**
54
+ * Props for the InkwellRenderer component.
55
+ */
56
+ interface InkwellRendererProps {
57
+ /**
58
+ * Markdown content string
59
+ */
60
+ content: string;
61
+ /**
62
+ * Additional CSS class for the wrapper element
63
+ */
64
+ className?: string;
65
+ /**
66
+ * Custom component overrides for rendered markdown elements
67
+ */
68
+ components?: InkwellComponents;
69
+ /**
70
+ * Custom rehype plugins for the markdown pipeline
71
+ */
72
+ rehypePlugins?: RehypePluginConfig$1[];
73
+ /**
74
+ * Show a copy button on fenced code blocks (default: true)
75
+ */
76
+ copyButton?: boolean;
77
+ }
78
+ /**
79
+ * Map of HTML element names to custom React components
80
+ */
81
+ type InkwellComponents = Partial<{
82
+ [K in keyof JSX.IntrinsicElements]: (props: JSX.IntrinsicElements[K] & {
83
+ children?: ReactNode;
84
+ }) => ReactNode;
85
+ }>;
86
+ /**
87
+ * Keyboard trigger for a plugin.
88
+ *
89
+ * Uses tinykeys-style key strings:
90
+ * - `"Control+/"` — modifier combo, prevents default
91
+ * - `"@"` — single character, typed into editor (e.g. for mentions)
92
+ */
93
+ interface PluginTrigger {
94
+ /**
95
+ * Key combo (tinykeys format)
96
+ */
97
+ key: string;
98
+ }
99
+ /**
100
+ * Props passed to every plugin's render function on every render
101
+ */
102
+ interface PluginRenderProps {
103
+ /**
104
+ * Whether this plugin's trigger has fired (always true for plugins without triggers)
105
+ */
106
+ active: boolean;
107
+ /**
108
+ * Text typed after the trigger fired
109
+ */
110
+ query: string;
111
+ /**
112
+ * Insert text into the editor at the current cursor position
113
+ */
114
+ onSelect: (text: string) => void;
115
+ /**
116
+ * Deactivate this plugin (resets `active` to false)
117
+ */
118
+ onDismiss: () => void;
119
+ /**
120
+ * Cursor position when the trigger fired
121
+ */
122
+ position: {
123
+ top: number;
124
+ left: number;
125
+ };
126
+ /**
127
+ * Ref to the editor's contenteditable element
128
+ */
129
+ editorRef: RefObject<HTMLDivElement | null>;
130
+ /**
131
+ * Wrap the current selection with markdown markers
132
+ */
133
+ wrapSelection: (before: string, after: string) => void;
134
+ }
135
+ /**
136
+ * Context passed to a plugin's `onKeyDown` handler.
137
+ */
138
+ interface PluginKeyDownContext {
139
+ /**
140
+ * Wrap the current selection with markdown markers
141
+ */
142
+ wrapSelection: (before: string, after: string) => void;
143
+ }
144
+ /**
145
+ * A Inkwell editor plugin.
146
+ *
147
+ * Every plugin is always rendered. Plugins with a `trigger` receive
148
+ * `active: true` when the trigger fires and `active: false` otherwise.
149
+ * Plugins without a trigger always receive `active: true`.
150
+ */
151
+ interface InkwellPlugin {
152
+ /**
153
+ * Unique plugin name
154
+ */
155
+ name: string;
156
+ /**
157
+ * Optional keyboard trigger
158
+ */
159
+ trigger?: PluginTrigger;
160
+ /**
161
+ * Render the plugin UI. Return `null` when inactive.
162
+ */
163
+ render: (props: PluginRenderProps) => ReactNode;
164
+ /**
165
+ * Optional keydown handler. Runs for events on the editor before trigger
166
+ * matching, and is skipped while another plugin is active. Call
167
+ * `event.preventDefault()` to stop further dispatch for this event.
168
+ */
169
+ onKeyDown?: (event: KeyboardEvent, ctx: PluginKeyDownContext) => void;
170
+ }
171
+ /**
172
+ * Props passed to each bubble menu item component.
173
+ */
174
+ interface BubbleMenuItemProps {
175
+ /**
176
+ * Wrap or unwrap the current selection with markdown markers. Toggles if already wrapped.
177
+ */
178
+ wrapSelection: (before: string, after: string) => void;
179
+ }
180
+ /**
181
+ * An item in the bubble menu.
182
+ */
183
+ interface BubbleMenuItem {
184
+ /**
185
+ * Unique key for React reconciliation
186
+ */
187
+ key: string;
188
+ /**
189
+ * Optional keyboard shortcut (single key, used with Cmd/Ctrl).
190
+ */
191
+ shortcut?: string;
192
+ /**
193
+ * Action to run when the shortcut fires. Receives wrapSelection.
194
+ */
195
+ onShortcut?: (wrapSelection: (before: string, after: string) => void) => void;
196
+ /**
197
+ * React component to render in the menu. Receives `wrapSelection`.
198
+ */
199
+ render: (props: BubbleMenuItemProps) => ReactNode;
200
+ }
201
+ /**
202
+ * Snippet item for the snippets plugin
203
+ */
204
+ interface Snippet {
205
+ /**
206
+ * Display title (searchable)
207
+ */
208
+ title: string;
209
+ /**
210
+ * Markdown content to insert
211
+ */
212
+ content: string;
213
+ }
214
+ /**
215
+ * Controls which markdown block elements the editor recognizes.
216
+ * All decorations are enabled by default. Pass `false` to disable.
217
+ */
218
+ interface InkwellDecorations {
219
+ /**
220
+ * Recognize `# ` as h1 (default: true)
221
+ */
222
+ heading1?: boolean;
223
+ /**
224
+ * Recognize `## ` as h2 (default: true)
225
+ */
226
+ heading2?: boolean;
227
+ /**
228
+ * Recognize `### ` as h3 (default: true)
229
+ */
230
+ heading3?: boolean;
231
+ /**
232
+ * Recognize `#### ` as h4 (default: true)
233
+ */
234
+ heading4?: boolean;
235
+ /**
236
+ * Recognize `##### ` as h5 (default: true)
237
+ */
238
+ heading5?: boolean;
239
+ /**
240
+ * Recognize `###### ` as h6 (default: true)
241
+ */
242
+ heading6?: boolean;
243
+ /**
244
+ * Recognize `- `, `* `, `+ ` as list items (default: true)
245
+ */
246
+ lists?: boolean;
247
+ /**
248
+ * Recognize `> ` as blockquotes (default: true)
249
+ */
250
+ blockquotes?: boolean;
251
+ /**
252
+ * Recognize ``` fences as code blocks (default: true)
253
+ */
254
+ codeBlocks?: boolean;
255
+ }
256
+ /**
257
+ * Configuration for real-time collaborative editing via Yjs.
258
+ *
259
+ * The consumer owns the Yjs document and provider (WebSocket,
260
+ * WebRTC, Hocuspocus, etc.). Inkwell only needs the shared type
261
+ * and awareness instance.
262
+ */
263
+ interface CollaborationConfig {
264
+ /**
265
+ * Yjs shared type for the document. Create via `doc.get("content", Y.XmlText)`.
266
+ */
267
+ sharedType: XmlText;
268
+ /**
269
+ * Awareness instance for remote cursor/presence sharing.
270
+ */
271
+ awareness: Awareness;
272
+ /**
273
+ * Local user metadata, displayed on remote cursors.
274
+ */
275
+ user: {
276
+ name: string;
277
+ color: string;
278
+ };
279
+ }
280
+
281
+ declare function InkwellEditor$1({ content, onChange, className, placeholder, plugins: userPlugins, rehypePlugins, decorations, collaboration, bubbleMenu, }: InkwellEditorProps): ReactNode;
282
+
283
+ /**
284
+ * Element types in the Inkwell editor
285
+ */
286
+ type ElementType = "paragraph" | "code-fence" | "code-line" | "blockquote" | "list-item" | "heading";
287
+ /**
288
+ * A block-level element in the editor
289
+ */
290
+ interface InkwellElement extends BaseElement {
291
+ type: ElementType;
292
+ /**
293
+ * Unique element identifier. Session-scoped, not persisted in markdown.
294
+ */
295
+ id: string;
296
+ /**
297
+ * Heading level (1-6). Only present on `heading` elements.
298
+ */
299
+ level?: number;
300
+ children: InkwellText[];
301
+ }
302
+ /**
303
+ * A text leaf node
304
+ */
305
+ interface InkwellText extends BaseText {
306
+ text: string;
307
+ bold?: true;
308
+ italic?: true;
309
+ strikethrough?: true;
310
+ inlineCode?: true;
311
+ boldMarker?: true;
312
+ italicMarker?: true;
313
+ strikeMarker?: true;
314
+ codeMarker?: true;
315
+ hljs?: string;
316
+ remoteCursor?: string;
317
+ remoteCursorCaret?: boolean;
318
+ }
319
+ /**
320
+ * The composed Inkwell editor type
321
+ */
322
+ type InkwellEditor = BaseEditor & ReactEditor & HistoryEditor;
323
+ declare module "slate" {
324
+ interface CustomTypes {
325
+ Editor: InkwellEditor;
326
+ Element: InkwellElement;
327
+ Text: InkwellText;
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Deserialize a markdown string into Slate decorations.
333
+ *
334
+ * Each line becomes its own element. Block-level patterns (code fences,
335
+ * blockquotes, list items, headings) get their own element types based
336
+ * on the `decorations` config. Everything else is a paragraph. Text content
337
+ * is stored verbatim — visual formatting is handled by decorations at
338
+ * render time, not in the data model.
339
+ */
340
+ declare function deserialize(markdown: string, decorations?: InkwellDecorations): InkwellElement[];
341
+
342
+ declare function pluginClass(pluginName: string): (component: string) => string;
343
+
344
+ /**
345
+ * Default bubble menu items: bold, italic, strikethrough.
346
+ */
347
+ declare const defaultBubbleMenuItems: BubbleMenuItem[];
348
+ interface BubbleMenuOptions {
349
+ /**
350
+ * Menu items. Defaults to bold, italic, strikethrough.
351
+ */
352
+ items?: BubbleMenuItem[];
353
+ }
354
+ declare function createBubbleMenuPlugin(options?: BubbleMenuOptions): InkwellPlugin;
355
+
356
+ declare function createSnippetsPlugin(options: {
357
+ snippets: Snippet[];
358
+ key?: string;
359
+ }): InkwellPlugin;
360
+
361
+ /**
362
+ * Convert an HTML string to a markdown string
363
+ */
364
+ declare function serializeToMarkdown(html: string): string;
365
+
366
+ declare function InkwellRenderer({ content, className, components, rehypePlugins, copyButton, }: InkwellRendererProps): ReactNode;
367
+
368
+ type RehypePlugin = Plugin<any[], any>;
369
+ type RehypePluginConfig = RehypePlugin | [RehypePlugin, Record<string, unknown>];
370
+ /**
371
+ * Parse a markdown string into React elements synchronously
372
+ */
373
+ declare function parseMarkdown(markdown: string, components?: InkwellComponents, rehypePlugins?: RehypePluginConfig[]): ReactNode;
374
+
375
+ export { type BubbleMenuItem, type BubbleMenuItemProps, type CollaborationConfig, type InkwellComponents, type InkwellDecorations, InkwellEditor$1 as InkwellEditor, type InkwellEditorProps, type InkwellPlugin, InkwellRenderer, type InkwellRendererProps, type PluginKeyDownContext, type PluginRenderProps, type PluginTrigger, type RehypePluginConfig$1 as RehypePluginConfig, type Snippet, createBubbleMenuPlugin, createSnippetsPlugin, defaultBubbleMenuItems, deserialize, parseMarkdown, pluginClass, serializeToMarkdown };