@sendbird/actionbook-core 0.7.2
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 +157 -0
- package/dist/index.d.ts +393 -0
- package/dist/index.js +5316 -0
- package/dist/index.js.map +1 -0
- package/dist/types-rEXLrfo_.d.ts +191 -0
- package/dist/ui/index.d.ts +383 -0
- package/dist/ui/index.js +6708 -0
- package/dist/ui/index.js.map +1 -0
- package/package.json +115 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
type BoldMark = {
|
|
2
|
+
readonly type: 'bold';
|
|
3
|
+
};
|
|
4
|
+
type ItalicMark = {
|
|
5
|
+
readonly type: 'italic';
|
|
6
|
+
};
|
|
7
|
+
type UnderlineMark = {
|
|
8
|
+
readonly type: 'underline';
|
|
9
|
+
};
|
|
10
|
+
type StrikethroughMark = {
|
|
11
|
+
readonly type: 'strikethrough';
|
|
12
|
+
};
|
|
13
|
+
type CodeMark = {
|
|
14
|
+
readonly type: 'code';
|
|
15
|
+
};
|
|
16
|
+
type LinkMark = {
|
|
17
|
+
readonly type: 'link';
|
|
18
|
+
readonly href: string;
|
|
19
|
+
readonly title?: string;
|
|
20
|
+
};
|
|
21
|
+
type Mark = BoldMark | ItalicMark | UnderlineMark | StrikethroughMark | CodeMark | LinkMark;
|
|
22
|
+
type TextNode = {
|
|
23
|
+
readonly type: 'text';
|
|
24
|
+
readonly text: string;
|
|
25
|
+
readonly marks?: readonly Mark[];
|
|
26
|
+
};
|
|
27
|
+
type ResourceTagNode = {
|
|
28
|
+
readonly type: 'resourceTag';
|
|
29
|
+
readonly tagType: 'tool' | 'manual' | 'agent_message_template' | 'handoff' | 'time_diff';
|
|
30
|
+
readonly resourceId: string;
|
|
31
|
+
readonly text: string;
|
|
32
|
+
};
|
|
33
|
+
type JumpPointNode = {
|
|
34
|
+
readonly type: 'jumpPoint';
|
|
35
|
+
readonly id: string;
|
|
36
|
+
};
|
|
37
|
+
type HardBreakNode = {
|
|
38
|
+
readonly type: 'hardBreak';
|
|
39
|
+
};
|
|
40
|
+
type JinjaIfBranch<T> = {
|
|
41
|
+
readonly branchType: 'if' | 'elif' | 'else';
|
|
42
|
+
readonly condition?: string;
|
|
43
|
+
readonly content: readonly T[];
|
|
44
|
+
};
|
|
45
|
+
type JinjaIfInlineNode = {
|
|
46
|
+
readonly type: 'jinjaIfInline';
|
|
47
|
+
readonly branches: readonly JinjaIfBranch<InlineNode>[];
|
|
48
|
+
};
|
|
49
|
+
type InlineNode = TextNode | ResourceTagNode | JumpPointNode | HardBreakNode | JinjaIfInlineNode;
|
|
50
|
+
type ParagraphNode = {
|
|
51
|
+
readonly type: 'paragraph';
|
|
52
|
+
readonly content: readonly InlineNode[];
|
|
53
|
+
};
|
|
54
|
+
type HeadingNode = {
|
|
55
|
+
readonly type: 'heading';
|
|
56
|
+
readonly level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
57
|
+
readonly content: readonly InlineNode[];
|
|
58
|
+
};
|
|
59
|
+
type ListItemNode = {
|
|
60
|
+
readonly type: 'listItem';
|
|
61
|
+
readonly checked?: boolean | null;
|
|
62
|
+
readonly content: readonly BlockNode[];
|
|
63
|
+
};
|
|
64
|
+
type BulletListNode = {
|
|
65
|
+
readonly type: 'bulletList';
|
|
66
|
+
readonly content: readonly ListItemNode[];
|
|
67
|
+
};
|
|
68
|
+
type OrderedListNode = {
|
|
69
|
+
readonly type: 'orderedList';
|
|
70
|
+
readonly start: number;
|
|
71
|
+
readonly content: readonly ListItemNode[];
|
|
72
|
+
};
|
|
73
|
+
type BlockquoteNode = {
|
|
74
|
+
readonly type: 'blockquote';
|
|
75
|
+
readonly content: readonly BlockNode[];
|
|
76
|
+
};
|
|
77
|
+
type HorizontalRuleNode = {
|
|
78
|
+
readonly type: 'horizontalRule';
|
|
79
|
+
};
|
|
80
|
+
type TableCellNode = {
|
|
81
|
+
readonly type: 'tableCell';
|
|
82
|
+
readonly header?: boolean;
|
|
83
|
+
readonly content: readonly InlineNode[];
|
|
84
|
+
};
|
|
85
|
+
type TableRowNode = {
|
|
86
|
+
readonly type: 'tableRow';
|
|
87
|
+
readonly content: readonly TableCellNode[];
|
|
88
|
+
};
|
|
89
|
+
type TableNode = {
|
|
90
|
+
readonly type: 'table';
|
|
91
|
+
readonly content: readonly TableRowNode[];
|
|
92
|
+
};
|
|
93
|
+
type JinjaIfBlockNode = {
|
|
94
|
+
readonly type: 'jinjaIfBlock';
|
|
95
|
+
readonly branches: readonly JinjaIfBranch<BlockNode>[];
|
|
96
|
+
};
|
|
97
|
+
type BlockNode = ParagraphNode | HeadingNode | BulletListNode | OrderedListNode | ListItemNode | BlockquoteNode | HorizontalRuleNode | TableNode | JinjaIfBlockNode;
|
|
98
|
+
type DocumentNode = {
|
|
99
|
+
readonly type: 'doc';
|
|
100
|
+
readonly content: readonly BlockNode[];
|
|
101
|
+
};
|
|
102
|
+
type AstNode = DocumentNode | BlockNode | InlineNode | TableRowNode | TableCellNode;
|
|
103
|
+
type NodePath = readonly number[];
|
|
104
|
+
declare const RESOURCE_TAG_TYPES: readonly ["tool", "manual", "agent_message_template", "handoff", "time_diff"];
|
|
105
|
+
type ResourceTagType = (typeof RESOURCE_TAG_TYPES)[number];
|
|
106
|
+
declare const JUMP_POINT_ID_PATTERN: RegExp;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Convert ProseMirror/Tiptap JSONContent to ActionbookAST.
|
|
110
|
+
*
|
|
111
|
+
* This handles the legacy editor_data format stored in LogicV2.
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
type JSONContent = {
|
|
115
|
+
type?: string;
|
|
116
|
+
attrs?: Record<string, unknown>;
|
|
117
|
+
content?: JSONContent[];
|
|
118
|
+
marks?: {
|
|
119
|
+
type: string;
|
|
120
|
+
attrs?: Record<string, unknown>;
|
|
121
|
+
}[];
|
|
122
|
+
text?: string;
|
|
123
|
+
};
|
|
124
|
+
declare function fromProseMirrorJSON(pmJSON: JSONContent): DocumentNode;
|
|
125
|
+
|
|
126
|
+
type LintSeverity = 'error' | 'warning' | 'info';
|
|
127
|
+
type LintResult = {
|
|
128
|
+
readonly ruleId: string;
|
|
129
|
+
readonly severity: LintSeverity;
|
|
130
|
+
readonly message: string;
|
|
131
|
+
readonly path?: NodePath;
|
|
132
|
+
readonly suggestion?: string;
|
|
133
|
+
};
|
|
134
|
+
type LintVisitorHandler = (node: AstNode, path?: NodePath) => void;
|
|
135
|
+
/**
|
|
136
|
+
* Optional visitor-based interface for LintRule.
|
|
137
|
+
* When a rule provides `buildVisitor`, the runner will invoke it as part of a
|
|
138
|
+
* single combined AST traversal instead of running `rule.run()` per-rule.
|
|
139
|
+
*
|
|
140
|
+
* `buildVisitor` returns per-node-type handlers and a `finish()` function
|
|
141
|
+
* that collects accumulated results after the traversal completes.
|
|
142
|
+
*/
|
|
143
|
+
type LintVisitorFactory = (ctx: LintContext) => {
|
|
144
|
+
visitor: Partial<Record<string, LintVisitorHandler>>;
|
|
145
|
+
finish: (severity: LintSeverity) => LintResult[];
|
|
146
|
+
};
|
|
147
|
+
type LintRule = {
|
|
148
|
+
readonly id: string;
|
|
149
|
+
readonly description: string;
|
|
150
|
+
readonly defaultSeverity: LintSeverity;
|
|
151
|
+
readonly run: (doc: DocumentNode, ctx: LintContext) => LintResult[];
|
|
152
|
+
/**
|
|
153
|
+
* Optional: optimized visitor-based implementation.
|
|
154
|
+
* When present, the runner uses a single combined traversal for all visitor rules.
|
|
155
|
+
*/
|
|
156
|
+
readonly buildVisitor?: LintVisitorFactory;
|
|
157
|
+
};
|
|
158
|
+
type LintContext = {
|
|
159
|
+
readonly severityOverrides?: Readonly<Record<string, LintSeverity>>;
|
|
160
|
+
readonly disabledRules?: readonly string[];
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Generic completion endpoint interface.
|
|
164
|
+
* Accepts a prompt string and returns the model's text response.
|
|
165
|
+
* Implementations can wrap any LLM backend (OpenAI, Anthropic, Gemini Nano, etc.).
|
|
166
|
+
*/
|
|
167
|
+
type LlmCompletionEndpoint = (prompt: string, options?: {
|
|
168
|
+
signal?: AbortSignal;
|
|
169
|
+
}) => Promise<string>;
|
|
170
|
+
/**
|
|
171
|
+
* A document section extracted for LLM analysis.
|
|
172
|
+
* Sections are split by headings to stay within token limits.
|
|
173
|
+
*/
|
|
174
|
+
type LintSection = {
|
|
175
|
+
readonly heading: string;
|
|
176
|
+
readonly headingPath: readonly string[];
|
|
177
|
+
readonly text: string;
|
|
178
|
+
readonly blockRange: readonly [start: number, end: number];
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Async lint rule that uses an LLM endpoint for analysis.
|
|
182
|
+
*/
|
|
183
|
+
type LlmLintRule = {
|
|
184
|
+
readonly id: string;
|
|
185
|
+
readonly description: string;
|
|
186
|
+
readonly defaultSeverity: LintSeverity;
|
|
187
|
+
readonly buildPrompt: (section: LintSection) => string;
|
|
188
|
+
readonly parseResponse: (response: string, section: LintSection) => LintResult[];
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export { type AstNode as A, type BlockNode as B, type CodeMark as C, type DocumentNode as D, type HardBreakNode as H, type InlineNode as I, type JinjaIfBranch as J, type ListItemNode as L, type Mark as M, type NodePath as N, type OrderedListNode as O, type ParagraphNode as P, type ResourceTagType as R, type StrikethroughMark as S, type TableRowNode as T, type UnderlineMark as U, type BlockquoteNode as a, type BoldMark as b, type BulletListNode as c, type HeadingNode as d, type HorizontalRuleNode as e, type ItalicMark as f, type JinjaIfBlockNode as g, type JinjaIfInlineNode as h, type JumpPointNode as i, type LinkMark as j, type ResourceTagNode as k, type TableNode as l, type TableCellNode as m, type TextNode as n, type LintRule as o, type LintContext as p, type LintResult as q, type LlmLintRule as r, type LlmCompletionEndpoint as s, type LintSection as t, type JSONContent as u, JUMP_POINT_ID_PATTERN as v, type LintSeverity as w, RESOURCE_TAG_TYPES as x, fromProseMirrorJSON as y };
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { Schema, Node } from 'prosemirror-model';
|
|
2
|
+
import { Plugin, Command, EditorState, PluginKey } from 'prosemirror-state';
|
|
3
|
+
import { InputRule } from 'prosemirror-inputrules';
|
|
4
|
+
import { EditorView, NodeView, Decoration } from 'prosemirror-view';
|
|
5
|
+
import React$1, { ReactElement, RefObject } from 'react';
|
|
6
|
+
import { D as DocumentNode, A as AstNode, s as LlmCompletionEndpoint, I as InlineNode, B as BlockNode, u as JSONContent } from '../types-rEXLrfo_.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ProseMirror Schema for Actionbook documents.
|
|
10
|
+
*/
|
|
11
|
+
declare const actionbookSchema: Schema<"doc" | "paragraph" | "heading" | "bulletList" | "orderedList" | "listItem" | "blockquote" | "horizontalRule" | "table" | "tableRow" | "tableCell" | "tableHeader" | "text" | "inlineToolTag" | "jumpPoint" | "hardBreak", "bold" | "italic" | "underline" | "strikethrough" | "code" | "link" | "diffMark">;
|
|
12
|
+
|
|
13
|
+
type NodeViewFactory = (node: Node, view: EditorView, getPos: () => number | undefined) => NodeView;
|
|
14
|
+
interface ActionbookPlugin {
|
|
15
|
+
name: string;
|
|
16
|
+
plugins?: () => Plugin[];
|
|
17
|
+
inputRules?: () => InputRule[];
|
|
18
|
+
keymap?: () => Record<string, Command>;
|
|
19
|
+
nodeViews?: () => Record<string, NodeViewFactory>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Converts ActionbookPlugin[] into a flat array of PM Plugins.
|
|
24
|
+
* Merges inputRules and keymaps from all plugins.
|
|
25
|
+
*
|
|
26
|
+
* Priority order (ProseMirror tries plugins in array order — first wins):
|
|
27
|
+
* 1. Merged inputRules — highest priority for text-input transforms
|
|
28
|
+
* 2. Merged keymap — custom handlers (e.g. splitListItem on Enter)
|
|
29
|
+
* 3. Raw PM plugins — baseline behaviors (history, baseKeymap, etc.)
|
|
30
|
+
*
|
|
31
|
+
* Putting the merged keymap BEFORE raw plugins ensures that handlers like
|
|
32
|
+
* `splitListItem` on Enter take priority over `baseKeymap`'s `splitBlock`.
|
|
33
|
+
*/
|
|
34
|
+
declare function createPluginArray(plugins: ActionbookPlugin[]): {
|
|
35
|
+
pmPlugins: Plugin[];
|
|
36
|
+
nodeViews: Record<string, NodeViewFactory>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
interface ActionbookRendererProps {
|
|
40
|
+
doc: DocumentNode;
|
|
41
|
+
className?: string;
|
|
42
|
+
/** Custom node renderer for extension points */
|
|
43
|
+
renderNode?: (node: AstNode, defaultRender: () => ReactElement) => ReactElement;
|
|
44
|
+
}
|
|
45
|
+
declare function ActionbookRenderer({ doc, className, renderNode }: ActionbookRendererProps): ReactElement;
|
|
46
|
+
|
|
47
|
+
interface EditorShellProps {
|
|
48
|
+
className?: string;
|
|
49
|
+
style?: React$1.CSSProperties;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Contenteditable container for ProseMirror EditorView.
|
|
53
|
+
* Pass the ref from `useEditorView().viewRef` to mount the editor.
|
|
54
|
+
*/
|
|
55
|
+
declare const EditorShell: React$1.ForwardRefExoticComponent<EditorShellProps & React$1.RefAttributes<HTMLDivElement>>;
|
|
56
|
+
|
|
57
|
+
interface ReactNodeViewProps {
|
|
58
|
+
node: Node;
|
|
59
|
+
view: EditorView;
|
|
60
|
+
getPos: () => number | undefined;
|
|
61
|
+
selected: boolean;
|
|
62
|
+
/** Outer node decorations currently applied to this node. */
|
|
63
|
+
decorations: readonly Decoration[];
|
|
64
|
+
}
|
|
65
|
+
interface ReactNodeViewOptions {
|
|
66
|
+
/** The React component to render */
|
|
67
|
+
component: React.ComponentType<ReactNodeViewProps>;
|
|
68
|
+
/** Container tag name (default: 'span' for inline, 'div' for block) */
|
|
69
|
+
as?: string;
|
|
70
|
+
/** CSS class name for the container */
|
|
71
|
+
className?: string;
|
|
72
|
+
/** Whether this is an inline node (default: true) */
|
|
73
|
+
inline?: boolean;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Creates a NodeViewFactory that renders a React component as a PM NodeView.
|
|
77
|
+
*
|
|
78
|
+
* Usage:
|
|
79
|
+
* ```ts
|
|
80
|
+
* const jumpPointPlugin: ActionbookPlugin = {
|
|
81
|
+
* name: 'jumpPoint',
|
|
82
|
+
* nodeViews: () => ({
|
|
83
|
+
* jumpPoint: createReactNodeView({ component: JumpPointView }),
|
|
84
|
+
* }),
|
|
85
|
+
* };
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
declare function createReactNodeView(options: ReactNodeViewOptions): NodeViewFactory;
|
|
89
|
+
|
|
90
|
+
interface EditorViewConfig {
|
|
91
|
+
plugins?: ActionbookPlugin[];
|
|
92
|
+
content?: DocumentNode | null;
|
|
93
|
+
editable?: boolean;
|
|
94
|
+
onTransaction?: (state: EditorState) => void;
|
|
95
|
+
onContentChange?: (doc: DocumentNode) => void;
|
|
96
|
+
/**
|
|
97
|
+
* Optional LLM completion endpoint for LLM-based lint rules.
|
|
98
|
+
* When provided, the editor can run async lint rules that leverage LLM analysis.
|
|
99
|
+
* Accepts any backend: OpenAI, Anthropic, Gemini Nano, custom proxy, etc.
|
|
100
|
+
*/
|
|
101
|
+
llmCompletionEndpoint?: LlmCompletionEndpoint;
|
|
102
|
+
}
|
|
103
|
+
interface EditorViewHandle {
|
|
104
|
+
viewRef: RefObject<HTMLDivElement | null>;
|
|
105
|
+
view: EditorView | null;
|
|
106
|
+
getAST: () => DocumentNode | null;
|
|
107
|
+
getMarkdown: () => string;
|
|
108
|
+
setContent: (doc: DocumentNode) => void;
|
|
109
|
+
focus: () => void;
|
|
110
|
+
destroy: () => void;
|
|
111
|
+
}
|
|
112
|
+
declare function useEditorView(config: EditorViewConfig): EditorViewHandle;
|
|
113
|
+
|
|
114
|
+
declare function createInputRulesPlugin(): ActionbookPlugin;
|
|
115
|
+
|
|
116
|
+
declare function createKeymapPlugin(): ActionbookPlugin;
|
|
117
|
+
|
|
118
|
+
declare function createMarkdownClipboardPlugin(): ActionbookPlugin;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Jump-point adjacent decoration + edit plugins.
|
|
122
|
+
*
|
|
123
|
+
* Adjacent plugin: marks jumpPoint nodes near the cursor with a decoration
|
|
124
|
+
* so the NodeView can show `^id^` raw syntax when the cursor is beside them.
|
|
125
|
+
*
|
|
126
|
+
* Edit plugin: tracks a "raw range" after ArrowLeft explodes a jumpPoint atom
|
|
127
|
+
* to `^aa^` text. When the cursor moves outside that range the plugin
|
|
128
|
+
* auto-reconverts the text back to a jumpPoint node (if it still matches
|
|
129
|
+
* `^validId^` and the id has no duplicates elsewhere in the doc).
|
|
130
|
+
*
|
|
131
|
+
* Keymap:
|
|
132
|
+
* Backspace — explode atom to `^aa` (closing `^` consumed), cursor at end
|
|
133
|
+
* ArrowLeft — explode atom to `^aa^`, cursor before closing `^`
|
|
134
|
+
*/
|
|
135
|
+
|
|
136
|
+
/** Decoration spec set on a jumpPoint node when the cursor is adjacent. */
|
|
137
|
+
declare const JUMP_POINT_ADJACENT_SPEC: {
|
|
138
|
+
readonly jumpPointAdjacent: true;
|
|
139
|
+
};
|
|
140
|
+
declare function createJumpPointAdjacentPlugin(): ActionbookPlugin;
|
|
141
|
+
|
|
142
|
+
declare function createJumpPointNodeViewPlugin(): ActionbookPlugin;
|
|
143
|
+
|
|
144
|
+
declare function createInlineToolTagNodeViewPlugin(): ActionbookPlugin;
|
|
145
|
+
|
|
146
|
+
declare function createJinjaDecorationPlugin(): ActionbookPlugin;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Link editing plugin for the Actionbook editor.
|
|
150
|
+
*
|
|
151
|
+
* UX flow:
|
|
152
|
+
* 1. `[aa](#aa)` is rendered as a clickable "aa" link mark.
|
|
153
|
+
* 2. Pressing Backspace while cursor is inside or just after the link
|
|
154
|
+
* "explodes" it to raw markdown `[aa](#aa)` for editing.
|
|
155
|
+
* 3. While the cursor stays inside the raw text, normal editing applies.
|
|
156
|
+
* 4. When the cursor leaves the raw region:
|
|
157
|
+
* - If the text still matches `[label](href)` → reconverts to link mark.
|
|
158
|
+
* - Otherwise → stays as plain text (user intentionally broke the link).
|
|
159
|
+
* 5. Completing the markdown syntax by typing `)` triggers the input rule
|
|
160
|
+
* and immediately converts to a link mark.
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Creates the link editing `ActionbookPlugin`.
|
|
165
|
+
*
|
|
166
|
+
* Bundles:
|
|
167
|
+
* - Backspace → explode to raw markdown
|
|
168
|
+
* - `appendTransaction` auto-reconvert on cursor leave
|
|
169
|
+
* - Input rule for `[label](href)` → link mark
|
|
170
|
+
* - Click handler for internal `#anchor` links
|
|
171
|
+
*/
|
|
172
|
+
declare function createLinkPlugin(): ActionbookPlugin;
|
|
173
|
+
|
|
174
|
+
declare function createDragHandlePlugin(): ActionbookPlugin;
|
|
175
|
+
|
|
176
|
+
declare function createTodoNodeViewPlugin(): ActionbookPlugin;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Slash-command trigger plugin.
|
|
180
|
+
*
|
|
181
|
+
* Tracks whether the cursor is inside a `/query` pattern and exposes the
|
|
182
|
+
* active range + query string via `slashCommandKey` so that the
|
|
183
|
+
* `SlashCommandMenu` React component can read it and render the popup.
|
|
184
|
+
*
|
|
185
|
+
* The plugin also handles:
|
|
186
|
+
* - Escape → dismisses the menu (sets dismissed state so the same `/`
|
|
187
|
+
* position doesn't re-trigger until the user moves away and types `/` again)
|
|
188
|
+
* - ArrowUp / ArrowDown / Enter → consumed by the React component through
|
|
189
|
+
* a capture-phase keydown listener; the plugin just marks them as handled
|
|
190
|
+
* when the menu is active so ProseMirror doesn't move the cursor.
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
interface SlashCommandState {
|
|
194
|
+
/** Whether a slash trigger is currently active. */
|
|
195
|
+
active: boolean;
|
|
196
|
+
/** Document positions of the full `/query` text (inclusive). */
|
|
197
|
+
range: {
|
|
198
|
+
from: number;
|
|
199
|
+
to: number;
|
|
200
|
+
} | null;
|
|
201
|
+
/** Text typed after `/`, not including the slash itself. */
|
|
202
|
+
query: string;
|
|
203
|
+
}
|
|
204
|
+
declare const slashCommandKey: PluginKey<SlashCommandState>;
|
|
205
|
+
declare function createSlashCommandPlugin(): ActionbookPlugin;
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* SlashCommandMenu — injectable floating slash-command picker.
|
|
209
|
+
*
|
|
210
|
+
* Usage:
|
|
211
|
+
* ```tsx
|
|
212
|
+
* // Define items (inject from outside)
|
|
213
|
+
* const items: SlashCommandItem[] = [
|
|
214
|
+
* {
|
|
215
|
+
* id: 'heading1',
|
|
216
|
+
* title: 'Heading 1',
|
|
217
|
+
* description: 'Large section heading',
|
|
218
|
+
* icon: <span>H1</span>,
|
|
219
|
+
* command({ view, range }) {
|
|
220
|
+
* const { state, dispatch } = view;
|
|
221
|
+
* const tr = state.tr.delete(range.from, range.to);
|
|
222
|
+
* setBlockType(heading, { level: 1 })(
|
|
223
|
+
* state.apply(tr),
|
|
224
|
+
* (newTr) => dispatch(tr.steps.length ? tr : newTr),
|
|
225
|
+
* );
|
|
226
|
+
* },
|
|
227
|
+
* },
|
|
228
|
+
* ];
|
|
229
|
+
*
|
|
230
|
+
* // In your editor component:
|
|
231
|
+
* const [editorState, setEditorState] = useState<EditorState | null>(null);
|
|
232
|
+
* const handle = useEditorView({ ..., onTransaction: setEditorState });
|
|
233
|
+
*
|
|
234
|
+
* return (
|
|
235
|
+
* <>
|
|
236
|
+
* <EditorShell ref={handle.viewRef} />
|
|
237
|
+
* <SlashCommandMenu
|
|
238
|
+
* view={handle.view}
|
|
239
|
+
* editorState={editorState}
|
|
240
|
+
* items={items}
|
|
241
|
+
* />
|
|
242
|
+
* </>
|
|
243
|
+
* );
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* The `command` callback receives the editor view and the range of the
|
|
247
|
+
* `/query` trigger text. It is responsible for deleting the trigger and
|
|
248
|
+
* inserting whatever content it wants.
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
interface SlashCommandItem {
|
|
252
|
+
/** Unique key; falls back to `title` if omitted. */
|
|
253
|
+
id?: string;
|
|
254
|
+
title: string;
|
|
255
|
+
description?: string;
|
|
256
|
+
/** Optional icon node rendered to the left of the title. */
|
|
257
|
+
icon?: React$1.ReactNode;
|
|
258
|
+
/**
|
|
259
|
+
* Execute this command.
|
|
260
|
+
*
|
|
261
|
+
* @param view - Live EditorView; use `view.state` / `view.dispatch`.
|
|
262
|
+
* @param range - Positions of the full `/query` text in the document.
|
|
263
|
+
* Delete this range before inserting your content.
|
|
264
|
+
*/
|
|
265
|
+
command: (props: {
|
|
266
|
+
view: EditorView;
|
|
267
|
+
range: {
|
|
268
|
+
from: number;
|
|
269
|
+
to: number;
|
|
270
|
+
};
|
|
271
|
+
}) => void;
|
|
272
|
+
}
|
|
273
|
+
interface SlashCommandMenuProps {
|
|
274
|
+
view: EditorView | null;
|
|
275
|
+
editorState: EditorState | null;
|
|
276
|
+
/** Full item list. Filtered by the current query automatically. */
|
|
277
|
+
items: SlashCommandItem[];
|
|
278
|
+
}
|
|
279
|
+
declare function SlashCommandMenu({ view, editorState, items }: SlashCommandMenuProps): React$1.ReactPortal | null;
|
|
280
|
+
|
|
281
|
+
interface JinjaTreeViewProps {
|
|
282
|
+
doc: DocumentNode;
|
|
283
|
+
className?: string;
|
|
284
|
+
}
|
|
285
|
+
declare function JinjaTreeView({ doc, className }: JinjaTreeViewProps): ReactElement | null;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Floating UI for the Actionbook editor.
|
|
289
|
+
*
|
|
290
|
+
* Two interaction patterns:
|
|
291
|
+
*
|
|
292
|
+
* 1. SelectionToolbar — appears above a non-empty text selection.
|
|
293
|
+
* Buttons: Bold · Italic · Underline · Strikethrough · Code · Link · ─ · Block type ▾
|
|
294
|
+
*
|
|
295
|
+
* 2. EmptyParaHandle — appears to the left of an empty paragraph/heading
|
|
296
|
+
* after the cursor has dwelled there for DWELL_MS (600 ms).
|
|
297
|
+
* A small "+" button opens a block-type picker menu.
|
|
298
|
+
*
|
|
299
|
+
* Both are rendered into document.body via React portals so they never
|
|
300
|
+
* get clipped by overflow:hidden ancestors.
|
|
301
|
+
*/
|
|
302
|
+
|
|
303
|
+
interface FloatingMenuProps {
|
|
304
|
+
view: EditorView | null;
|
|
305
|
+
editorState: EditorState | null;
|
|
306
|
+
}
|
|
307
|
+
declare function FloatingMenu({ view, editorState }: FloatingMenuProps): React$1.ReactPortal | null;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Inline suggestion (ghost text) plugin.
|
|
311
|
+
*
|
|
312
|
+
* Debounces after the cursor stops moving, calls an LLM endpoint via a
|
|
313
|
+
* user-supplied `InlineSuggestProvider`, then renders the response as gray
|
|
314
|
+
* ghost text after the cursor using a ProseMirror widget decoration.
|
|
315
|
+
*
|
|
316
|
+
* Accepts:
|
|
317
|
+
* Tab → insert the suggestion at the cursor
|
|
318
|
+
* Escape → dismiss the suggestion
|
|
319
|
+
*
|
|
320
|
+
* Fully detachable: just omit `createInlineSuggestPlugin()` from the plugins
|
|
321
|
+
* array to disable the feature entirely.
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Cursor context passed to the provider's `buildPrompt`.
|
|
326
|
+
*/
|
|
327
|
+
type InlineSuggestContext = {
|
|
328
|
+
/** Text before the cursor — current paragraph plus up to 2 preceding paragraphs. */
|
|
329
|
+
textBefore: string;
|
|
330
|
+
/** Text in the current paragraph after the cursor (up to 100 chars). */
|
|
331
|
+
textAfter: string;
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* Extensible provider interface, analogous to `LlmLintRule`.
|
|
335
|
+
*
|
|
336
|
+
* Implement this to customize what gets sent to the LLM and how the
|
|
337
|
+
* response is parsed into a suggestion string.
|
|
338
|
+
*/
|
|
339
|
+
type InlineSuggestProvider = {
|
|
340
|
+
/** Unique identifier for this provider. */
|
|
341
|
+
id: string;
|
|
342
|
+
/**
|
|
343
|
+
* Build the prompt string sent to the LLM endpoint.
|
|
344
|
+
* Return an empty string to suppress the request entirely.
|
|
345
|
+
*/
|
|
346
|
+
buildPrompt: (ctx: InlineSuggestContext) => string;
|
|
347
|
+
/**
|
|
348
|
+
* Parse the raw LLM response into the text to insert at the cursor.
|
|
349
|
+
* Return an empty string to suppress the decoration.
|
|
350
|
+
* `ctx` is the same context that was passed to `buildPrompt`.
|
|
351
|
+
*/
|
|
352
|
+
parseResponse: (response: string, ctx: InlineSuggestContext) => string;
|
|
353
|
+
};
|
|
354
|
+
interface InlineSuggestState {
|
|
355
|
+
/** Ghost text currently shown, or null if nothing is active. */
|
|
356
|
+
suggestion: string | null;
|
|
357
|
+
/** Document position the suggestion is anchored to. */
|
|
358
|
+
anchorPos: number | null;
|
|
359
|
+
}
|
|
360
|
+
declare const inlineSuggestKey: PluginKey<InlineSuggestState>;
|
|
361
|
+
declare function createInlineSuggestPlugin(provider: InlineSuggestProvider, endpoint: LlmCompletionEndpoint, options?: {
|
|
362
|
+
/**
|
|
363
|
+
* Called when the user accepts a suggestion (Tab) or the cursor moves to
|
|
364
|
+
* a new context. Use this to pre-create a fresh completion session so it's
|
|
365
|
+
* ready by the time the next debounce fires.
|
|
366
|
+
*/
|
|
367
|
+
onContextChange?: () => void;
|
|
368
|
+
}): ActionbookPlugin;
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Convert ActionbookAST → ProseMirror JSONContent.
|
|
372
|
+
*/
|
|
373
|
+
|
|
374
|
+
declare function convertInline(node: InlineNode): JSONContent;
|
|
375
|
+
declare function convertBlock(node: BlockNode, listDepth?: number): JSONContent;
|
|
376
|
+
declare function toProseMirrorJSON(doc: DocumentNode): JSONContent;
|
|
377
|
+
/**
|
|
378
|
+
* Convert parseFragment() output (InlineNode[] | BlockNode[]) to PM JSONContent[].
|
|
379
|
+
* If nodes are inline, wraps them in a single paragraph.
|
|
380
|
+
*/
|
|
381
|
+
declare function astNodesToJSONContent(nodes: (InlineNode | BlockNode)[]): JSONContent[];
|
|
382
|
+
|
|
383
|
+
export { type ActionbookPlugin, ActionbookRenderer, type ActionbookRendererProps, EditorShell, type EditorShellProps, type EditorViewConfig, type EditorViewHandle, FloatingMenu, type FloatingMenuProps, type InlineSuggestContext, type InlineSuggestProvider, type InlineSuggestState, JUMP_POINT_ADJACENT_SPEC, JinjaTreeView, type JinjaTreeViewProps, type NodeViewFactory, type ReactNodeViewOptions, type ReactNodeViewProps, type SlashCommandItem, SlashCommandMenu, type SlashCommandMenuProps, type SlashCommandState, actionbookSchema, astNodesToJSONContent, convertBlock, convertInline, createDragHandlePlugin, createInlineSuggestPlugin, createInlineToolTagNodeViewPlugin, createInputRulesPlugin, createJinjaDecorationPlugin, createJumpPointAdjacentPlugin, createJumpPointNodeViewPlugin, createKeymapPlugin, createLinkPlugin, createMarkdownClipboardPlugin, createPluginArray, createReactNodeView, createSlashCommandPlugin, createTodoNodeViewPlugin, inlineSuggestKey, slashCommandKey, toProseMirrorJSON, useEditorView };
|