You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

使用tiptap-pagination-plus时insertContentAt插入流式Markdown块出现多余字符问题

使用tiptap-pagination-plus时insertContentAt插入流式Markdown块出现多余字符问题

我之前踩过这个分页插件和流式插入的坑,折腾了好一阵才找到几个可行的解决方向,给你参考下:

1. 核心问题:用字符串长度计算插入位置完全错误

你现在用toScrible.value += newChunk.length来更新下一次插入的位置,这是最可能导致多余字符的原因——Markdown解析成ProseMirror节点后的文档长度,和原Markdown字符串的长度完全不一样。比如你插入# 标题,原字符串长度是4,但解析成Heading节点后,文档中的实际字符长度是2(去掉#和空格),多次累加后位置会严重偏移,导致后续插入到错误的节点位置,产生重复或多余字符。

修改代码:用编辑器选区来获取正确的插入位置
把原来的toScrible.value += newChunk.length替换成:

// 插入后,选区会自动跳到内容末尾,直接取这个位置就是正确的下一次插入点
toScrible.value = tiptapEditor.value.state.selection.to

2. 分页插件的实时计算干扰内容插入

tiptap-pagination-plus会在每次内容变化时自动计算分页、插入分页元素,这个过程会干扰ProseMirror的文档状态,导致插入的内容被分页节点分割,产生多余的标记或字符。可以在流式插入时暂时禁用分页更新,完成后再重新计算:

watch(
  () => props.scribbleNewContent,
  async newChunk => {
    if (!tiptapEditor.value || !newChunk) return
    // 找到分页插件实例
    const paginationExt = tiptapEditor.value.extensionManager.extensions.find(ext => ext.name === 'paginationPlus')
    // 流式插入时禁用分页自动更新
    if (paginationExt) paginationExt.options.disabled = true

    const { from, to } = initialSelectionRange.value
    if (toScrible.value === null || toScrible.value === undefined) toScrible.value = from

    try {
      if (isFirstChunk) {
        tiptapEditor.value.commands.deleteRange({ from, to })
        isFirstChunk = false
      }
      await tiptapEditor.value.commands.insertContentAt(toScrible.value, newChunk, {
        applyInputRules: true,
        applyPasteRules: true,
        errorOnInvalidContent: false
      })
      // 用选区位置更新下一次插入点
      toScrible.value = tiptapEditor.value.state.selection.to
    } catch (e) {
      console.warn('Erro ao inserir chunk:', e)
    }

    markdownBuffer.value += newChunk

    if (props.scribbleCompleted) {
      // 完成后重新启用分页并强制更新
      if (paginationExt) {
        paginationExt.options.disabled = false
        tiptapEditor.value.view.dispatch(tiptapEditor.value.state.tr.setMeta('paginationPlus', { forceUpdate: true }))
      }
      // 你的原有最终插入逻辑...
      const docSize = tiptapEditor.value.state.doc.content.size
      if (from < 0 || toScrible.value > docSize) {
        console.warn('Invalid range')
        return
      }
      tiptapEditor.value?.chain()
        .focus()
        .deleteRange({ from, to: toScrible.value })
        .insertContentAt(from, markdownBuffer.value, {
          parseOptions: { preserveWhitespace: false },
          applyPasteRules: true
        })
        .run()
      // 重置状态
      isFirstChunk = true
      markdownBuffer.value = ''
    }
  },
)

3. 最稳妥的替代方案:只缓冲Markdown,最后一次性插入

如果你的场景可以接受“最后一次性渲染完整内容”而不是实时流式显示,那直接放弃中间的实时插入,只往markdownBuffer里累加chunk,直到scribbleCompleted时再一次性替换目标范围,完全避开分页插件的中间干扰:

watch(
  () => props.scribbleNewContent,
  async newChunk => {
    if (!tiptapEditor.value || !newChunk) return
    const { from, to } = initialSelectionRange.value

    // 只累加缓冲区,不实时插入
    markdownBuffer.value += newChunk

    if (props.scribbleCompleted) {
      try {
        tiptapEditor.value?.chain()
          .focus()
          .deleteRange({ from, to })
          .insertContentAt(from, markdownBuffer.value, {
            parseOptions: { preserveWhitespace: false },
            applyPasteRules: true
          })
          .run()
        console.log('CONFIRMA')
      } catch (e) {
        console.warn('Erro ao inserir conteúdo final:', e)
      }
      // 重置所有状态
      isFirstChunk = true
      markdownBuffer.value = ''
      toScrible.value = null
    }
  },
)

额外排查小技巧

打开ProseMirror的开发者工具(可以装prosemirror-dev-tools插件),查看文档的节点结构,看看多余字符是来自分页插件插入的临时节点,还是位置偏移导致的重复插入——这能帮你快速定位问题根源。

另外注意下你PaginationPlus配置里的headerLeft有个小笔误:diplay: flex应该是display: flex,虽然不影响多余字符,但会导致header布局异常😉

火山引擎 最新活动