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

ProseMirror自定义Inline节点光标选中停止问题求助

自定义ProseMirror Inline节点光标选中异常问题解决

问题背景

基于ProseMirror定义了SignatureNode自定义Inline节点,代码如下:

export default Node.create({
  name: "SignatureNode",
  inline: true,
  group: "inline",
  atom: false,

  addAttributes() {
    return {
      content: {
        default: "",
        parseHTML: (el) =>
          el.getAttribute("content") ?? el.getAttribute("data-content"),
      },
      signId: {
        default: "",
        parseHTML: (el) =>
          el.getAttribute("signId") ?? el.getAttribute("data-signId"),
      },
    };
  },

  parseHTML() {
    return [
      {
        tag: "signature-node",
      },
      {
        tag: "div",
        getAttrs: (node) =>
          (node as HTMLElement).classList.contains("signature-node") && null,
      },
    ];
  },
  addCommands() {
    return {
      addSign:
        (options) =>
        ({ chain, state }) => {
          // ...
        },
    };
  },

  renderHTML({ HTMLAttributes }) {
    const div = document.createElement("div");

    // Create app for getting article component html
    const app = createApp(SignatureNode, {
      content: HTMLAttributes.content,
      signId: HTMLAttributes.signId,
    });

    const mounted = app.mount(div);
    const html = mounted.$el;

    // Unmount app for performance
    app.unmount();

    return html;

  },

  addNodeView() {
    return VueNodeViewRenderer(SignatureNodeWrapper);
  },
});

拖动光标选中内容时,光标会在该自定义节点的起始位置停止(仅从外部移动光标到节点内操作正常)。尝试给NodeViewWrapper添加contenteditable="true"后,光标会直接跳转到节点末尾,仍无法选中节点内元素。

解决方案

1. 调整NodeView结构与属性

修改SignatureNodeWrapper的DOM结构,确保外层容器保持inline特性,内部内容容器禁用原生编辑:

<NodeViewWrapper class="signature-node-wrapper" style="display: inline;">
  <div class="signature-content" contenteditable="false">
    <SignatureNode
      :content="node.attrs.content"
      :sign-id="node.attrs.signId"
      :update-attributes="updateAttributes"
    />
  </div>
</NodeViewWrapper>

2. 实现NodeView的选中处理逻辑

在SignatureNodeWrapper组件中手动处理选中事件,让ProseMirror识别节点内的选中范围:

// SignatureNodeWrapper.vue
export default {
  props: ['node', 'updateAttributes', 'view'],
  methods: {
    selectNode() {
      this.$el.classList.add('ProseMirror-selectednode');
    },
    deselectNode() {
      this.$el.classList.remove('ProseMirror-selectednode');
    },
    updateSelection() {
      const contentEl = this.$el.querySelector('.signature-content');
      if (!contentEl) return;

      const range = document.createRange();
      range.selectNodeContents(contentEl);
      const sel = window.getSelection();
      sel.removeAllRanges();
      sel.addRange(range);
    }
  },
  mounted() {
    this.$el.addEventListener('mousedown', (e) => {
      e.preventDefault();
      const contentEl = this.$el.querySelector('.signature-content');
      const range = document.createRange();
      range.selectNodeContents(contentEl);
      const sel = window.getSelection();
      sel.removeAllRanges();
      sel.addRange(range);
    });
  }
}

3. 优化节点的renderHTML方法

将渲染根元素改为span,保证节点处于inline文本流中,同时直接渲染内容让ProseMirror识别文本长度:

renderHTML({ HTMLAttributes }) {
  return [
    'span',
    { class: 'signature-node', ...HTMLAttributes },
    HTMLAttributes.content
  ];
}

4. 补充CSS样式

添加基础样式确保节点表现与普通inline文本一致:

.signature-node-wrapper {
  display: inline;
  cursor: text;
}
.signature-content {
  display: inline;
  user-select: text;
}

原理说明

  • 通过自定义鼠标事件和updateSelection方法,手动告诉浏览器选中节点内全部内容,让ProseMirror正确识别选中范围。
  • 使用span作为渲染根元素,避免block元素破坏编辑器的文本流,导致选中中断。
  • 禁用内部元素的contenteditable,防止原生编辑逻辑干扰ProseMirror的选中处理。

内容的提问来源于stack exchange,提问作者Mohib Arshi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 19:50:24