
背景原版 wangEditor 已經(jīng)很久不維護(hù)了對(duì)中文輸入法IME組合輸入的支持有歷史遺留問(wèn)題。具體表現(xiàn)為當(dāng)輸入的候選拼音占位字符和最終確定的漢字在文本節(jié)點(diǎn)里沒(méi)有正確合并時(shí)內(nèi)容就沒(méi)有即時(shí)渲染直到下一次編輯動(dòng)作回車/刪除觸發(fā)了重渲染漢字才遲到地顯示出來(lái)。為解決對(duì)應(yīng)問(wèn)題將其遷移到wangEditor-next版本。從 wangEditor 遷到 wangEditor-next先解決輸入法老毛病又踩了工具欄按鈕集體置灰的坑修復(fù)一個(gè) bug 時(shí)發(fā)現(xiàn)比較容易的竟然是那個(gè)看起來(lái)更嚇人的輸入法問(wèn)題。最近給項(xiàng)目里的文本編輯器做了一次大遷移從原版wangEditor升級(jí)到社區(qū)維護(hù)的wangeditor-next/editor。本來(lái)只是想修一個(gè)中文輸入法的老毛病結(jié)果遷移完成、輸入法問(wèn)題解決后又冒出來(lái)一個(gè)更隱蔽的工具欄 bug。兩個(gè)問(wèn)題都值得記錄這一篇把完整過(guò)程寫(xiě)下來(lái)。一、起因原版 wangEditor 的輸入法老毛病一開(kāi)始項(xiàng)目用的是原版wangeditor/editor一直存在一個(gè)很煩人的中文輸入法 bug在行首輸入中文不顯示要敲回車或刪除后才突然冒出來(lái)。這個(gè)毛病的根源在于原版 wangEditor 已經(jīng)很久不維護(hù)了對(duì)中文輸入法IME組合輸入的支持有歷史遺留問(wèn)題。具體表現(xiàn)為當(dāng)輸入的候選拼音占位字符和最終確定的漢字在文本節(jié)點(diǎn)里沒(méi)有正確合并時(shí)內(nèi)容就沒(méi)有即時(shí)渲染直到下一次編輯動(dòng)作回車/刪除觸發(fā)了重渲染漢字才遲到地顯示出來(lái)。這個(gè)問(wèn)題在輸入框類組件里屬于能忍但很難受的那種尤其對(duì)中文用戶幾乎是天天都要撞上。原版不再更新靠打補(bǔ)丁治標(biāo)不治本于是我們把目光投向社區(qū)維護(hù)的分支——wangeditor-next/editor。維護(hù)者明確確認(rèn)這個(gè) IME 問(wèn)題在新版本里已經(jīng)修復(fù)。二、遷移換包名API 幾乎不動(dòng)wangeditor-next是原版 wangEditor 的社區(qū)分支API 設(shè)計(jì)基本一致遷移比想象中順利包名wangeditor/editor→wangeditor-next/editorVue 封裝wangeditor/editor-for-vue→wangeditor-next/editor-for-vue版本對(duì)齊editor和editor-for-vue都鎖定到6.4.2代碼改動(dòng)改import來(lái)源把樣式引入路徑從wangeditor/editor/dist/css/style.css換成wangeditor-next/editor/dist/css/style.css組件用法Editor、Toolbar、createEditor/createToolbar等 API 完全兼容toolbarConfig、editorConfig、事件回調(diào)都照舊遷移完成后驗(yàn)證果然——行首中文輸入不顯示的問(wèn)題解決了。正當(dāng)我以為收工時(shí)緊接測(cè)試發(fā)現(xiàn)了一個(gè)新的工具欄問(wèn)題。三、新坑工具欄按鈕集體置灰、右鍵卻正常問(wèn)題遷移后工具欄上的有序列表 / 無(wú)序列表 / 待辦 / 表情按鈕置灰、點(diǎn)不了但右鍵懸浮菜單里的無(wú)序列表卻能用?,F(xiàn)象可以拆成三條看起來(lái)互相矛盾的關(guān)鍵線索有序列表numberedList、無(wú)序列表bulletedList、待辦todo、表情emotion工具欄上置灰。加粗bold、顏色color等同批控件看起來(lái)正常。右鍵菜單里的無(wú)序列表能用。第一反應(yīng)當(dāng)然是這幾個(gè)按鈕的模塊沒(méi)注冊(cè)。因?yàn)?wangEditor 的機(jī)制是一個(gè)菜單按鈕 一個(gè)注冊(cè)在key下的菜單工廠工具欄渲染時(shí)找不到工廠就渲染不出來(lái)。但「加粗能用、列表不能用、右鍵能用」這三條擺在一起立刻否掉了模塊完全沒(méi)注冊(cè)的猜測(cè)——它們很多都在同一個(gè)模塊basic-modules里。如果是注冊(cè)問(wèn)題加粗也該一起掛掉。于是轉(zhuǎn)向懷疑是不是這四個(gè)按鈕的isDisabled判斷邏輯在遷移后出錯(cuò)四、一度跑偏盯著 isDisabled 猜了很久wangEditor 里每個(gè)菜單都有isDisabled(editor)返回 true 就置灰。列表/待辦類的邏輯通常是這樣isDisabled(editor){// 選中塊為空、或光標(biāo)在 void/pre/code/table 節(jié)點(diǎn)里 → 禁用return!selection||getTopLevelSelectedBlocks(editor).length0||...;}我一度把注意力全放在為什么bold的isDisabled返回 false、而bulletedList返回 true上懷疑遷移后光標(biāo)選擇selection狀態(tài)沒(méi)正常建立。這方向也走了挺深但始終說(shuō)服不了自己——它們的底層判斷邏輯幾乎一樣沒(méi)理由加粗能拿到正常 selection、列表就拿不到。就在糾結(jié) selection 的時(shí)候用戶貼了一條控制臺(tái)報(bào)錯(cuò)直接把方向拉了回來(lái)。五、控制臺(tái)報(bào)錯(cuò)才是根子Uncaught (in promise) Error: Not found menu item factory by key link注意報(bào)錯(cuò)的 key 是link而不是列表/待辦/表情里的任何一個(gè)。但正是這個(gè)看起來(lái)不相關(guān)的 key把所有按鈕一起打趴下了。順著這條報(bào)錯(cuò)我去node_modules/wangeditor-next/editor的分發(fā)包dist/index.mjs打包產(chǎn)物里一查發(fā)現(xiàn)了遷移時(shí)最容易被忽略的坑wangEditor-next 悄悄改了一堆菜單 key我在打包產(chǎn)物里逐個(gè)搜索key:xxx這種菜單工廠注冊(cè)聲明把白名單里的 key 全過(guò)了一遍發(fā)現(xiàn)有三個(gè) key 在原版里存在、在 next 里根本不存在白名單里寫(xiě)的 key結(jié)果正確的 keylink? 無(wú)此工廠insertLinkimage圖片組內(nèi)? 無(wú)此工廠insertImagetable? 無(wú)此工廠insertTable為什么一個(gè)壞 key 會(huì)讓一堆好按鈕置灰這是整個(gè)問(wèn)題最核心、也最反直覺(jué)的地方。wangEditor-next 的Toolbar拿到toolbarKeys后會(huì)逐個(gè)把 key 解析成已注冊(cè)的菜單工廠。只要中間遇到一個(gè)哪怕一個(gè)解析不了的 key就會(huì)拋出Not found menu item factory by key ...。關(guān)鍵在(in promise)這幾個(gè)字這個(gè)錯(cuò)誤是在工具欄的異步/響應(yīng)式構(gòu)建過(guò)程中拋出的。一旦拋錯(cuò)工具欄的初始化流程就被打斷——本該繼續(xù)往下算的那些按鈕的可用狀態(tài)disabled/active全部停在默認(rèn)的灰那兒更新不出來(lái)。所以白名單里那幾個(gè)無(wú)效 keylink、image、table就像三顆老鼠屎壞了一鍋湯自己拋錯(cuò)不說(shuō)還把bulletedList、numberedList、todo、emotion這些本來(lái)完全正常的按鈕一起拖進(jìn)灰色狀態(tài)。為什么右鍵/懸浮菜單卻一直正常這正好解釋了那條看似矛盾的現(xiàn)象工具欄走toolbarKeys→ 解析成工廠這套先校驗(yàn)、會(huì)拋錯(cuò)的邏輯。壞 key 一來(lái)整個(gè)工具欄初始化被打斷。懸浮/右鍵菜單hoverbar根本不經(jīng)過(guò)toolbarKeys這套校驗(yàn)而是在選中內(nèi)容時(shí)動(dòng)態(tài)彈出。菜單工廠和插件都是好的所以一直能用。也就是說(shuō)問(wèn)題從來(lái)不是列表/待辦/表情這三個(gè)按鈕壞了而是工具欄因?yàn)閯e的壞 key 崩了把他們都拖下水了。六、修復(fù)把無(wú)效 key 全部改掉直接在白名單里把三個(gè)無(wú)效 key 改成有效 key 即可一行都沒(méi)多動(dòng)// frontend/src/components/RichEditor.vuefunctionbuildToolbarKeys():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,// 原先是 tablecodeBlock,code,divider,undo,redo,]}修完后我把所有白名單 key 在打包產(chǎn)物里逐一核對(duì)抗有效性確保不再有漏網(wǎng)的壞 keyheaderSelect OK · blockquote OK · bold OK · ... · bulletedList OK · todo OK emotion OK · insertLink OK · insertImage OK · ... · insertTable OK · ...全部OK。Vite dev 是熱更新的用戶刷新頁(yè)面后控制臺(tái)不再報(bào)Not found menu item factory四個(gè)按鈕恢復(fù)可點(diǎn)問(wèn)題解決。七、復(fù)盤(pán)兩輪踩坑四條經(jīng)驗(yàn)菜單可用狀態(tài)和菜單是否注冊(cè)要先分開(kāi)。isDisabled返回置灰和工廠根本沒(méi)注冊(cè)是兩碼事。前者靠改邏輯解決后者靠修 key/注冊(cè)解決。別一上來(lái)就懷疑自己的判斷邏輯??纯刂婆_(tái)報(bào)錯(cuò)別看感覺(jué)。我盯著為什么加粗能行、列表不行猜了很久一個(gè)link的報(bào)錯(cuò)就把方向拉回來(lái)了。遇到可疑 bug先把運(yùn)行時(shí)異常翻出來(lái)。換庫(kù)后菜單 key 是會(huì)變的。wangeditor/editor→wangeditor-next/editor不能只對(duì) API菜單注冊(cè)的 key 名link→insertLink、image→insertImage、table→insertTable也會(huì)改。最穩(wěn)的核對(duì)方式直接去打包產(chǎn)物里搜key:你用的key看能否命中工廠。一個(gè)壞 key 會(huì)讓看起來(lái)無(wú)關(guān)的好按鈕一起掛掉。因?yàn)楣ぞ邫跇?gòu)建是一串整體流程中間拋錯(cuò)會(huì)打斷后續(xù)所有按鈕可用狀態(tài)的更新。所以修的時(shí)候要把白名單里所有 key 一次性全查一遍而不是修一個(gè)再試一次。附快速核對(duì) wangEditor-next 菜單 key 是否有效遇到類似問(wèn)題可以在安裝目錄里快速核對(duì)任意 key 是否存在$fnode_modules/wangeditor-next/editor/dist/index.mjs$lineGet-Content$f-Rawforeach($kin (link,insertLink,table,insertTable)){$p$line.IndexOf(key:$k)Write-Output{0,-12} - {1}-f$k,($(if($p-ge0){OK $p}else{INVALID}))}OK表示這個(gè) key 有對(duì)應(yīng)菜單工廠INVALID則表示換成正確的 key通常就是在前面加insert。這次遷移告訴我們修輸入法這種看著難的問(wèn)題往往挺直接而工具欄按鈕置灰這種看著簡(jiǎn)單的問(wèn)題背后反而藏著坑。別背鍋給好按鈕先去控制臺(tái)里找那個(gè)拋錯(cuò)的壞 key。