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 更新了该帖

相关帖子

欢迎来到这里!

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

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

    有点复杂,有没有简单的应用场景。

    列如:

    
    <div id="a">你好</div> 
    # 测试一级目录
    内容
    

    使用

    Vditor.preview(document.getElementById('id'),text)
    

    以 md 文本的形式渲染,如何保留里面的 div 不被转换?

    把 div 用代码块包起来
    Vanessa
  • 其他回帖
  • emmm

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

    1 回复
  • zhuyou 4 评论

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

    这个是 preview 上的参数,只和预览相关
    Vanessa
    @Vanessa 即使预览不也是预览么
    zhuyou
    @Vanessa 即时预览不也是预览么
    zhuyou
    @zhuyou 是编辑
    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
  • 查看全部回帖