Vditor 自定义渲染

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

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。

    349 引用 • 1803 回帖 • 1 关注
  • 开发指南
    8 引用 • 757 回帖
1 操作
Vanessa 在 2020-05-02 18:54:00 更新了该帖

相关帖子

欢迎来到这里!

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

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

    看这意思是,自定义是需要在 vditor 已有的进行修改?如果要自己定义一个没有的渲染方式怎么进行?比如我要写一个 renderFace,规避已有的 renderEmoji,我需要图片表情,所以要自定义 ,望解答一下

    使用接口就行了
    Vanessa
  • 其他回帖
  • zhuyou 1 1 评论

    image.png

    在编辑模式下,正常初始化,能够正常给图片带上 linkBase 的前缀,但是来回切换编辑模式后,之前插入的图片前缀就没了,烦请解决下这个问题呢 😭

    image.png

    发现问题了,是在切换模式后,给之前的相对链接加上了/,这样 linBase 就失效了,烦请作者解决下这个问题呢

    image.png

    1 操作
    zhuyou 在 2024-06-17 20:46:47 更新了该回帖
    感谢反馈,后续改进 Issue #1636 · Vanessa219/vditor
    Vanessa
  • emmm

    node.Tokenstr()? 这个好像是的,但是我 return [<div><img src="${src}"/></div>,Lute.WalkContinue]

    好像没生效,页面上把 img 的 alt 显示了

  • 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
  • 查看全部回帖