Vditor 自定义渲染

本贴最后更新于 1664 天前,其中的信息可能已经天翻地覆

Vditor 支持自定义渲染器,如果需要修改渲染结果的话可考虑使用该特性。

渲染流程

Vditor 通过 Lute 引擎进行 Markdown 解析,解析后将生成语法树,最后通过遍历语法树进行渲染。根据不同的渲染场景,Vditor 会触发不同的自定义渲染器回调。

目前支持的渲染回调场景如下:

  • HTML2Md:HTML 转 Markdown,v3.2.0 后改为 HTML2VditorSVDOM。使用场景:
    • options.modesv 时,初始化编辑器内容
    • 调用 html2md 方法
    • sv 模式粘贴复制好的 HTML 时触发
  • HTML2VditorDOM:HTML 转 Vditor DOM,在 wysiwyg 模式粘贴 HTML 时触发
  • HTML2VditorIRDOM:HTML 转 Vditor IR DOM,在 ir 模式粘贴 HTML 时触发
  • HTML2VditorSVDOM:HTML 转 Vditor SV DOM,在 sv 模式粘贴 HTML 时触发
  • Md2HTML:Markdown 转 HTML,v3.2.0 后改为 Md2VditorSVDOM。使用场景:
    • 调用静态方法 md2html
    • 调用静态方法 previewRender
  • Md2VditorDOM:Markdown 转 Vditor DOM,在 wysiwyg 模式粘贴纯文本时触发
  • Md2VditorIRDOM:Markdown 转 Vditor IR DOM,在 ir 模式粘贴纯文本时触发
  • Md2VditorSVDOM:Markdown 转 Vditor SV DOM,在 sv 模式粘贴纯文本时触发

自定义渲染器

通过接口 vditor.lute.SetJSRenderers 来设置自定义渲染器,参数格式如下:

(options?: {
        renderers: {
            HTML2VditorDOM?: ILuteRender,
            HTML2VditorIRDOM?: ILuteRender,
            HTML2Md?: ILuteRender,
            Md2HTML?: ILuteRender,
            Md2VditorDOM?: ILuteRender,
            Md2VditorIRDOM?: ILuteRender,
        },
    }): void;

interface ILuteRender {
    renderHeading?: ILuteRenderCallback;
    renderLinkDest?: ILuteRenderCallback;
    ... // 参见渲染器函数列表
}

type ILuteRenderCallback = (node: {
    TokensStr: () => string;
    __internal_object__: {
        Parent: {
            Type: number,
        },
        HeadingLevel: string,
    }
},                          entering: boolean) => [string, number];

其中 Md2HTML 渲染回调场景中,渲染器函数是带有两个输入参数和两个返回值(合并在一个数组中)的函数,两个参数:

  • node:当前遍历到的节点。比如渲染器函数是 renderHeading 则 node 为标题节点,如果渲染器函数是 renderLink 则 node 为链接节点
  • entering:遍历是否为进入节点。我们按深度优先算法来遍历语法树,在进入节点时为 true,离开节点时为 false

函数返回值为一个数组,包含两个元素:

  • 第一个元素为 string:渲染的字符串,比如 renderHeading 返回 <h2>foo</h2>
  • 第二个元素为 number:遍历状态,0:停止遍历,1:跳过子节点遍历,2:继续遍历

为了方便说明,下面插入 Lute 遍历实现部分的代码来帮助理解:

// WalkStatus 描述了遍历状态。
type WalkStatus int

const (
	// WalkStop 意味着不需要继续遍历。
	WalkStop = iota
	// WalkSkipChildren 意味着不要遍历子节点。
	WalkSkipChildren
	// WalkContinue 意味着继续遍历。
	WalkContinue
)

// Walker 函数定义了遍历节点 n 时需要执行的操作,进入节点设置 entering 为 true,离开节点设置为 false。
// 如果返回 WalkStop 或者 error 则结束遍历。
type Walker func(n *Node, entering bool) WalkStatus

// Walk 使用深度优先算法遍历指定的树节点 n。
func Walk(n *Node, walker Walker) {
	var status WalkStatus

	// 进入节点
	status = walker(n, true)
	if status == WalkStop {
		return
	}

	if status != WalkSkipChildren {
		// 递归遍历子节点
		for c := n.FirstChild; nil != c; c = c.Next {
			Walk(c, walker)
		}
	}

	// 离开节点
	status = walker(n, false)
	return
}

其中 Walker 就是 renderXXX 渲染器函数内部实现的签名,在外部我们进行了包装,以方便返回渲染结果字符串,所以自定义渲染器函数的返回值是包含两个值的一个数组。

渲染器函数列表

Vditor 支持自定义如下渲染器函数,我们按元素类型分组介绍。

块级渲染器

  • renderDocument:渲染整个文档
  • renderParagraph:渲染段落
  • renderText:渲染文本
  • renderCodeBlock:渲染代码块,相关渲染器包括 renderCodeBlockOpenMarkerrenderCodeBlockInfoMarkerrenderCodeBlockCode renderCodeBlockCloseMarker
  • renderMathBlock:渲染数学公式块,相关渲染器包括 renderMathBlockOpenMarkerrenderMathBlockContentrenderMathBlockCloseMarker
  • renderBlockquote:渲染块引用,相关渲染器包括 renderBlockquoteMarker
  • renderHeading:渲染标题,相关渲染器包括 renderHeadingC8hMarker
  • renderList:渲染列表
  • renderListItem:渲染列表项,相关渲染器包括 renderTaskListItemMarker
  • renderThematicBreak:渲染分隔线
  • renderHTML:渲染 HTML 块
  • renderTable:渲染表格,相关渲染器包括 renderTableHeadrenderTableRowrenderTableCell
  • renderFootnotesDef:渲染脚注定义块

行级渲染器

  • renderCodeSpan:渲染代码,相关渲染器包括 renderCodeSpanOpenMarkerrenderCodeSpanContentrenderCodeSpanCloseMarker
  • renderInlineMath:渲染行级数学公式,相关渲染器包括 renderInlineMathOpenMarkerrenderInlineMathContentrenderInlineMathCloseMarker
  • renderEmphasis:渲染强调,相关渲染器包括 renderEmAsteriskOpenMarkerrenderEmAsteriskCloseMarkerrenderEmUnderscoreOpenMarkerrenderEmUnderscoreCloseMarker
  • renderStrong:渲染加粗,相关渲染器包括 renderStrongA6kOpenMarkerrenderStrongA6kCloseMarkerrenderStrongU8eOpenMarkerrenderStrongU8eCloseMarker
  • renderStrikethrough:渲染删除线,相关渲染器包括 renderStrikethrough1OpenMarkerrenderStrikethrough1CloseMarkerrenderStrikethrough2OpenMarkerrenderStrikethrough2CloseMarker
  • renderHardBreak:渲染硬换行
  • renderSoftBreak:渲染软换行
  • renderInlineHTML:渲染行级 HTML
  • renderLink:渲染链接,相关渲染器包括 renderOpenBracketrenderCloseBracketrenderOpenParenrenderCloseParenrenderLinkTextrenderLinkSpacerenderLinkDestrenderLinkTitle
  • renderImage:渲染图片,相关渲染器包括 renderBangrenderLink
  • renderEmoji:渲染 Emoji,相关渲染器包括 renderEmojiUnicoderenderEmojiImgrenderEmojiAlias
  • renderToC:渲染页内目录
  • renderFootnotesRef:渲染脚注引用
  • renderBackslash:渲染反斜杆,相关渲染器包括 renderBackslashContent

以上中的“相关渲染器”中的“相关”代表语法树节点结构的相关性,具体节点结构描述请参考 Markdown 解析原理详解和 Markdown AST 描述

示例

完整示例可参见 demo。下面我们来看一下自定义渲染的代码示例。

const renderers = {
  renderHeading: (node, entering) => {
    if (entering) {
      return [`<h${node.__internal_object__.HeadingLevel} class="vditor__heading"><span class="prefix"></span><span>`, Lute.WalkContinue];
    } else {
      return [`</span></h${node.__internal_object__.HeadingLevel}>`, Lute.WalkContinue];
    }
  },
}
  • Vditor

    Vditor 是一款浏览器端的 Markdown 编辑器,支持所见即所得、即时渲染(类似 Typora)和分屏预览模式。它使用 TypeScript 实现,支持原生 JavaScript、Vue、React 和 Angular。

    351 引用 • 1813 回帖
  • 开发指南
    8 引用 • 759 回帖
1 操作
Vanessa 在 2020-05-02 18:54:00 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • chaobei 1 评论

    大佬,想问一下,mermaid 相关,以及 echarts 的渲染是通过哪个接口实现的?渲染 html 吗?

  • 其他回帖
  • xiaoMaYa 5 评论

    V 大可不可以帮忙看下 为什么没有进入 renderBlockquote,并且 readerHeading 中返回的内容也没有生效
    代码如下:

    image.png

    markdown 文本为

    `
    ## {.tabset}
    # 欢迎使用魔法师~
    > 您可以在这个板块了解到魔法师平台中的特有概念和名词所代表的含义,来帮助你更好地上手和使用产品。
     {.is-success}
    `
    

    生成的效果如下
    image.png

    你把源码发我,我调试看看。
    Vanessa 1 赞同
    @Vanessa after: () => { vditor.vditor.lute.SetJSRenderers({//自定义渲染 renderers: { Md2VditorDOM: { renderDocument: (node, entering) => { console.log('renderDocument', node) if (entering) { console.log('renderDocument', node) return [, Lute.WalkContinue] } return [, Lute.WalkContinue] }, renderLink: (node, entering) => { console.log('renderImage', node) if (entering) { return [, Lute.WalkContinue] } return [, Lute.WalkContinue] }, }, HTML2Md: {//当模式为分屏预览时初次进入会加载 renderDocument: (node, entering) => { if (entering) { return ['', Lute.WalkContinue] } return ['', Lute.WalkContinue] }, renderHeading: (node, entering) => {//标题 if (entering) { if (Lute.GetHeadingID(node)) { return [, Lute.WalkContinue] } return [`<ceshi>`, Lute.WalkContinue] } if (Lute.GetHeadingID(node)) { return [, Lute.WalkContinue] } return [<ceshi></ceshi>, Lute.WalkContinue] }, renderBlockquote: (node, entering) => {//块引用(未生效) console.log('renderBlockquote', node) if (entering) { return [我是引用的, Lute.WalkContinue] } return [‘我是引用的’, Lute.WalkContinue] }, renderParagraph: (node, entering) => {//块引用(无法改变内容) console.log('renderParagraph', node) if (entering) { return [我是引用的, Lute.WalkContinue] } return [‘我是引用的’, Lute.WalkContinue] }, }, } }) },
    xiaoMaYa
    @xiaoMaYa 需要调用 vditor.html2md("<h1> 1 </h1><blockquote>test</blockquote>") 就可以触发了
    Vanessa
    @Vanessa 我在 renderParagraph 中调用 html2md 这个函数可以触发 renderBlockquote 了 然后请问下渲染的方式怎么自定义我看在 render 函数中返回没有效果
    xiaoMaYa
    @xiaoMaYa 你在控制台上执行 vditor.html2md("<h1> 1 </h1><blockquote>test</blockquote>") 就可以看到渲染的最终效果了
    Vanessa 1
  • zhuyou 4 评论

    vditor 的 options.preview.markdown 的 linkBase 加链接前缀功能,在即时预览模式下为什么不能生效,在分屏预览和所见即所得是生效的,这是 bug 吧

    这个是 preview 上的参数,只和预览相关
    Vanessa
    @Vanessa 即使预览不也是预览么
    zhuyou
    @Vanessa 即时预览不也是预览么
    zhuyou
    @zhuyou 是编辑
    Vanessa
  • zhuyou 2 评论

    image.png

    这个自定义渲染链接后,生成了两个链接,跳转的是上面没有前缀的链接,我想跳转的时带前缀的

    是不是标签闭合没弄对?
    Vanessa
    @Vanessa 这个问题解决了,参考了这篇文章 搞定 Vditor 自定义渲染超链接,其它原理也相同
    zhuyou
  • 查看全部回帖