TipTap自定义Markdown选中文本包裹功能遇越界错误的排查
问题分析与解决
错误原因
你遇到的「nodeSize」错误,本质是因为代码直接尝试访问选区外的无效位置:
- 当选中第一个单词时,
from - len会变成负数(比如选区起始位置是1,标记长度是2,计算后为-1),超出文档的合法位置范围 - 当选中最后一个单词时,
to + len会超过文档的总长度,同样属于无效位置
ProseMirror(TipTap的底层依赖)无法读取这些不存在的位置,因此抛出错误。
选区不从0开始的原因
TipTap基于ProseMirror,其位置计算规则包含节点结构:
每个段落节点本身会占用一个起始位置(通常是0),段落内的文本内容从位置1开始计数。比如一段文本「Hello World」,第一个字符「H」的位置是1,而非0。这就是选中第一个单词时,选区起始位置不为0的核心原因。
更稳妥的实现方式
下面是修复了越界问题、并处理了位置偏移的优化版本:
const wrapUnwrap = marker => { const { state, view } = toRaw(editor.value); const { from, to } = state.selection; const len = marker.length; if (from === to) return; let hasMarkerBefore = false; let hasMarkerAfter = false; // 安全检查选区前是否存在标记 if (from - len >= 0) { const textBefore = state.doc.textBetween(from - len, from); hasMarkerBefore = textBefore === marker; } // 安全检查选区后是否存在标记 if (to + len <= state.doc.content.size) { const textAfter = state.doc.textBetween(to, to + len); hasMarkerAfter = textAfter === marker; } const tr = state.tr; if (hasMarkerBefore && hasMarkerAfter) { // 先删除末尾标记,避免删除开头标记后位置偏移 tr.delete(to, to + len); tr.delete(from - len, from); } else { // 插入标记时,先在开头插入,末尾位置需偏移标记长度 tr.insertText(marker, from); tr.insertText(marker, to + len); } view.dispatch(tr); }; const makeBold = () => { wrapUnwrap("**"); }; const makeItalic = () => { wrapUnwrap("*"); };
关键优化点:
- 增加了位置合法性检查,避免访问无效范围
- 删除标记时调整顺序,解决删除开头标记后末尾标记位置偏移的问题
- 插入标记时修正末尾插入的位置,适配开头插入后的长度变化
内容的提问来源于stack exchange,提问作者Tallboy
相关产品推荐
相关产品推荐

