CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-milkdown--core

The core module of milkdown - a plugin-driven WYSIWYG markdown Editor built on top of prosemirror and remark

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

command-system.mddocs/

Command System

The command system provides type-safe command registration and execution for editor actions. Commands are the primary way to interact with and modify the editor state in Milkdown.

Capabilities

Command Manager

The central command management system that handles registration, retrieval, and execution of commands.

/**
 * The command manager that handles all editor commands
 */
class CommandManager {
  /** Register a command with the manager */
  create<T>(meta: CmdKey<T>, value: Cmd<T>): SliceType<Cmd<T>>;
  
  /** Get a registered command by its key */
  get<T extends CmdKey<any>>(slice: string): Cmd<InferParams<T>>;
  get<T>(slice: CmdKey<T>): Cmd<T>;
  
  /** Remove a command from the manager */
  remove<T extends CmdKey<any>>(slice: string): void;
  remove<T>(slice: CmdKey<T>): void;
  
  /** Execute a registered command with optional payload */
  call<T extends CmdKey<any>>(slice: string, payload?: InferParams<T>): boolean;
  call<T>(slice: CmdKey<T>, payload?: T): boolean;
  
  /** Execute an inline ProseMirror command directly */
  inline(command: Command): boolean;
  
  /** Create a command chain for sequential execution */
  chain(): CommandChain;
  
  /** Get the current editor context */
  readonly ctx: Ctx | null;
}

Usage Examples:

import { Editor, commandsCtx, createCmdKey } from "@milkdown/core";

// Create a command key
const toggleBoldKey = createCmdKey<void>('toggleBold');

// Register the command
editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  commands.create(toggleBoldKey, () => (state, dispatch) => {
    // ProseMirror command implementation
    const { schema } = state;
    const markType = schema.marks.strong;
    return toggleMark(markType)(state, dispatch);
  });
});

// Execute the command
editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  const success = commands.call(toggleBoldKey);
  console.log('Command executed:', success);
});

Command Chain

Interface for chaining multiple commands together with sequential execution.

/**
 * A chainable command helper for executing multiple commands in sequence
 */
interface CommandChain {
  /** Execute all commands in the chain */
  run(): boolean;
  
  /** Add a ProseMirror command directly to the chain */
  inline(command: Command): CommandChain;
  
  /** Add a registered command to the chain with optional payload */
  pipe<T extends CmdKey<any>>(slice: string, payload?: InferParams<T>): CommandChain;
  pipe<T>(slice: CmdKey<T>, payload?: T): CommandChain;
  pipe(slice: string | CmdKey<any>, payload?: any): CommandChain;
}

Usage Examples:

import { Editor, commandsCtx } from "@milkdown/core";

editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  // Chain multiple commands
  const success = commands.chain()
    .pipe(toggleBoldKey)
    .inline((state, dispatch) => {
      // Inline command
      return selectAll(state, dispatch);
    })
    .pipe(insertTextKey, 'Hello World')
    .run();
    
  console.log('Chain executed:', success);
});

Command Key Creation

Function to create type-safe command keys for command registration.

/**
 * Create a command key for type-safe command registration
 * @param key - Optional identifier for the command key
 * @returns A typed command key slice
 */
function createCmdKey<T = undefined>(key?: string): CmdKey<T>;

Usage Examples:

import { createCmdKey } from "@milkdown/core";

// Command with no payload
const saveDocumentKey = createCmdKey<void>('saveDocument');

// Command with string payload
const insertTextKey = createCmdKey<string>('insertText');

// Command with complex payload
interface FormatOptions {
  bold?: boolean;
  italic?: boolean;
  color?: string;
}
const formatTextKey = createCmdKey<FormatOptions>('formatText');

// Usage
editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  commands.call(saveDocumentKey);
  commands.call(insertTextKey, 'Hello World');
  commands.call(formatTextKey, { bold: true, color: 'red' });
});

Command Context Slice

The context slice that provides access to the command manager.

/**
 * Context slice containing the command manager instance
 */
const commandsCtx: SliceType<CommandManager, 'commands'>;

Command Types

Core Command Types

/**
 * Command function type that returns a ProseMirror command
 * @param payload - Optional payload data for the command
 * @returns ProseMirror Command function
 */
type Cmd<T = undefined> = (payload?: T) => Command;

/**
 * Command key type for type-safe command registration
 */
type CmdKey<T = undefined> = SliceType<Cmd<T>>;

/**
 * Type helper to infer command payload types from CmdKey
 */
type InferParams<T> = T extends CmdKey<infer U> ? U : never;

Advanced Usage

Custom Command Implementation

import { Editor, commandsCtx, createCmdKey } from "@milkdown/core";
import { setBlockType } from "@milkdown/prose/commands";

// Create a custom heading command
const setHeadingKey = createCmdKey<{ level: number }>('setHeading');

editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  commands.create(setHeadingKey, ({ level }) => (state, dispatch) => {
    const { schema } = state;
    const headingType = schema.nodes.heading;
    
    if (!headingType) return false;
    
    return setBlockType(headingType, { level })(state, dispatch);
  });
});

// Use the command
editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  commands.call(setHeadingKey, { level: 2 });
});

Command Composition

import { Editor, commandsCtx, createCmdKey } from "@milkdown/core";

// Compose multiple operations into a single command
const formatParagraphKey = createCmdKey<{
  bold?: boolean;
  italic?: boolean;
  align?: 'left' | 'center' | 'right';
}>('formatParagraph');

editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  commands.create(formatParagraphKey, (options) => (state, dispatch, view) => {
    // Chain multiple formatting commands
    return commands.chain()
      .pipe(toggleBoldKey)
      .pipe(toggleItalicKey)
      .pipe(setAlignmentKey, options.align || 'left')
      .run();
  });
});

Conditional Command Execution

import { Editor, commandsCtx } from "@milkdown/core";

editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  // Execute command only if selection is not empty
  const success = commands.inline((state, dispatch) => {
    if (state.selection.empty) {
      console.log('Cannot format empty selection');
      return false;
    }
    
    return commands.call(formatSelectionKey, { bold: true });
  });
});

Error Handling

Command Execution Errors

import { Editor, commandsCtx } from "@milkdown/core";
import { callCommandBeforeEditorView } from "@milkdown/exception";

editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  try {
    const success = commands.call(someCommandKey, payload);
    if (!success) {
      console.warn('Command execution failed - command returned false');
    }
  } catch (error) {
    if (error === callCommandBeforeEditorView()) {
      console.error('Cannot execute command before editor view is ready');
    } else {
      console.error('Command execution error:', error);
    }
  }
});

Command Registration Safety

import { Editor, commandsCtx, createCmdKey } from "@milkdown/core";

const myCommandKey = createCmdKey<string>('myCommand');

editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  // Check if command already exists before registering
  try {
    const existing = commands.get(myCommandKey);
    console.log('Command already registered');
  } catch {
    // Command doesn't exist, safe to register
    commands.create(myCommandKey, (text) => (state, dispatch) => {
      // Command implementation
      return true;
    });
  }
});

Integration with ProseMirror

The command system seamlessly integrates with ProseMirror's command system:

import { Editor, commandsCtx } from "@milkdown/core";
import { 
  toggleMark, 
  wrapIn, 
  setBlockType,
  chainCommands,
  exitCode
} from "@milkdown/prose/commands";

editor.action((ctx) => {
  const commands = ctx.get(commandsCtx);
  
  // Use ProseMirror commands directly
  commands.inline(toggleMark(schema.marks.strong));
  
  // Chain ProseMirror commands
  commands.inline(chainCommands(
    exitCode,
    (state, dispatch) => {
      // Custom logic
      return true;
    }
  ));
});

docs

command-system.md

context-system.md

editor-management.md

index.md

internal-plugins.md

keymap-management.md

tile.json