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

keymap-management.mddocs/

Keymap Management

The keymap system provides keyboard shortcut handling with priority-based execution and seamless integration with ProseMirror's keymap system. It allows for flexible keyboard interaction management across the editor.

Capabilities

Keymap Manager

The central keymap management system that handles keyboard shortcut registration and execution.

/**
 * The keymap manager that handles keyboard shortcuts and key bindings
 */
class KeymapManager {
  /** Add a single keymap item with optional priority */
  add(keymap: KeymapItem): () => void;
  
  /** Add multiple keymap items as an object mapping keys to commands */
  addObjectKeymap(keymaps: Record<string, Command | KeymapItem>): () => void;
  
  /** Add the standard ProseMirror base keymap with Milkdown customizations */
  addBaseKeymap(): () => void;
  
  /** Get the current editor context */
  readonly ctx: Ctx | null;
}

Usage Examples:

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

editor.action((ctx) => {
  const keymap = ctx.get(keymapCtx);
  
  // Add a single keymap item
  const removeHandler = keymap.add({
    key: 'Mod-b',
    onRun: (ctx) => toggleBoldCommand(),
    priority: 100
  });
  
  // Add multiple keymaps at once
  const removeMultiple = keymap.addObjectKeymap({
    'Mod-i': toggleItalicCommand(),
    'Mod-k': {
      key: 'Mod-k',
      onRun: (ctx) => insertLinkCommand(),
      priority: 50
    }
  });
  
  // Add base keymap (backspace, enter, etc.)
  const removeBase = keymap.addBaseKeymap();
  
  // Remove keymaps when needed
  removeHandler();
  removeMultiple();
  removeBase();
});

Keymap Item

Interface defining a keyboard shortcut with its associated command and priority.

/**
 * Configuration item for a keyboard shortcut
 */
interface KeymapItem {
  /** The keyboard shortcut (e.g., 'Mod-b' for Ctrl/Cmd+B) */
  key: string;
  
  /** Function that returns the command to execute when key is pressed */
  onRun: (ctx: Ctx) => Command;
  
  /** Optional priority for command execution order (higher = first, default: 50) */
  priority?: number;
}

Usage Examples:

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

// High priority shortcut (executes first)
const highPriorityKeymap: KeymapItem = {
  key: 'Mod-s',
  onRun: (ctx) => {
    const commands = ctx.get(commandsCtx);
    return (state, dispatch) => {
      // Custom save logic with high priority
      return commands.call(saveDocumentKey);
    };
  },
  priority: 100
};

// Default priority shortcut
const defaultKeymap: KeymapItem = {
  key: 'Mod-Enter',
  onRun: (ctx) => (state, dispatch) => {
    // Insert hard break
    return insertHardBreakCommand()(state, dispatch);
  }
  // priority defaults to 50
};

// Low priority shortcut (executes last)
const lowPriorityKeymap: KeymapItem = {
  key: 'Tab',
  onRun: (ctx) => (state, dispatch) => {
    // Fallback tab behavior
    return insertTabCommand()(state, dispatch);
  },
  priority: 10
};

Keymap Context Slice

The context slice that provides access to the keymap manager.

/**
 * Context slice containing the keymap manager instance
 */
const keymapCtx: SliceType<KeymapManager, 'keymap'>;

Keymap Key Type

Type alias for keymap item slice types.

/**
 * Type for keymap key slice references
 */
type KeymapKey = SliceType<KeymapItem>;

Key Syntax

Keymap keys use ProseMirror's key syntax:

  • Mod: Maps to Cmd on Mac, Ctrl on other platforms
  • Shift: Shift modifier
  • Alt: Alt modifier (Option on Mac)
  • Ctrl: Always Ctrl (even on Mac)
  • Cmd: Always Cmd (Mac only)

Common Key Examples:

const keymaps = {
  'Mod-b': toggleBoldCommand(),           // Ctrl/Cmd + B
  'Mod-Shift-k': insertLinkCommand(),     // Ctrl/Cmd + Shift + K
  'Alt-ArrowUp': moveLineUpCommand(),     // Alt + Up Arrow
  'Ctrl-Space': showAutoCompleteCommand(), // Ctrl + Space
  'Enter': insertNewlineCommand(),        // Enter key
  'Backspace': deleteCharCommand(),       // Backspace
  'Mod-z': undoCommand(),                 // Ctrl/Cmd + Z
  'Mod-Shift-z': redoCommand(),          // Ctrl/Cmd + Shift + Z
};

Priority System

The keymap system supports priority-based execution for handling conflicting key bindings:

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

editor.action((ctx) => {
  const keymap = ctx.get(keymapCtx);
  
  // High priority - executes first
  keymap.add({
    key: 'Mod-Enter',
    onRun: (ctx) => customSubmitCommand(),
    priority: 100
  });
  
  // Medium priority - executes if high priority returns false
  keymap.add({
    key: 'Mod-Enter', 
    onRun: (ctx) => insertParagraphCommand(),
    priority: 50
  });
  
  // Low priority - fallback behavior
  keymap.add({
    key: 'Mod-Enter',
    onRun: (ctx) => defaultEnterCommand(),
    priority: 10
  });
});

Base Keymap Integration

Milkdown provides customized base keymap with enhanced backspace behavior:

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

editor.action((ctx) => {
  const keymap = ctx.get(keymapCtx);
  
  // Add base keymap with Milkdown enhancements
  const removeBase = keymap.addBaseKeymap();
  
  // The base keymap includes:
  // - Enhanced Backspace (undo input rules, delete selection, join blocks, select backward)
  // - Standard navigation (arrows, home, end, page up/down)
  // - Text selection (Shift + navigation)
  // - Standard editing (Enter, Delete, etc.)
});

Advanced Usage

Conditional Keymaps

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

editor.action((ctx) => {
  const keymap = ctx.get(keymapCtx);
  
  keymap.add({
    key: 'Tab',
    onRun: (ctx) => (state, dispatch, view) => {
      // Different behavior based on selection
      if (state.selection.empty) {
        return insertTabCommand()(state, dispatch, view);
      } else {
        return indentSelectionCommand()(state, dispatch, view);
      }
    },
    priority: 60
  });
});

Dynamic Keymap Management

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

let vimModeEnabled = false;
let vimKeymapRemover: (() => void) | null = null;

function toggleVimMode() {
  editor.action((ctx) => {
    const keymap = ctx.get(keymapCtx);
    
    if (vimModeEnabled) {
      // Disable vim mode
      if (vimKeymapRemover) {
        vimKeymapRemover();
        vimKeymapRemover = null;
      }
      vimModeEnabled = false;
    } else {
      // Enable vim mode
      vimKeymapRemover = keymap.addObjectKeymap({
        'h': moveLeftCommand(),
        'j': moveDownCommand(), 
        'k': moveUpCommand(),
        'l': moveRightCommand(),
        'i': enterInsertModeCommand(),
        'Escape': exitInsertModeCommand()
      });
      vimModeEnabled = true;
    }
  });
}

Context-Aware Commands

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

editor.action((ctx) => {
  const keymap = ctx.get(keymapCtx);
  
  keymap.add({
    key: 'Mod-/',
    onRun: (ctx) => (state, dispatch, view) => {
      const commands = ctx.get(commandsCtx);
      
      // Context-aware comment toggling
      const { $from } = state.selection;
      const node = $from.parent;
      
      if (node.type.name === 'code_block') {
        return commands.call(toggleCodeCommentKey);
      } else if (node.type.name === 'paragraph') {
        return commands.call(toggleLineCommentKey);
      } else {
        return false;
      }
    }
  });
});

Error Handling

Safe Keymap Registration

import { Editor, keymapCtx } from "@milkdown/core";
import { ctxCallOutOfScope } from "@milkdown/exception";

editor.action((ctx) => {
  const keymap = ctx.get(keymapCtx);
  
  const safeKeymap: KeymapItem = {
    key: 'Mod-x',
    onRun: (ctx) => (state, dispatch, view) => {
      try {
        // Attempt command execution
        return dangerousCommand()(state, dispatch, view);
      } catch (error) {
        console.error('Keymap command failed:', error);
        return false; // Prevent command from appearing to succeed
      }
    }
  };
  
  keymap.add(safeKeymap);
});

Context Validation

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

editor.action((ctx) => {
  const keymap = ctx.get(keymapCtx);
  
  keymap.add({
    key: 'Mod-p',
    onRun: (ctx) => (state, dispatch, view) => {
      // Validate context before execution
      if (!ctx || !dispatch || !view) {
        console.warn('Invalid context for print command');
        return false;
      }
      
      return printDocumentCommand()(state, dispatch, view);
    }
  });
});

Integration Patterns

Plugin Keymap Integration

import { MilkdownPlugin } from "@milkdown/ctx";
import { keymapCtx, KeymapReady } from "@milkdown/core";

const myPlugin: MilkdownPlugin = (ctx) => {
  ctx.inject(/* plugin context */);
  
  return async () => {
    await ctx.wait(KeymapReady);
    
    const keymap = ctx.get(keymapCtx);
    
    // Add plugin-specific keymaps
    const removeKeymaps = keymap.addObjectKeymap({
      'Mod-Shift-p': pluginSpecificCommand(),
      'Alt-p': anotherPluginCommand()
    });
    
    // Return cleanup function
    return () => {
      removeKeymaps();
    };
  };
};

Theme-Based Keymaps

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

function applyKeymapTheme(theme: 'standard' | 'vim' | 'emacs') {
  editor.action((ctx) => {
    const keymap = ctx.get(keymapCtx);
    
    switch (theme) {
      case 'vim':
        return keymap.addObjectKeymap({
          'h': moveLeftCommand(),
          'j': moveDownCommand(),
          'k': moveUpCommand(),
          'l': moveRightCommand()
        });
        
      case 'emacs':
        return keymap.addObjectKeymap({
          'Ctrl-f': moveRightCommand(),
          'Ctrl-b': moveLeftCommand(),
          'Ctrl-n': moveDownCommand(),
          'Ctrl-p': moveUpCommand()
        });
        
      default:
        return keymap.addBaseKeymap();
    }
  });
}

docs

command-system.md

context-system.md

editor-management.md

index.md

internal-plugins.md

keymap-management.md

tile.json