Milkdown 历史记录插件 @milkdown/plugin-history 实战指南:撤销/重做、快捷键重映射与编程式命令调用
【免费下载链接】milkdown🍼 Plugin driven WYSIWYG markdown editor framework.项目地址: https://gitcode.com/GitHub_Trending/mi/milkdown
@milkdown/plugin-history 是 Milkdown 编辑器框架内置的撤销(Undo)/ 重做(Redo)历史记录插件,它基于 ProseMirror 的prosemirror-history模块,为 WYSIWYG Markdown 编辑器提供开箱即用的操作历史能力。本指南以官方文档 docs/api/plugin-history.md 为主体,结合仓库源码深入讲解插件的组成结构、快捷键重映射方式、编程式命令调用方法及底层实现原理,读完即可在你的 Milkdown 编辑器中接入并定制完整的历史记录功能。
插件是什么
历史记录是富文本编辑器最基础、最不可或缺的能力之一:用户每次输入、删除、格式化或执行命令都会改变文档状态,历史记录允许用户通过Ctrl/⌘ + Z撤销误操作,通过Ctrl/⌘ + Y或Shift + Ctrl/⌘ + Z重做。Milkdown 没有自行实现这套复杂的算法,而是将prosemirror-history作为底层引擎(packages/prose/src/history.ts 直接export * from 'prosemirror-history'),在其之上封装成符合 Milkdown 插件体系的模块。
插件当前版本为7.22.1(见 packages/plugins/plugin-history/package.json),依赖@milkdown/core、@milkdown/ctx、@milkdown/prose与@milkdown/utils四个基础包。
安装与快速开始
在安装@milkdown/plugin-history后,通过Editor.make()的.use()链式调用接入即可,无需任何额外配置:
import { Editor } from '@milkdown/kit/core' import { history } from '@milkdown/kit/plugin/history' import { commonmark } from '@milkdown/kit/preset/commonmark' import { nord } from '@milkdown/theme-nord' Editor.make().use(nord).use(commonmark).use(history).create()注意两个细节:
- 推荐从
@milkdown/kit聚合包导入。仓库中 packages/kit/src/plugin/history.ts 只有一行export * from '@milkdown/plugin-history',即 kit 包负责统一转发,让用户只需引入一个入口; - 插件加载顺序不敏感,
history是独立模块,不依赖 commonmark 等 preset,但通常建议与 preset 一起使用,保证对段落、标题、列表等内容的编辑操作都能被记录。
插件的组成结构
官方文档通过@history、@historyProviderConfig、@historyProviderPlugin三个符号说明插件的组成。查看源码 packages/plugins/plugin-history/src/index.ts 可以确认,history实际上是一个由四个子插件扁平化组合而成的插件数组:
export const history: MilkdownPlugin[] = [ historyProviderConfig, historyProviderPlugin, historyKeymap, undoCommand, redoCommand, ].flat()每个子插件各司其职:
| 子插件 | 类型 | 职责 |
|---|---|---|
historyProviderConfig | $ctx | 声明历史插件的配置切片(Slice),承载可调参数 |
historyProviderPlugin | $prose | 将 ProseMirror 原生history插件挂载进 Milkdown 编辑器视图 |
historyKeymap | $useKeymap | 绑定默认的撤销/重做快捷键 |
undoCommand/redoCommand | $command | 注册可被编程式调用的撤销/重做命令 |
下面逐一展开。
historyProviderConfig:可配置项
historyProviderConfig是一个$ctx配置切片,类型定义如下(见 packages/plugins/plugin-history/src/index.ts):
export const historyProviderConfig = $ctx< { depth?: number; newGroupDelay?: number }, 'historyProviderConfig' >({}, 'historyProviderConfig')它暴露了两个可选参数,直接透传给 ProseMirror 的history()配置:
depth:历史记录栈最多保存的文档状态数量,超过后最早的记录会被丢弃,默认由prosemirror-history内部决定;newGroupDelay:将连续输入事件归并为同一历史分组的时间阈值(毫秒)。两次输入间隔小于该值会被视为同一步操作,从而合并为一次撤销;间隔更大则拆分为独立步骤。适当地调大该值可以让"输入一句话后按一次撤销"的体验更连贯。
默认值传入了空对象{},即不覆盖 ProseMirror 的默认行为。
historyProviderPlugin:桥接 ProseMirror
historyProviderPlugin是一个$prose插件,作用是把 ProseMirror 的历史插件实例注册进编辑器(见 packages/plugins/plugin-history/src/index.ts):
export const historyProviderPlugin = $prose((ctx) => prosemirrorHistory(ctx.get(historyProviderConfig.key)) )这里$prose是 Milkdown 提供的组合式 API,用来将任意的 ProseMirror 插件接入编辑器状态机;ctx.get(historyProviderConfig.key)则负责在插件初始化时读取用户通过配置切片设置的depth/newGroupDelay值。也就是说,上层配置通过$ctx与$prose的联动,最终成为 ProseMirror 历史插件的构造参数。
快捷键与重映射
插件默认绑定三组快捷键(见 packages/plugins/plugin-history/src/index.ts):
export const historyKeymap = $useKeymap('historyKeymap', { Undo: { shortcuts: 'Mod-z', command: (ctx) => { const commands = ctx.get(commandsCtx) return () => commands.call(undoCommand.key) }, }, Redo: { shortcuts: ['Mod-y', 'Shift-Mod-z'], command: (ctx) => { const commands = ctx.get(commandsCtx) return () => commands.call(redoCommand.key) }, }, })Undo:Mod-z(macOS 上为⌘ + Z,Windows/Linux 上为Ctrl + Z);Redo:Mod-y与Shift-Mod-z(macOS 的⌘ + Shift + Z、Windows/Linux 的Ctrl + Y与Ctrl + Shift + Z均可用)。
Mod是 ProseMirror 的修饰键占位符,会根据运行平台自动解析为Cmd或Ctrl,因此无需为不同系统分别写快捷键。
重映射快捷键
官方文档提供了通过historyKeymap.key重映射快捷键的完整示例:
import { history, historyKeymap } from '@milkdown/plugin-history' Editor.make() .config((ctx) => { ctx.set(historyKeymap.key, { // Remap to one shortcut. Undo: 'Mod-z', // Remap to multiple shortcuts. Redo: ['Mod-y', 'Shift-Mod-z'], }) }) .use(nord) .use(commonmark) .use(history) .create()这套机制的实现原理在$useKeymap组合式 API 中(packages/utils/src/composable/composed/$use-keymap.ts):
$useKeymap(name, userKeymap)会把传入的快捷键配置写入一个名为historyKeymap的$ctx切片,key即该切片的SliceType,供ctx.set/ctx.get读写;- 同时生成一个
$shortcut插件,在运行时ctx.get(keymapDef.key)读取用户的最新配置,并把它与userKeymap中定义好的command配对,展开成键名到命令的映射表; - 重映射时只需提供
shortcuts(可传字符串或字符串数组)与可选的priority,command无需重复声明,因为它始终取自插件源码中的原始定义。
priority可用于处理快捷键冲突:当多个插件绑定同一快捷键时,数值越大的键位越优先响应。
编程式调用命令
除了快捷键,历史记录还可以通过命令(Command)体系编程式触发。文档中@undoCommand与@redoCommand对应的源码定义(packages/plugins/plugin-history/src/index.ts):
export const undoCommand = $command('Undo', () => () => undo) export const redoCommand = $command('Redo', () => () => redo)$command是 Milkdown 的命令工厂(packages/utils/src/composable/$command.ts),它创建一个以Undo/Redo为 key 的命令插件,在CommandsReady时把命令注册进commandsCtx,并暴露run()与key两个属性;插件卸载时命令也会随之移除,避免内存泄漏。
文档给出的调用方式是与@milkdown/plugin-utils的callCommand宏配合,通过editor.action执行:
import { Undo, history } from '@milkdown/plugin-history' import { callCommand } from '@milkdown/plugin-utils' const editor = await Editor.make().use(/* ... */).use(history).create() editor.action(callCommand(Undo))其中Undo即undoCommand.key(命令 key)。callCommand的实现非常精简(packages/utils/src/macro/call-command.ts):它返回一个接收ctx的函数,内部调用ctx.get(commandsCtx).call(slice, payload)。因此editor.action(callCommand(Undo))等价于在编辑器上下文中执行commandsCtx.call('Undo')。
同样地,重做命令的完整写法为editor.action(callCommand(Redo)),其中Redo是redoCommand.key。这一模式非常适合用于:
- 自定义工具条上的撤销/重做按钮;
- 在插件逻辑中基于业务条件(如表单校验失败)主动回退文档;
- 与其他命令组合成复合操作流。
与编辑器其他机制的配合
从源码可以进一步确认插件与 Milkdown 核心机制的协作关系:
- 命令路由:快捷键回调通过
ctx.get(commandsCtx)拿到命令上下文,再commands.call(undoCommand.key)间接触发命令,而不是直接调用 ProseMirror 的undo,从而保证所有触发路径都经过统一的命令系统(便于被监听、拦截或二次封装); - ProseMirror 桥接:
undo/redo本身来自prosemirror-history(经 packages/prose/src/history.ts 转发),由$prose插件的状态字段挂载到编辑器视图中,历史记录与 Milkdown 的文档变更管线天然同步; - kit 聚合入口:通过
@milkdown/kit/plugin/history导入时,packages/kit/src/plugin/history.ts 会原样转发@milkdown/plugin-history的全部导出,因此文档示例中history、historyKeymap、Undo等符号均可从 kit 包获取。
小结
@milkdown/plugin-history 用极小的 API 面(一个history插件数组 + 一个可重映射的historyKeymap+ 两个可编程调用的命令)完成了编辑器的撤销/重做闭环。接入只需.use(history)一行;需要定制快捷键时用.config((ctx) => ctx.set(historyKeymap.key, ...));需要程序化控制时用editor.action(callCommand(Undo))。其背后是 Milkdown 组合式插件体系($ctx/$prose/$command/$useKeymap)与prosemirror-history的成熟实现结合,值得作为学习 Milkdown 插件开发范式的入门案例。若需深入,可继续阅读 packages/plugins/plugin-history/src/index.ts、packages/utils/src/composable/$command.ts 与 packages/utils/src/composable/composed/$use-keymap.ts 三份核心源码。
【免费下载链接】milkdown🍼 Plugin driven WYSIWYG markdown editor framework.项目地址: https://gitcode.com/GitHub_Trending/mi/milkdown
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考