Prosemirror编辑器编辑时文本装饰样式异常问题求助
Prosemirror文本装饰插件编辑时样式异常问题
我基于Prosemirror Editor开发了一款文本装饰插件,正常状态下运行良好,但编辑文本时出现样式异常:原本带有橙色背景的文本,编辑过程中装饰样式会部分消失或仅在局部显示。
核心代码
export const getDecorationsForAnnotations = ( doc: any, data: Data[], selectedDataId?: string ) => { let decos: any[] = []; let initialPos: number | undefined = undefined; let finalPos: number | undefined = undefined; doc.descendants((node: Node, pos: number, parent: any) => { // ... my logic to filter nodes here decos.push( Decoration.inline(pos, pos + parent.content.size, { style: S.DECORATED_PROSEMIRROR_ANNOTATED_SELECTED_NODE, }) ); } } } return true; }); return { decos }; }; export const getHighlightAnnotationsPlugin = ( ... ) => { return new Plugin({ key: new PluginKey("pluginHighlightNotes"), state: { init(config, editorState) { const doc = editorState.doc as any; return DecorationSet.create(doc, []); }, apply(transaction, oldEditorState, newEditorState) { let data: Data[] | undefined = undefined; let decorationSet = undefined; let selectedDataId = undefined; const mark = new Mark(); // being used to pass metadata to pluginView ( to add components in the DOM ) if (transaction.getMeta("isTransactionToListen")) { data = transaction.getMeta("data") as Data[]; selectedDataId = transaction.getMeta("selectedDataId") as string; } else { if (!data && oldEditorState instanceof DecorationSet) { // reuse previous state and decorations decorationSet = oldEditorState; } } if (!decorationSet && data?.length) { const doc = transaction.doc; const { decos } = getDecorationsForAnnotations( doc, data, selectedDataId ); decorationSet = DecorationSet.create(doc, decos); } return decorationSet ? decorationSet : DecorationSet.create(transaction.doc, []); }, }, view: (editorView: any) => { return new pluginView(...); }, props: { decorations(state) { return this.getState(state); }, }, }); };
问题原因
- 装饰范围计算错误:当前代码用
pos + parent.content.size作为装饰结束位置,覆盖的是父节点的全部内容而非目标文本段。编辑时文档结构变化(文本拆分/合并)会导致这个范围与实际目标文本错位,样式显示不全。 - 装饰集未实时更新:仅在带有
isTransactionToListen元数据的事务中才重新生成装饰,普通编辑事务(输入、删除)直接复用旧装饰集,而旧装饰的位置已与新文档结构不匹配。 - 未处理位置映射:Prosemirror文档变更后,旧装饰的位置需要通过
mapping映射到新位置,直接复用会导致位置失效。
解决办法
1. 修正装饰位置计算
遍历节点时,基于目标文本的实际位置生成装饰:
- 如果
data包含文本的起始/结束偏移量,用doc.resolve获取准确位置:
data.forEach(item => { const startPos = doc.resolve(item.startOffset).pos; const endPos = doc.resolve(item.endOffset).pos; decos.push( Decoration.inline(startPos, endPos, { style: S.DECORATED_PROSEMIRROR_ANNOTATED_SELECTED_NODE, }) ); });
- 如果是匹配文本节点,用节点自身的文本长度计算结束位置:
doc.descendants((node: Node, pos: number) => { if (node.isText() && /* 你的匹配条件 */) { decos.push( Decoration.inline(pos, pos + node.textContent.length, { style: S.DECORATED_PROSEMIRROR_ANNOTATED_SELECTED_NODE, }) ); } return true; });
2. 实时更新装饰集
修改插件apply方法,所有事务都重新生成或映射装饰:
// 先调整插件state结构,存储data、selectedDataId和decorationSet state: { init(config, editorState) { return { data: [], selectedDataId: undefined, decorationSet: DecorationSet.create(editorState.doc, []) }; }, apply(transaction, oldState, newState) { let data = oldState.data; let selectedDataId = oldState.selectedDataId; // 从事务元数据获取最新数据 if (transaction.getMeta("isTransactionToListen")) { data = transaction.getMeta("data") as Data[]; selectedDataId = transaction.getMeta("selectedDataId") as string; } // 重新生成装饰集 const decorationSet = data.length ? DecorationSet.create(newState.doc, getDecorationsForAnnotations(newState.doc, data, selectedDataId).decos) : DecorationSet.create(newState.doc, []); return { data, selectedDataId, decorationSet }; }, }, props: { decorations(state) { return this.getState(state).decorationSet; }, },
3. 优化:使用map方法复用装饰
若要避免频繁重新生成装饰,可先映射旧装饰位置,再更新:
apply(transaction, oldState, newState) { let data = oldState.data; let selectedDataId = oldState.selectedDataId; let decorationSet = oldState.decorationSet.map(transaction.mapping, newState.doc); if (transaction.getMeta("isTransactionToListen")) { data = transaction.getMeta("data") as Data[]; selectedDataId = transaction.getMeta("selectedDataId") as string; // 用新数据重新生成装饰 decorationSet = DecorationSet.create(newState.doc, getDecorationsForAnnotations(newState.doc, data, selectedDataId).decos); } return { data, selectedDataId, decorationSet }; },
内容的提问来源于stack exchange,提问作者Marcos Paulo Júnior
相关产品推荐
相关产品推荐

