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 引用 • 1814 回帖
  • 开发指南
    8 引用 • 760 回帖
1 操作
Vanessa 在 2020-05-02 18:54:00 更新了该帖

相关帖子

欢迎来到这里!

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

注册 关于
请输入回帖内容 ...
  • emmm

    请教一下 renderImage 这个方法里面有图片的路径参数吗?我想自定义渲染 img,没找到图片的路径,多谢指点下

    1 回复
  • 其他回帖
  • emmm 1 评论

    还有个问题请教下,heading 自定义渲染,怎么获取标题的内容,因为需要做个锚点,我看这个标题示例没有锚点,内容貌似内部渲染的

    Vanessa 1
  • cyf783 1 评论

    image.pngimage.png

    ir 模式,自定义渲染,在初始化时可以正常按自定义的渲染,但是在修改文档内容后即时渲染没有使用自定义渲染,请问怎么解决啊。

    正文解析存在很多判断,不允许自定义。只有粘贴和预览的时候可以。
    Vanessa
  • 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
  • 查看全部回帖