You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
      },
    },
  });
};

问题原因

  1. 装饰范围计算错误:当前代码用pos + parent.content.size作为装饰结束位置,覆盖的是父节点的全部内容而非目标文本段。编辑时文档结构变化(文本拆分/合并)会导致这个范围与实际目标文本错位,样式显示不全。
  2. 装饰集未实时更新:仅在带有isTransactionToListen元数据的事务中才重新生成装饰,普通编辑事务(输入、删除)直接复用旧装饰集,而旧装饰的位置已与新文档结构不匹配。
  3. 未处理位置映射: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 08:55:38