|
|
@@ -53,23 +53,20 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
_destroyEditor() {
|
|
|
- // 关键决策:wangEditor 5 在销毁 path 上有 SLIGHT-TIMING 的 listener 残留问题,
|
|
|
- // editor.destroy() 之前 TextArea 已经在 document 上 addEventListener 了
|
|
|
- // selectionchange;一旦销毁链中途抛 "Cannot perform 'get' on a proxy
|
|
|
- // that has been revoked",emit("destroyed") 不会触发,removeEventListener
|
|
|
- // 也就不会跑。下次用户点页面 selectionchange 触发同一个 revoke 错误循环。
|
|
|
- //
|
|
|
- // 解法:不调用 editor.destroy()/toolbar.destroy(),只断引用。
|
|
|
- // Vue 卸载组件时已经会把 $toolbarContainer / $editorContainer DOM 节点摘掉,
|
|
|
- // 元素级监听器随之释放。document 上的 selectionchange listener 持有的是
|
|
|
- // TextArea 实例;TextArea 内部 WeakMap 指向 editor 实例;editor 实例
|
|
|
- // 不再被任何地方引用时,GC 一并回收,listener 因 handler 函数被回收而失效。
|
|
|
- // 期间不调用 destroy,Slate proxy 不会被显式 revoke,所有 handler 路径上的
|
|
|
- // 属性访问都还是合法的,不会再抛异常。
|
|
|
+ // 同步调用 destroy():coreCreateEditor 内部注册了 document 上的
|
|
|
+ // selectionchange 监听器,只有 emit("destroyed") 才会走
|
|
|
+ // removeEventListener。如果不调 destroy,旧监听器会永久留在
|
|
|
+ // document 上,且其 closure 持有的 Slate state 代理在后续操作
|
|
|
+ // (setHtml / 输入触发 change) 中会被 Slate 自动 revoke,下次用户
|
|
|
+ // 一点页面就抛 "Cannot perform 'get' on a proxy that has been revoked"。
|
|
|
+ // 同步调用 + try-catch 兜住 destroy 链内部任何抛错,保证 emit("destroyed")
|
|
|
+ // 至少能走到,监听器能被摘掉。
|
|
|
if (this.editor) {
|
|
|
+ try { this.editor.destroy() } catch (e) { /* swallow */ }
|
|
|
this.editor = null
|
|
|
}
|
|
|
if (this.toolbar) {
|
|
|
+ try { this.toolbar.destroy() } catch (e) { /* swallow */ }
|
|
|
this.toolbar = null
|
|
|
}
|
|
|
},
|
|
|
@@ -116,6 +113,26 @@ export default {
|
|
|
mode: 'default'
|
|
|
})
|
|
|
|
|
|
+ // 兜底:把 editor.apply 包一层 try-catch。Slate.js 在每次 op 后会
|
|
|
+ // revoke 旧 state 代理;如果 selectionchange 触发的 editor.select →
|
|
|
+ // apply 路径撞上一个 stale 代理(常见于 setHtml 跑了一串 op 后,
|
|
|
+ // 监听器 closure 还指向早期 state),会直接 Uncaught。捕获后该次
|
|
|
+ // 选择同步丢弃,不影响后续输入;后续 Slate 自己的 op 流程仍走原 apply,
|
|
|
+ // 这里只是把"已是 normal"的封死,不是替换实现。
|
|
|
+ if (this.editor && typeof this.editor.apply === 'function') {
|
|
|
+ const origApply = this.editor.apply
|
|
|
+ this.editor.apply = function(op) {
|
|
|
+ try {
|
|
|
+ return origApply.call(this, op)
|
|
|
+ } catch (err) {
|
|
|
+ if (err && err.message && err.message.indexOf('proxy has been revoked') !== -1) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ throw err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 工具栏:列出 wangEditor 5 已注册的全部有效菜单 key
|
|
|
// 注:wangEditor 把删除线菜单命名为 "through","mergeCells" 不是注册菜单
|
|
|
const toolbarConfig = {
|