Command System Overview¶
SciFlow's editor core exposes a command layer via the @sciflow/editor-core/commands entry point. The system centralises command registration and projects three complementary APIs:
- Immediate commands (
runner.commands) execute right away and dispatch their transaction automatically. - Flows (
runner.flow()) create a lazy pipeline; each call queues a command andrun()commits the accumulated changes. - Inspection helpers (
runner.available()) perform dry-runs. The returned object mirrors the command names and exposesflow()so you can compose non-dispatching pipelines.
Registering Commands¶
Commands are registered through registerCommands. Each entry is a factory that returns a ProseMirror-compatible command once invoked with its arguments.
Important:
registerCommandsnow scopes commands to the editor instance that is currently being configured. Call it from inside a feature'sinitialize()hook (or anywhere that is run whileEditor.create()is wiring features) so each editor only receives the commands it explicitly enables.
import type { Feature } from '@sciflow/editor-core';
import { Editor } from '@sciflow/editor-core';
import { registerCommands } from '@sciflow/editor-core/commands';
export const annotationCommandsFeature: Feature = {
name: 'annotation-commands',
initialize() {
registerCommands({
annotate: (color: string) => ({ state, dispatch, tr }) => {
if (!dispatch) {
// Just report whether the mark exists when called through `available()`.
return Boolean(state.schema.marks.highlight);
}
const { from, to } = tr.selection;
dispatch(
tr.addMark(from, to, state.schema.marks.highlight.create({ color })),
);
return true;
},
});
},
};
// Later, opt-in to the commands on any editor instance:
await Editor.create({
docId: 'doc-123',
sync,
features: [annotationCommandsFeature],
});
Commands become available immediately after registration on all three command surfaces, but only for the editor (and command runner) that executed the feature initialization above.
Using Commands¶
const runner = editor.getCommands();
// Immediate execution
runner.commands.focus();
runner.commands.insertText('Hello world');
// Flow-based pipelines
runner.flow().focus().toggleMark('strong').run();
// Capability checks with optional flow composition
if (runner.available().toggleMark('em')) {
runner.flow().toggleMark('em').run();
}
Inside a command you receive CommandProps, which include the editor, view, state, the mutable transaction, dispatch, and live accessors to commands, flow() and available().
Built-in Commands¶
Out of the box the core package registers:
| Command | Description |
|---|---|
focus |
Focus the current editor view. |
blur |
Remove focus from the editor view. |
insertText |
Insert text at the current selection. |
toggleMark |
Toggle a mark by name or MarkType, with extendEmptyMarkRange support. |
Feature packages can register additional commands by invoking registerCommands inside their initialize() hook; the editor automatically opens and closes the registration window while it instantiates each feature.