The core module of milkdown - a plugin-driven WYSIWYG markdown Editor built on top of prosemirror and remark
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
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();
});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
};The context slice that provides access to the keymap manager.
/**
* Context slice containing the keymap manager instance
*/
const keymapCtx: SliceType<KeymapManager, 'keymap'>;Type alias for keymap item slice types.
/**
* Type for keymap key slice references
*/
type KeymapKey = SliceType<KeymapItem>;Keymap keys use ProseMirror's key syntax:
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
};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
});
});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.)
});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
});
});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;
}
});
}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;
}
}
});
});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);
});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);
}
});
});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();
};
};
};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();
}
});
}