Files
llm-in-text/milkdown-docs/guide/commands.md
ydy0615 d9ab341223 Add documentation for using Milkdown with various frameworks
- Created a new document for using components in Milkdown.
- Added a guide for using plugins in Milkdown, including toggling plugins programmatically and listing official plugins.
- Introduced a recipe for integrating Milkdown with Angular, including installation steps and component creation.
- Added a recipe for using Milkdown with Next.js, detailing installation and component setup.
- Created a guide for integrating Milkdown with NuxtJS, including installation and component creation.
- Added a comprehensive guide for using Milkdown with React, covering both Crepe and core Milkdown usage.
- Introduced a recipe for SolidJS integration with Milkdown, including installation and component creation.
- Added a guide for using Milkdown with Svelte, detailing installation and component setup.
- Created a comprehensive guide for integrating Milkdown with Vue, covering both Crepe and core Milkdown usage.
- Added a recipe for using Milkdown with Vue2, including installation and component creation.
2026-01-17 14:18:08 +08:00

5.9 KiB

Commands

Commands are a powerful way to programmatically modify editor content. The command system in Milkdown provides a flexible and type-safe way to create, manage, and execute commands.

Command Manager


The command manager is the central place for handling all editor commands. It provides methods to:

  • Register new commands
  • Execute commands
  • Chain multiple commands together
  • Handle command arguments

Run a Command


You can execute commands using the command manager through the editor's action system:

import { Editor, commandsCtx } from "@milkdown/kit/core";
import {
  commonmark,
  toggleEmphasisCommand,
} from "@milkdown/kit/preset/commonmark";

async function setup() {
  const editor = await Editor.make().use(commonmark).create();

  const toggleItalic = () =>
    editor.action((ctx) => {
      // get command manager
      const commandManager = ctx.get(commandsCtx);

      // call command
      commandManager.call(toggleEmphasisCommand.key);
    });

  // get markdown string:
  $button.onClick = toggleItalic;
}

Command Chaining


You can chain multiple commands together using the command manager's chain method. Commands in the chain will be executed in order until one of them returns true:

import { Editor, commandsCtx } from "@milkdown/kit/core";
import {
  commonmark,
  toggleEmphasisCommand,
  toggleStrongCommand,
} from "@milkdown/kit/preset/commonmark";

const editor = await Editor.make().use(commonmark).create();

editor.action((ctx) => {
  const commandManager = ctx.get(commandsCtx);

  // Chain multiple commands
  commandManager
    .chain()
    .pipe(toggleEmphasisCommand.key) // Try to toggle emphasis
    .pipe(toggleStrongCommand.key) // If emphasis fails, try to toggle strong
    .run();
});

You can also mix inline commands with registered commands:

import { chainCommands } from "@milkdown/prose/commands";

editor.action((ctx) => {
  const commandManager = ctx.get(commandsCtx);

  commandManager
    .chain()
    .inline(someInlineCommand) // Add an inline command
    .pipe(toggleEmphasisCommand.key) // Add a registered command
    .run();
});

Create a Command


To create a command, use the $command utility from @milkdown/utils. Commands should be prosemirror commands.

Example: Command without argument

import { Editor } from "@milkdown/kit/core";
import { blockquoteSchema } from "@milkdown/kit/preset/commonmark";
import { wrapIn } from "@milkdown/kit/prose/commands";
import { $command, callCommand } from "@milkdown/kit/utils";

const wrapInBlockquoteCommand = $command(
  "WrapInBlockquote",
  (ctx) => () => wrapIn(blockquoteSchema.type(ctx)),
);

// register the command when creating the editor
const editor = Editor().make().use(wrapInBlockquoteCommand).create();

// call command
editor.action(callCommand(wrapInBlockquoteCommand.key));

Example: Command with argument

Commands can accept arguments of any type:

import { headingSchema } from "@milkdown/kit/preset/commonmark";
import { setBlockType } from "@milkdown/kit/prose/commands";
import { $command, callCommand } from "@milkdown/kit/utils";

// use number as the type of argument
export const WrapInHeading = createCmdKey<number>();
const wrapInHeadingCommand = $command(
  "WrapInHeading",
  (ctx) =>
    (level = 1) =>
      setBlockType(headingSchema.type(ctx), { level }),
);

// call command
editor.action(callCommand(wrapInHeadingCommand.key)); // turn to h1 by default
editor.action(callCommand(wrapInHeadingCommand.key, 2)); // turn to h2

Example: Command with Multiple Arguments

interface TableConfig {
  rows: number;
  cols: number;
  withHeader: boolean;
}

const insertTableCommand = $command(
  "InsertTable",
  (ctx) => (config: TableConfig) => {
    // Implementation for inserting a table
    return (state, dispatch) => {
      // ... table insertion logic
      return true;
    };
  },
);

// Usage
editor.action(
  callCommand(insertTableCommand.key, {
    rows: 3,
    cols: 3,
    withHeader: true,
  }),
);

Best Practices


  1. Command Naming

    • Use clear, descriptive names
    • Follow the pattern: [Action][Target]Command
    • Example: toggleEmphasisCommand, insertTableCommand
  2. Command Organization

    • Group related commands together
    • Use namespaces for command keys
    • Keep commands focused and single-purpose
  3. Error Handling

    • Always check if the command can be executed
    • Return false if the command cannot be executed
    • Handle edge cases gracefully
  4. Performance

    • Keep commands lightweight
    • Avoid unnecessary state updates
    • Use command chaining for complex operations
  5. Type Safety

    • Use TypeScript for command arguments
    • Define clear interfaces for command payloads
    • Use generics for type-safe command keys

Common Patterns


Toggle Commands

const toggleCommand = $command(
  "ToggleFeature",
  (ctx) => () => (state, dispatch) => {
    const isActive = checkIfActive(state);
    return isActive
      ? removeFeature(state, dispatch)
      : addFeature(state, dispatch);
  },
);

Insert Commands

const insertCommand = $command(
  "InsertContent",
  (ctx) => (content: string) => (state, dispatch) => {
    const { selection } = state;
    if (!selection) return false;

    const tr = state.tr.insertText(content, selection.from);
    dispatch?.(tr);
    return true;
  },
);

Transform Commands

const transformCommand = $command(
  "TransformContent",
  (ctx) => (transform: (node: ProseNode) => ProseNode) => (state, dispatch) => {
    const { selection } = state;
    if (!selection) return false;

    const tr = state.tr.replaceWith(
      selection.from,
      selection.to,
      transform(state.doc.nodeAt(selection.from)!),
    );
    dispatch?.(tr);
    return true;
  },
);