Skip to content
...

高级定制 — parseOptions & 自定义节点

本页介绍如何自定义解析流程和提供作用域自定义组件。

parseOptions

parseOptions 可传递给 MarkdownRender 或直接用于 parseMarkdownToStructure

  • preTransformTokens?: (tokens: MarkdownToken[]) => MarkdownToken[] — 在 markdown-it 解析后立即变换 tokens
  • postTransformTokens?: (tokens: MarkdownToken[]) => MarkdownToken[] — 进一步变换 tokens
  • postTransformNodes?: (nodes: ParsedNode[]) => ParsedNode[] — 操作最终节点树

示例:自定义 HTML‑like 标签(推荐)

对于 <thinking>...</thinking> 这类简单自定义标签,现在不再需要先正则换行或重写 token。只要把标签加入白名单并注册组件即可:

ts
import { setCustomComponents } from 'markstream-vue'
import ThinkingNode from './ThinkingNode.vue'

setCustomComponents('docs', { thinking: ThinkingNode })
vue
<MarkdownRender
  custom-id="docs"
  :custom-html-tags="['thinking']"
  :content="markdown"
/>

custom-html-tags 包含某个标签名时,解析器会:

  • 在流式场景下吞并未闭合中间态,直到 <tag ...> 完整出现;
  • 输出 CustomComponentNodetype: '<tag>'),并带上 content、可选 attrsloading/autoClosed 标记。

自定义组件解析示例

上面的内置白名单 + 自定义节点管线已覆盖大多数“组件式”标签(内联或块级)。 当你需要进一步改造节点(例如剥掉包裹、合并分段、手动映射 attrs)时,再使用钩子:

ts
function preTransformTokens(tokens: MarkdownToken[]) {
  return tokens.map((t) => {
    if (t.type === 'html_block' && t.tag === 'thinking')
      return { ...t, content: String(t.content ?? '').replace(/<\/?thinking[^>]*>/g, '').trim() }
    return t
  })
}

const nodes = parseMarkdownToStructure(markdown, md, { preTransformTokens })

其他可选方案(按复杂度递增):

  • 后端将 thinking 拆成单独字段/类型:前端用两次 MarkdownRender,一个渲染 thinking,另一个渲染剩余正文,互不干扰。
  • 在进入解析前用正则占位:先 replace<thinking>...</thinking> 保存到数组;正文用 MarkdownRender;最后把占位符替换回自定义组件渲染结果。thinking 常在开头时可以直接截取头部做单独渲染。

setCustomComponents(id, mapping)

  • 使用 setCustomComponents('docs', { thinking: ThinkingComponent }) 作用于带 custom-id="docs"MarkdownRender 实例。
  • 调用 removeCustomComponents 清理映射,避免单页应用内存泄漏。

作用域示例

vue
<MarkdownRender content="..." custom-id="docs" />
// 在 setup 中
setCustomComponents('docs', { thinking: ThinkingNode })

高级钩子是为 Markdown 添加领域语法的强大方式,无需更改核心解析器。

Typewriter 属性

MarkdownRender 支持 typewriter 布尔属性,控制非 code_block 节点是否包裹小型 enter 过渡。适用于演示 UI,但在 SSR 或打印/导出场景下可能不需要。

示例:

vue
<MarkdownRender :content="markdown" :typewriter="false" />

CSS 变量:--typewriter-fade-duration--typewriter-fade-ease 可用于主题调整。

国际化(i18n)

默认 getMarkdown 使用英文 UI 文案(如代码块复制按钮)。你可以通过 i18n 选项自定义这些文本:

翻译映射用法:

ts
import { getMarkdown } from 'markstream-vue'

const md = getMarkdown('editor-1', {
  i18n: {
    'common.copy': '复制',
  }
})

翻译函数用法:

ts
import { getMarkdown } from 'markstream-vue'
import { useI18n } from 'vue-i18n' // 或其他 i18n 库

const { t } = useI18n()

const md = getMarkdown('editor-1', {
  i18n: (key: string) => t(key)
})

全局覆盖默认文案:

如果你只是想修改一些默认 UI 文案(例如 "Copy" → "复制"),而不需要完整的 i18n 功能,可以使用 setDefaultI18nMap 来替换默认的翻译映射:

ts
import { setDefaultI18nMap } from 'markstream-vue'

setDefaultI18nMap({
  'common.copy': '复制',
  'common.copySuccess': '已复制',
  'common.decrease': '减少',
  'common.reset': '重置',
  'common.increase': '增加',
  'common.expand': '展开',
  'common.collapse': '收起',
  'common.preview': '预览',
  'common.source': '源码',
  'common.export': '导出',
  'common.open': '打开',
  'common.zoomIn': '放大',
  'common.zoomOut': '缩小',
  'common.resetZoom': '重置缩放',
  'image.loadError': '图片加载失败',
  'image.loading': '图片加载中...',
})

这个功能适合以下场景:

  • 不需要语言切换,只是觉得默认文案不合适
  • 希望默认使用中文(或其他语言)UI
  • 没有使用 vue-i18n,但仍想自定义 UI 文案

默认翻译键:

  • common.copy:代码块复制按钮文本
  • common.copySuccess:复制成功后的提示文本
  • image.loadError:图片加载失败的提示
  • image.loading:图片加载中的提示

该设计保证 markdown 工具函数纯净,无全局副作用,可与任意 i18n 方案集成或直接传入静态翻译。