背景
原版 wangEditor 已经很久不维护了,对中文输入法(IME)组合输入的支持有历史遗留问题。具体表现为:当输入的候选拼音占位字符和最终确定的汉字在文本节点里没有正确合并时,内容就没有即时渲染;直到下一次编辑动作(回车/删除)触发了重渲染,汉字才"迟到"地显示出来。为解决对应问题,将其迁移到wangEditor-next版本。

从 wangEditor 迁到 wangEditor-next:先解决输入法老毛病,又踩了工具栏按钮集体"置灰"的坑
修复一个 bug 时发现,比较容易的竟然是那个看起来更吓人的输入法问题。
最近给项目里的文本编辑器做了一次大迁移:从原版 wangEditor 升级到社区维护的 @wangeditor-next/editor。本来只是想修一个中文输入法的老毛病,结果迁移完成、输入法问题解决后,又冒出来一个更隐蔽的工具栏 bug。两个问题都值得记录,这一篇把完整过程写下来。

一、起因:原版 wangEditor 的输入法老毛病
一开始项目用的是原版 @wangeditor/editor,一直存在一个很烦人的中文输入法 bug:
在行首输入中文不显示,要敲回车或删除后才突然冒出来。
这个毛病的根源在于,原版 wangEditor 已经很久不维护了,对中文输入法(IME)组合输入的支持有历史遗留问题。具体表现为:当输入的候选拼音占位字符和最终确定的汉字在文本节点里没有正确合并时,内容就没有即时渲染;直到下一次编辑动作(回车/删除)触发了重渲染,汉字才"迟到"地显示出来。
这个问题在输入框类组件里属于"能忍但很难受"的那种,尤其对中文用户几乎是天天都要撞上。原版不再更新,靠打补丁治标不治本,于是我们把目光投向社区维护的分支------@wangeditor-next/editor。维护者明确确认:这个 IME 问题在新版本里已经修复。
二、迁移:换包名,API 几乎不动
@wangeditor-next 是原版 wangEditor 的社区分支,API 设计基本一致,迁移比想象中顺利:
- 包名:
@wangeditor/editor→@wangeditor-next/editor,Vue 封装@wangeditor/editor-for-vue→@wangeditor-next/editor-for-vue - 版本对齐:
editor和editor-for-vue都锁定到6.4.2 - 代码改动:改
import来源,把样式引入路径从@wangeditor/editor/dist/css/style.css换成@wangeditor-next/editor/dist/css/style.css - 组件用法:
Editor、Toolbar、createEditor/createToolbar等 API 完全兼容,toolbarConfig、editorConfig、事件回调都照旧
迁移完成后验证,果然------行首中文输入不显示的问题解决了。正当我以为收工时,紧接测试发现了一个新的工具栏问题。
三、新坑:工具栏按钮集体"置灰"、右键却正常
问题:
迁移后,工具栏上的 有序列表 / 无序列表 / 待办 / 表情 按钮置灰、点不了;但右键(悬浮菜单)里的无序列表却能用。
现象可以拆成三条看起来互相矛盾的关键线索:
- 有序列表
numberedList、无序列表bulletedList、待办todo、表情emotion:工具栏上置灰。 - 加粗
bold、颜色color等同批控件:看起来正常。 - 右键菜单 里的无序列表:能用。
第一反应当然是"这几个按钮的模块没注册"。因为 wangEditor 的机制是:一个菜单按钮 = 一个注册在 key 下的"菜单工厂",工具栏渲染时找不到工厂就渲染不出来。
但「加粗能用、列表不能用、右键能用」这三条摆在一起,立刻否掉了"模块完全没注册"的猜测------它们很多都在同一个模块(basic-modules)里。如果是注册问题,加粗也该一起挂掉。于是转向怀疑:是不是这四个按钮的 isDisabled 判断逻辑在迁移后出错?
四、一度跑偏:盯着 isDisabled 猜了很久
wangEditor 里每个菜单都有 isDisabled(editor),返回 true 就置灰。列表/待办类的逻辑通常是这样:
ts
isDisabled(editor) {
// 选中块为空、或光标在 void/pre/code/table 节点里 → 禁用
return !selection || getTopLevelSelectedBlocks(editor).length === 0 || ...;
}
我一度把注意力全放在"为什么 bold 的 isDisabled 返回 false、而 bulletedList 返回 true"上,怀疑迁移后光标选择(selection)状态没正常建立。这方向也走了挺深,但始终说服不了自己------它们的底层判断逻辑几乎一样,没理由加粗能拿到正常 selection、列表就拿不到。
就在纠结 selection 的时候,用户贴了一条控制台报错,直接把方向拉了回来。
五、控制台报错才是根子
Uncaught (in promise) Error: Not found menu item factory by key 'link'
注意报错的 key 是 link,而不是列表/待办/表情里的任何一个。但正是这个"看起来不相关的 key",把所有按钮一起打趴下了。
顺着这条报错,我去 node_modules/@wangeditor-next/editor 的分发包 dist/index.mjs(打包产物)里一查,发现了迁移时最容易被忽略的坑:
wangEditor-next 悄悄改了一堆菜单 key
我在打包产物里逐个搜索 key:"xxx" 这种"菜单工厂注册"声明,把白名单里的 key 全过了一遍,发现有三个 key 在原版里存在、在 next 里根本不存在:
| 白名单里写的 key | 结果 | 正确的 key |
|---|---|---|
link |
❌ 无此工厂 | insertLink |
image(图片组内) |
❌ 无此工厂 | insertImage |
table |
❌ 无此工厂 | insertTable |
为什么一个坏 key 会让一堆好按钮置灰?
这是整个问题最核心、也最反直觉的地方。
wangEditor-next 的 Toolbar 拿到 toolbarKeys 后,会逐个把 key 解析成已注册的菜单工厂 。只要中间遇到一个(哪怕一个)解析不了的 key,就会抛出 Not found menu item factory by key '...'。
关键在"(in promise) "这几个字:这个错误是在工具栏的异步/响应式构建过程中抛出的 。一旦抛错,工具栏的初始化流程就被打断------本该继续往下算的那些按钮的可用状态(disabled/active),全部停在默认的灰那儿更新不出来。
所以白名单里那几个无效 key(link、image、table)就像三颗老鼠屎坏了一锅汤:自己抛错不说,还把 bulletedList、numberedList、todo、emotion 这些本来完全正常的按钮一起拖进灰色状态。
为什么右键/悬浮菜单却一直正常?
这正好解释了那条看似矛盾的现象:
- 工具栏 :走
toolbarKeys→ 解析成工厂这套"先校验、会抛错"的逻辑。坏 key 一来,整个工具栏初始化被打断。 - 悬浮/右键菜单(hoverbar) :根本不经过
toolbarKeys这套校验,而是在选中内容时动态弹出。菜单工厂和插件都是好的,所以一直能用。
也就是说:问题从来不是列表/待办/表情这三个按钮坏了,而是工具栏因为别的坏 key 崩了,把他们都拖下水了。
六、修复:把无效 key 全部改掉
直接在白名单里把三个无效 key 改成有效 key 即可,一行都没多动:
ts
// frontend/src/components/RichEditor.vue
function buildToolbarKeys(): IToolbarConfig['toolbarKeys'] {
return [
'headerSelect',
'blockquote',
'bold',
'underline',
'italic',
'through',
'color',
'bgColor',
'clearStyle',
'fontFamily',
'fontSize',
'bulletedList',
'numberedList',
'todo',
'emotion',
'insertLink', // 原先是 'link'
{
key: 'group-image',
title: '图片',
iconSvg: '',
menuKeys: ['insertImage', 'viewImageLink', 'deleteImage', 'editImage'], // 原先是 'image,...'
},
'insertTable', // 原先是 'table'
'codeBlock',
'code',
'divider',
'undo',
'redo',
]
}
修完后,我把所有白名单 key 在打包产物里逐一核对抗有效性,确保不再有漏网的坏 key:
text
headerSelect OK · blockquote OK · bold OK · ... · bulletedList OK · todo OK
emotion OK · insertLink OK · insertImage OK · ... · insertTable OK · ...
全部 OK。Vite dev 是热更新的,用户刷新页面后,控制台不再报 Not found menu item factory,四个按钮恢复可点,问题解决。
七、复盘:两轮踩坑,四条经验
- "菜单可用状态"和"菜单是否注册"要先分开 。
isDisabled返回置灰,和"工厂根本没注册"是两码事。前者靠改逻辑解决,后者靠修 key/注册解决。别一上来就怀疑自己的判断逻辑。 - 看控制台报错,别看感觉 。我盯着"为什么加粗能行、列表不行"猜了很久,一个
link的报错就把方向拉回来了。遇到可疑 bug,先把运行时异常翻出来。 - 换库后,菜单 key 是会变的 。
@wangeditor/editor→@wangeditor-next/editor不能只对 API,菜单注册的 key 名(link→insertLink、image→insertImage、table→insertTable)也会改。最稳的核对方式:直接去打包产物里搜key:"你用的key",看能否命中工厂。 - 一个坏 key 会让"看起来无关"的好按钮一起挂掉。因为工具栏构建是一串整体流程,中间抛错会打断后续所有按钮可用状态的更新。所以修的时候,要把白名单里所有 key 一次性全查一遍,而不是修一个再试一次。
附:快速核对 wangEditor-next 菜单 key 是否有效
遇到类似问题,可以在安装目录里快速核对任意 key 是否存在:
powershell
$f = 'node_modules/@wangeditor-next/editor/dist/index.mjs'
$line = Get-Content $f -Raw
foreach ($k in @('link','insertLink','table','insertTable')) {
$p = $line.IndexOf('key:"' + $k + '"')
Write-Output "{0,-12} -> {1}" -f $k, ($(if($p -ge 0){'OK @' + $p}else{'INVALID'}))
}
OK 表示这个 key 有对应菜单工厂,INVALID 则表示换成正确的 key(通常就是在前面加 insert)。
这次迁移告诉我们:修输入法这种"看着难"的问题往往挺直接,而"工具栏按钮置灰"这种"看着简单"的问题背后反而藏着坑。 别背锅给好按钮,先去控制台里找那个抛错的坏 key。