@railway/inkwell 0.1.0 → 1.0.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/dist/index.d.cts CHANGED
@@ -1,375 +1,478 @@
1
- import { ReactNode, JSX, RefObject, KeyboardEvent } from 'react';
1
+ import * as react from 'react';
2
+ import { ReactNode, JSX, CSSProperties, RefObject, KeyboardEvent } from 'react';
2
3
  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
4
 
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
- */
5
+ type RehypePlugin = Plugin<any[], any>;
6
+ type RehypePluginConfig = RehypePlugin | [RehypePlugin, ...unknown[]];
7
+ interface InkwellEditorState {
8
+ /** Current source content. Markdown syntax is part of the content. */
18
9
  content: string;
19
- /**
20
- * Called with serialized markdown on every document change
21
- */
10
+ /** True when the editor has no non-whitespace content. */
11
+ isEmpty: boolean;
12
+ /** True when the Slate editable is focused. */
13
+ isFocused: boolean;
14
+ /** True when user edits are enabled. */
15
+ isEditable: boolean;
16
+ /** Current source content character count. */
17
+ characterCount: number;
18
+ /** Configured character limit, if any. */
19
+ characterLimit?: number;
20
+ /** True when `characterCount` exceeds `characterLimit`. */
21
+ overLimit: boolean;
22
+ }
23
+ interface InkwellEditorFocusOptions {
24
+ /** Where to place the caret after focusing. Defaults to preserving selection. */
25
+ at?: "start" | "end";
26
+ }
27
+ interface InkwellPluginPlaceholder {
28
+ /** Placeholder text shown by Slate while the editor is empty. */
29
+ text: string;
30
+ /** Optional hint prepended to the placeholder text. */
31
+ hint?: string;
32
+ }
33
+ type InkwellContentSelectionOptions = {
34
+ select?: "start" | "end" | "preserve";
35
+ };
36
+ interface InkwellEditorHandle {
37
+ /** Return a snapshot of current editor state. */
38
+ getState: () => InkwellEditorState;
39
+ /** Focus the editor and optionally move the caret. */
40
+ focus: (options?: InkwellEditorFocusOptions) => void;
41
+ /** Replace the document with empty content without calling onChange. */
42
+ clear: (options?: InkwellContentSelectionOptions) => void;
43
+ /** Replace the current document content without calling onChange. */
44
+ setContent: (content: string, options?: InkwellContentSelectionOptions) => void;
45
+ /** Insert content at the current selection. */
46
+ insertContent: (content: string) => void;
47
+ }
48
+ interface InkwellEditorClassNames {
49
+ /** Class added to the root wrapper. */
50
+ root?: string;
51
+ /** Class added to the editable surface. */
52
+ editor?: string;
53
+ }
54
+ interface InkwellEditorStyles {
55
+ /** Inline styles applied to the root wrapper. */
56
+ root?: CSSProperties;
57
+ /** Inline styles applied to the editable surface. */
58
+ editor?: CSSProperties;
59
+ }
60
+ interface InkwellHeadingFeatures {
61
+ h1?: boolean;
62
+ h2?: boolean;
63
+ h3?: boolean;
64
+ h4?: boolean;
65
+ h5?: boolean;
66
+ h6?: boolean;
67
+ }
68
+ /** Controls which Markdown features the editor recognizes. */
69
+ interface InkwellFeatures {
70
+ /** Recognize heading markers. Pass per-level overrides for granular control. */
71
+ headings?: boolean | InkwellHeadingFeatures;
72
+ /** Recognize `> ` as blockquotes. */
73
+ blockquotes?: boolean;
74
+ /** Recognize fenced code blocks. */
75
+ codeBlocks?: boolean;
76
+ /** Recognize standalone image syntax as block images. */
77
+ images?: boolean;
78
+ }
79
+ interface InkwellEditorProps {
80
+ /** Source content string. Markdown syntax is part of the content. */
81
+ content?: string;
82
+ /** Called with source content on every document change. */
22
83
  onChange?: (content: string) => void;
23
- /**
24
- * Additional CSS class for the wrapper element
25
- */
84
+ /** Called with a full editor state snapshot whenever content, focus, or editability changes. */
85
+ onStateChange?: (state: InkwellEditorState) => void;
86
+ /** Additional CSS class for the root wrapper. Alias for `classNames.root`. */
26
87
  className?: string;
27
- /**
28
- * Placeholder text shown when editor is empty
29
- */
88
+ /** Additional CSS classes for editor slots. */
89
+ classNames?: InkwellEditorClassNames;
90
+ /** Inline styles for editor slots. */
91
+ styles?: InkwellEditorStyles;
92
+ /** Placeholder text shown when editor is empty. */
30
93
  placeholder?: string;
31
- /**
32
- * Editor plugins (bubble toolbar, snippets, custom)
33
- */
94
+ /** Whether users can edit the document. Defaults to true. */
95
+ editable?: boolean;
96
+ /** Editor plugins. */
34
97
  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
- */
98
+ /** Custom rehype plugins for the syntax highlighting pipeline. */
99
+ rehypePlugins?: RehypePluginConfig[];
100
+ /** Configure which Markdown features the editor recognizes. */
101
+ features?: InkwellFeatures;
102
+ /** Include the built-in bubble menu plugin. Defaults to true. */
51
103
  bubbleMenu?: boolean;
104
+ /** Soft character budget. Typing, paste, and inserts past the limit are allowed. */
105
+ characterLimit?: number;
106
+ /** Called on every document change with the current character count and configured limit. */
107
+ onCharacterCount?: (count: number, limit?: number) => void;
108
+ /** When true, Enter submits the editor instead of inserting a newline. */
109
+ submitOnEnter?: boolean;
110
+ /** Called when submitOnEnter handles Enter. */
111
+ onSubmit?: (content: string) => void;
52
112
  }
53
- /**
54
- * Props for the InkwellRenderer component.
55
- */
56
113
  interface InkwellRendererProps {
57
- /**
58
- * Markdown content string
59
- */
114
+ /** Markdown source content string. */
60
115
  content: string;
61
- /**
62
- * Additional CSS class for the wrapper element
63
- */
116
+ /** Additional CSS class for the wrapper element. */
64
117
  className?: string;
65
- /**
66
- * Custom component overrides for rendered markdown elements
67
- */
118
+ /** Custom component overrides for rendered markdown elements. */
68
119
  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;
120
+ /** Custom rehype plugins for the markdown pipeline. */
121
+ rehypePlugins?: RehypePluginConfig[];
122
+ /** Mention patterns to expand in rendered text. */
123
+ mentions?: MentionRenderer[];
124
+ }
125
+ interface ParseMarkdownOptions {
126
+ components?: InkwellComponents;
127
+ rehypePlugins?: RehypePluginConfig[];
128
+ mentions?: MentionRenderer[];
129
+ }
130
+ interface MentionRenderer {
131
+ /** Regular expression applied to text-node content. */
132
+ pattern: RegExp;
133
+ /** Map a match to a React node (rendered in place of the match text). */
134
+ resolve: (match: RegExpExecArray) => ReactNode;
77
135
  }
78
- /**
79
- * Map of HTML element names to custom React components
80
- */
81
136
  type InkwellComponents = Partial<{
82
137
  [K in keyof JSX.IntrinsicElements]: (props: JSX.IntrinsicElements[K] & {
83
138
  children?: ReactNode;
84
139
  }) => ReactNode;
85
140
  }>;
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
- */
141
+ type InkwellPluginActivation = {
142
+ type: "always";
143
+ } | {
144
+ type: "trigger";
97
145
  key: string;
146
+ } | {
147
+ type: "manual";
148
+ };
149
+ type SubscribeForwardedKey = (listener: (key: string) => void) => () => void;
150
+ interface InkwellPluginEditor {
151
+ getState: () => InkwellEditorState;
152
+ isEmpty: () => boolean;
153
+ focus: (options?: InkwellEditorFocusOptions) => void;
154
+ clear: (options?: InkwellContentSelectionOptions) => void;
155
+ setContent: (content: string, options?: InkwellContentSelectionOptions) => void;
156
+ insertContent: (content: string) => void;
157
+ getContentBeforeCursor: () => string | null;
158
+ getCurrentBlockContent: () => string | null;
159
+ getCurrentBlockContentBeforeCursor: () => string | null;
160
+ replaceCurrentBlockContent: (content: string) => void;
161
+ clearCurrentBlock: () => void;
162
+ wrapSelection: (before: string, after: string) => void;
163
+ insertImage: (image: {
164
+ id?: string;
165
+ url: string;
166
+ alt: string;
167
+ }) => string;
168
+ updateImage: (id: string, image: {
169
+ url?: string;
170
+ alt?: string;
171
+ }) => void;
172
+ removeImage: (id: string) => void;
98
173
  }
99
- /**
100
- * Props passed to every plugin's render function on every render
101
- */
102
174
  interface PluginRenderProps {
103
- /**
104
- * Whether this plugin's trigger has fired (always true for plugins without triggers)
105
- */
175
+ /** Whether this plugin is active. Always-on plugins receive true every render. */
106
176
  active: boolean;
107
- /**
108
- * Text typed after the trigger fired
109
- */
177
+ /** Content typed after the trigger fired. */
110
178
  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
- */
179
+ /** Insert content into the editor at the current cursor position. */
180
+ onSelect: (content: string) => void;
181
+ /** Deactivate this plugin. */
118
182
  onDismiss: () => void;
119
- /**
120
- * Cursor position when the trigger fired
121
- */
183
+ /** Cursor position when the trigger fired. */
122
184
  position: {
123
185
  top: number;
124
186
  left: number;
125
187
  };
126
- /**
127
- * Ref to the editor's contenteditable element
128
- */
188
+ /** Ref to the editable DOM element. */
129
189
  editorRef: RefObject<HTMLDivElement | null>;
130
- /**
131
- * Wrap the current selection with markdown markers
132
- */
190
+ /** Narrow editor controller for plugin actions. */
191
+ editor: InkwellPluginEditor;
192
+ /** Wrap the current selection with markdown markers. */
133
193
  wrapSelection: (before: string, after: string) => void;
194
+ /** Subscribe to editor-forwarded keystrokes while this plugin is active. */
195
+ subscribeForwardedKey: SubscribeForwardedKey;
196
+ }
197
+ interface PluginInsertDataContext {
198
+ /** Narrow editor controller for plugin actions. */
199
+ editor: InkwellPluginEditor;
200
+ /** Continue with the editor's default paste/drop handling. */
201
+ insertData: (data: DataTransfer) => void;
134
202
  }
135
- /**
136
- * Context passed to a plugin's `onKeyDown` handler.
137
- */
138
203
  interface PluginKeyDownContext {
139
- /**
140
- * Wrap the current selection with markdown markers
141
- */
204
+ /** Narrow editor controller for plugin actions. */
205
+ editor: InkwellPluginEditor;
206
+ /** Wrap the current selection with markdown markers. */
142
207
  wrapSelection: (before: string, after: string) => void;
208
+ /** Activate the current plugin. */
209
+ activate: (options?: {
210
+ query?: string;
211
+ }) => void;
212
+ /** Dismiss the active plugin. */
213
+ dismiss: () => void;
143
214
  }
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
215
  interface InkwellPlugin {
152
- /**
153
- * Unique plugin name
154
- */
216
+ /** Unique plugin name. */
155
217
  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
- */
218
+ /** Activation behavior. Defaults to `{ type: "always" }`. */
219
+ activation?: InkwellPluginActivation;
220
+ /** Render the plugin UI. Omit for headless plugins. */
221
+ render?: (props: PluginRenderProps) => ReactNode;
222
+ /** Optional dynamic placeholder. */
223
+ getPlaceholder?: (editor: InkwellPluginEditor) => string | InkwellPluginPlaceholder | null;
224
+ /** Optional guard for trigger activation. */
225
+ shouldTrigger?: (event: KeyboardEvent, ctx: PluginKeyDownContext) => boolean;
226
+ /** Optional document-change hook. */
227
+ onEditorChange?: (editor: InkwellPluginEditor) => void;
228
+ /** Optional keydown handler. */
169
229
  onKeyDown?: (event: KeyboardEvent, ctx: PluginKeyDownContext) => void;
230
+ /** Optional keydown handler while this plugin is active. */
231
+ onActiveKeyDown?: (event: KeyboardEvent, ctx: PluginKeyDownContext) => false | void;
232
+ /** Optional paste/drop hook. Return true when the data was handled. */
233
+ onInsertData?: (data: DataTransfer, ctx: PluginInsertDataContext) => boolean | void;
234
+ /** Optional one-time setup. */
235
+ setup?: (editor: InkwellPluginEditor) => void | (() => void);
170
236
  }
171
- /**
172
- * Props passed to each bubble menu item component.
173
- */
174
237
  interface BubbleMenuItemProps {
175
- /**
176
- * Wrap or unwrap the current selection with markdown markers. Toggles if already wrapped.
177
- */
238
+ /** Wrap or unwrap the current selection with markdown markers. */
178
239
  wrapSelection: (before: string, after: string) => void;
179
240
  }
180
- /**
181
- * An item in the bubble menu.
182
- */
183
241
  interface BubbleMenuItem {
184
- /**
185
- * Unique key for React reconciliation
186
- */
187
242
  key: string;
188
- /**
189
- * Optional keyboard shortcut (single key, used with Cmd/Ctrl).
190
- */
191
243
  shortcut?: string;
192
- /**
193
- * Action to run when the shortcut fires. Receives wrapSelection.
194
- */
195
244
  onShortcut?: (wrapSelection: (before: string, after: string) => void) => void;
196
- /**
197
- * React component to render in the menu. Receives `wrapSelection`.
198
- */
199
245
  render: (props: BubbleMenuItemProps) => ReactNode;
200
246
  }
201
- /**
202
- * Snippet item for the snippets plugin
203
- */
204
247
  interface Snippet {
205
- /**
206
- * Display title (searchable)
207
- */
208
248
  title: string;
209
- /**
210
- * Markdown content to insert
211
- */
212
249
  content: string;
213
250
  }
251
+
214
252
  /**
215
- * Controls which markdown block elements the editor recognizes.
216
- * All decorations are enabled by default. Pass `false` to disable.
253
+ * Public wrapper. Returns `null` during SSR so all hooks in the client
254
+ * component below are always called in the same order on the client.
217
255
  */
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;
256
+ declare const InkwellEditor: react.ForwardRefExoticComponent<InkwellEditorProps & react.RefAttributes<InkwellEditorHandle>>;
257
+
258
+ type AttachmentUploadResult = string | {
259
+ url: string;
260
+ alt?: string;
261
+ [key: string]: unknown;
262
+ };
263
+ interface Attachment {
264
+ url: string;
265
+ filename: string;
266
+ mime: string;
267
+ size: number;
268
+ /**
269
+ * Any extra fields returned from `onUpload` (e.g. a service-side
270
+ * record ID) are forwarded onto the attachment.
271
+ */
272
+ [key: string]: unknown;
273
+ }
274
+ interface AttachmentsPluginOptions {
235
275
  /**
236
- * Recognize `##### ` as h5 (default: true)
276
+ * Upload a single file and resolve to the public URL, or an object
277
+ * containing the URL plus optional metadata. Rejection triggers
278
+ * `onError`.
237
279
  */
238
- heading5?: boolean;
280
+ onUpload: (file: File) => Promise<AttachmentUploadResult>;
239
281
  /**
240
- * Recognize `###### ` as h6 (default: true)
282
+ * MIME-type filter. Accepts exact matches (`image/png`) and wildcards
283
+ * (`image/*`). Files that don't match pass through untouched.
241
284
  */
242
- heading6?: boolean;
285
+ accept?: string;
243
286
  /**
244
- * Recognize `- `, `* `, `+ ` as list items (default: true)
287
+ * Placeholder alt text shown on the inserted image element while an
288
+ * image upload is in flight. Defaults to `"Uploading…"`.
245
289
  */
246
- lists?: boolean;
290
+ uploadingPlaceholder?: (file: File) => string;
247
291
  /**
248
- * Recognize `> ` as blockquotes (default: true)
292
+ * Fired after a non-image file finishes uploading. Use this to track
293
+ * attachments in your own state for message submission, chip UI, etc.
294
+ *
295
+ * Image files (MIME `image/*`) are inserted inline as `<img>` and do
296
+ * NOT fire this callback. If omitted, non-image files are passed
297
+ * through to the editor's default paste/drop handling instead of
298
+ * being silently uploaded and discarded.
249
299
  */
250
- blockquotes?: boolean;
300
+ onAttachmentAdd?: (attachment: Attachment) => void;
251
301
  /**
252
- * Recognize ``` fences as code blocks (default: true)
302
+ * Called when `onUpload` rejects, or when the returned URL fails the
303
+ * URL safety allowlist. For image uploads, the placeholder element is
304
+ * removed before this fires.
253
305
  */
254
- codeBlocks?: boolean;
306
+ onError?: (error: unknown, file: File) => void;
255
307
  }
256
308
  /**
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.
309
+ * Intercepts file paste/drop, uploads via `onUpload`, and either
310
+ * inserts an inline image element (for `image/*` files) or fires
311
+ * `onAttachmentAdd` (for non-image files). Non-image files with no
312
+ * `onAttachmentAdd` callback pass through to the editor's default
313
+ * paste/drop handling.
262
314
  */
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
- }
315
+ declare function createAttachmentsPlugin(options: AttachmentsPluginOptions): InkwellPlugin;
280
316
 
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
317
  /**
288
- * A block-level element in the editor
318
+ * Default bubble menu items: bold, italic, strikethrough.
289
319
  */
290
- interface InkwellElement extends BaseElement {
291
- type: ElementType;
292
- /**
293
- * Unique element identifier. Session-scoped, not persisted in markdown.
294
- */
295
- id: string;
320
+ declare const defaultBubbleMenuItems: BubbleMenuItem[];
321
+ interface BubbleMenuOptions {
296
322
  /**
297
- * Heading level (1-6). Only present on `heading` elements.
323
+ * Menu items. Defaults to bold, italic, strikethrough.
298
324
  */
299
- level?: number;
300
- children: InkwellText[];
325
+ items?: BubbleMenuItem[];
301
326
  }
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;
327
+ declare function createBubbleMenuPlugin(options?: BubbleMenuOptions): InkwellPlugin;
328
+
329
+ interface CompletionsPluginOptions {
330
+ /** Unique plugin name. Defaults to `completions`. */
331
+ name?: string;
332
+ /** Completion content to render as placeholder text. Return null when inactive. */
333
+ getCompletion: () => string | null;
334
+ /** Whether to show a loading placeholder while no completion is available. */
335
+ isLoading?: () => boolean;
336
+ /** Text shown while `isLoading()` is true. */
337
+ loadingText?: string;
338
+ /** Hint rendered while the completion placeholder is visible. */
339
+ acceptHint?: string;
340
+ /** Called after Tab accepts and inserts the completion. */
341
+ onAccept?: (completion: string) => void;
342
+ /** Called when Escape or user typing dismisses the current completion. */
343
+ onDismiss?: (completion: string) => void;
344
+ /**
345
+ * Called when an accepted completion is undone back to an empty document.
346
+ * Enabled by default; set `restoreOnUndo` to false to opt out.
347
+ */
348
+ onRestore?: (completion: string) => void;
349
+ /** Whether undo-to-empty should restore the accepted completion. Defaults to true. */
350
+ restoreOnUndo?: boolean;
318
351
  }
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
- }
352
+ declare function createCompletionsPlugin({ name, getCompletion, isLoading, loadingText, acceptHint, onAccept, onDismiss, onRestore, restoreOnUndo, }: CompletionsPluginOptions): InkwellPlugin;
353
+
354
+ interface EmojiItem {
355
+ /** Emoji glyph inserted into the document. */
356
+ emoji: string;
357
+ /** Primary searchable name, without surrounding colons. */
358
+ name: string;
359
+ /** Optional aliases/shortcodes, without surrounding colons. */
360
+ shortcodes?: string[];
361
+ /** Optional searchable tags. */
362
+ tags?: string[];
363
+ }
364
+ interface EmojiPluginBaseOptions<T extends EmojiItem> {
365
+ name?: string;
366
+ trigger?: string;
367
+ renderItem?: (item: T, active: boolean) => ReactNode;
368
+ emptyMessage?: string;
329
369
  }
370
+ type IsExactEmojiItem<T extends EmojiItem> = EmojiItem extends T ? T extends EmojiItem ? true : false : false;
371
+ type EmojiPluginOptions<T extends EmojiItem = EmojiItem> = EmojiPluginBaseOptions<T> & (IsExactEmojiItem<T> extends true ? {
372
+ emojis?: T[];
373
+ search?: (query: string) => Promise<T[]> | T[];
374
+ } : {
375
+ emojis: T[];
376
+ search?: (query: string) => Promise<T[]> | T[];
377
+ } | {
378
+ emojis?: T[];
379
+ search: (query: string) => Promise<T[]> | T[];
380
+ });
381
+ declare const defaultEmojis: EmojiItem[];
382
+ declare function createEmojiPlugin<T extends EmojiItem>(options: EmojiPluginOptions<T>): InkwellPlugin;
383
+ declare function createEmojiPlugin(options?: EmojiPluginOptions): InkwellPlugin;
330
384
 
331
385
  /**
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.
386
+ * Item shape a mentions plugin operates on. `id` is required so the default
387
+ * reference-marker form (`@<marker>[<id>]`) has something to persist.
339
388
  */
340
- declare function deserialize(markdown: string, decorations?: InkwellDecorations): InkwellElement[];
341
-
342
- declare function pluginClass(pluginName: string): (component: string) => string;
343
-
389
+ interface MentionItem {
390
+ id: string;
391
+ title: string;
392
+ }
393
+ interface MentionsPluginOptions<T extends MentionItem = MentionItem> {
394
+ /** Unique plugin name. Defaults to `mentions`. */
395
+ name?: string;
396
+ /** Key that opens the picker. Defaults to `@`. */
397
+ trigger?: string;
398
+ /**
399
+ * Persisted marker name used when `onSelect` is not provided. Produces
400
+ * `@<marker>[<id>]` in the document. Defaults to `mention`.
401
+ */
402
+ marker?: string;
403
+ /** Async search callback. Return items matching the query. */
404
+ search: (query: string) => Promise<T[]> | T[];
405
+ /** Render a single item row in the picker. */
406
+ renderItem: (item: T, active: boolean) => ReactNode;
407
+ /**
408
+ * Map a selected item to the string inserted into the document. When
409
+ * omitted, the plugin inserts the marker form `@<marker>[<id>]`.
410
+ */
411
+ onSelect?: (item: T) => string;
412
+ /** Fallback message when `search` returns no results. */
413
+ emptyMessage?: string;
414
+ }
344
415
  /**
345
- * Default bubble menu items: bold, italic, strikethrough.
416
+ * Generic mentions plugin. Pick items from an async source and insert either
417
+ * the expanded content (via `onSelect`) or a persisted marker `@<marker>[<id>]`.
346
418
  */
347
- declare const defaultBubbleMenuItems: BubbleMenuItem[];
348
- interface BubbleMenuOptions {
349
- /**
350
- * Menu items. Defaults to bold, italic, strikethrough.
351
- */
352
- items?: BubbleMenuItem[];
419
+ declare function createMentionsPlugin<T extends MentionItem = MentionItem>(options: MentionsPluginOptions<T>): InkwellPlugin;
420
+
421
+ interface SlashCommandChoice {
422
+ value: string;
423
+ label: string;
424
+ disabled?: boolean;
353
425
  }
354
- declare function createBubbleMenuPlugin(options?: BubbleMenuOptions): InkwellPlugin;
426
+ interface SlashCommandArg {
427
+ name: string;
428
+ description: string;
429
+ choices?: SlashCommandChoice[];
430
+ fetchChoices?: () => Promise<SlashCommandChoice[]>;
431
+ }
432
+ interface SlashCommandItem {
433
+ name: string;
434
+ description: string;
435
+ aliases?: string[];
436
+ arg?: SlashCommandArg;
437
+ disabled?: () => string | false;
438
+ }
439
+ interface SlashCommandExecution {
440
+ name: string;
441
+ args: Record<string, string>;
442
+ raw: string;
443
+ }
444
+ interface SlashCommandsPluginOptions<T extends SlashCommandItem = SlashCommandItem> {
445
+ /** Plugin name. Defaults to `"slash-commands"`. */
446
+ name?: string;
447
+ /** Commands shown in the picker. */
448
+ commands: T[];
449
+ /** Called whenever the menu transitions in and out of the execute phase. */
450
+ onReadyChange?: (ready: boolean) => void;
451
+ /** Called with the structured execution payload when the user presses Enter
452
+ * during the execute phase. */
453
+ onExecute?: (command: SlashCommandExecution) => void;
454
+ /** Fallback message when filtering returns no commands. */
455
+ emptyMessage?: string;
456
+ }
457
+ declare const createSlashCommandsPlugin: <T extends SlashCommandItem>({ name, commands, onReadyChange, onExecute, emptyMessage, }: SlashCommandsPluginOptions<T>) => InkwellPlugin;
355
458
 
356
- declare function createSnippetsPlugin(options: {
459
+ interface SnippetsPluginOptions {
460
+ name?: string;
461
+ trigger?: string;
357
462
  snippets: Snippet[];
358
- key?: string;
359
- }): InkwellPlugin;
463
+ }
464
+ declare function createSnippetsPlugin({ snippets, name, trigger, }: SnippetsPluginOptions): InkwellPlugin;
360
465
 
361
466
  /**
362
467
  * Convert an HTML string to a markdown string
363
468
  */
364
- declare function serializeToMarkdown(html: string): string;
469
+ declare function htmlToMarkdown(html: string): string;
365
470
 
366
- declare function InkwellRenderer({ content, className, components, rehypePlugins, copyButton, }: InkwellRendererProps): ReactNode;
471
+ declare function InkwellRenderer({ content, className, components, rehypePlugins, mentions, }: InkwellRendererProps): ReactNode;
367
472
 
368
- type RehypePlugin = Plugin<any[], any>;
369
- type RehypePluginConfig = RehypePlugin | [RehypePlugin, Record<string, unknown>];
370
473
  /**
371
474
  * Parse a markdown string into React elements synchronously
372
475
  */
373
- declare function parseMarkdown(markdown: string, components?: InkwellComponents, rehypePlugins?: RehypePluginConfig[]): ReactNode;
476
+ declare function parseMarkdown(content: string, options?: ParseMarkdownOptions): ReactNode;
374
477
 
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 };
478
+ export { type Attachment, type AttachmentUploadResult, type AttachmentsPluginOptions, type BubbleMenuItem, type BubbleMenuItemProps, type BubbleMenuOptions, type CompletionsPluginOptions, type EmojiItem, type EmojiPluginOptions, type InkwellComponents, InkwellEditor, type InkwellEditorClassNames, type InkwellEditorFocusOptions, type InkwellEditorHandle, type InkwellEditorProps, type InkwellEditorState, type InkwellEditorStyles, type InkwellFeatures, type InkwellPlugin, type InkwellPluginActivation, type InkwellPluginEditor, type InkwellPluginPlaceholder, InkwellRenderer, type InkwellRendererProps, type MentionItem, type MentionRenderer, type MentionsPluginOptions, type ParseMarkdownOptions, type PluginInsertDataContext, type PluginKeyDownContext, type PluginRenderProps, type RehypePluginConfig, type SlashCommandArg, type SlashCommandChoice, type SlashCommandExecution, type SlashCommandItem, type SlashCommandsPluginOptions, type Snippet, type SnippetsPluginOptions, type SubscribeForwardedKey, createAttachmentsPlugin, createBubbleMenuPlugin, createCompletionsPlugin, createEmojiPlugin, createMentionsPlugin, createSlashCommandsPlugin, createSnippetsPlugin, defaultBubbleMenuItems, defaultEmojis, htmlToMarkdown, parseMarkdown };