news 2026/9/15 19:42:16

Milkdown 历史记录插件 @milkdown/plugin-history 实战指南:撤销/重做、快捷键重映射与编程式命令调用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Milkdown 历史记录插件 @milkdown/plugin-history 实战指南:撤销/重做、快捷键重映射与编程式命令调用

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/⌘ + YShift + 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) }, }, })
  • UndoMod-z(macOS 上为⌘ + Z,Windows/Linux 上为Ctrl + Z);
  • RedoMod-yShift-Mod-z(macOS 的⌘ + Shift + Z、Windows/Linux 的Ctrl + YCtrl + Shift + Z均可用)。

Mod是 ProseMirror 的修饰键占位符,会根据运行平台自动解析为CmdCtrl,因此无需为不同系统分别写快捷键。

重映射快捷键

官方文档提供了通过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):

  1. $useKeymap(name, userKeymap)会把传入的快捷键配置写入一个名为historyKeymap$ctx切片,key即该切片的SliceType,供ctx.set/ctx.get读写;
  2. 同时生成一个$shortcut插件,在运行时ctx.get(keymapDef.key)读取用户的最新配置,并把它与userKeymap中定义好的command配对,展开成键名到命令的映射表;
  3. 重映射时只需提供shortcuts(可传字符串或字符串数组)与可选的prioritycommand无需重复声明,因为它始终取自插件源码中的原始定义。

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-utilscallCommand宏配合,通过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))

其中UndoundoCommand.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)),其中RedoredoCommand.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的全部导出,因此文档示例中historyhistoryKeymapUndo等符号均可从 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 19:41:20

FastapiAdmin定时任务实战:从cron配置到APScheduler调度原理

做后台管理系统的时候&#xff0c;定时任务是个绕不开的需求。数据同步、报表生成、临时文件清理、推送补发……这些活儿&#xff0c;总不能每次都让运维半夜爬起来手动点按钮&#xff0c;更不能把生产环境的数据更新寄托在一次手工点击上。FastapiAdmin 这种基于 FastAPI 的一…

作者头像 李华
网站建设 2026/9/15 19:39:58

深度学习与传统视觉融合的车型识别技术实践

1. 项目概述&#xff1a;当深度学习遇上传统视觉的车型识别双方案在智能交通和安防监控领域&#xff0c;车型识别一直是个既基础又关键的技术需求。去年我参与某市智慧停车项目时&#xff0c;就深刻体会到&#xff1a;单纯依赖传统计算机视觉方法&#xff08;如OpenCV&#xff…

作者头像 李华
网站建设 2026/9/15 19:38:37

C++ 棋盘格角点检测与相机标定:从 OpenCV 到亚像素精度

简介&#xff1a;面向使用OpenCV进行相机标定的C开发者&#xff0c;这份资源提供一套完整的棋盘格自动检测程序&#xff0c;将常见Matlab相机标定算法改写为C实现&#xff0c;可自动查找图像中最大棋盘格并识别所有独立棋盘格&#xff0c;配合OpenCV即可完成相机内参标定。资源…

作者头像 李华
网站建设 2026/9/15 19:33:27

WindTerm文件上传功能详解与高效使用技巧

1. WindTerm文件上传功能深度解析WindTerm作为一款现代化的终端工具&#xff0c;其文件传输功能在日常开发运维工作中扮演着重要角色。不同于传统FTP客户端或SCP命令的繁琐操作&#xff0c;WindTerm内置的图形化文件传输界面让跨系统文件交换变得直观高效。我在实际使用中发现&…

作者头像 李华
网站建设 2026/9/15 19:33:06

慢病AI系统如何用可计算数学模块根治大模型幻觉问题

1. 问题拆解&#xff1a;慢病场景下&#xff0c;大模型幻觉为什么这么致命先说结论&#xff1a;慢病AI陪伴系统&#xff0c;和通用聊天机器人最大的区别在于&#xff0c;它犯错的代价完全不是一个量级。普通问答答错了&#xff0c;用户笑一笑就过去了&#xff1b;慢病场景里&am…

作者头像 李华