搞定 Vditor 自定义渲染超链接,其它原理也相同

本贴最后更新于 1540 天前,其中的信息可能已经沧海桑田

业务需要,须在 Markdown 渲染成 HTML 时,将所有超链接中增加自定义属性,如下:

Markdown 为:

[觅好图](http://mihaotu.com)

希望渲染后的超链接代码为:

<a href='http://mihaotu.com' my-attr='自定义属性'>觅好图</a>

按照官方文档例子,我们在 options.renderers 中绑定了方法 renderLink,如下:

renderLink: (node, entering) => { if (entering) { return [`<a href='${node.TokensStr()}' my-attr='自定义属性'>`, Lute.WalkContinue] } else { return ['</a>', Lute.WalkContinue] } }

其中 ${node.TokensStr()} 为超链接地址。可实际执行下来,${node.TokensStr()} 却总是空字符。渲染出的链接代码为: <a href my-attr='自定义属性'>觅好图</a>,href 后的链接地址为空。

仔细阅读官方文档,了解到链接属于行级节点,由链接相关标识符、链接文本等构成,其渲染函数为:

renderLink 及下级函数 renderOpenBracketrenderCloseBracketrenderOpenParenrenderCloseParenrenderLinkTextrenderLinkSpacerenderLinkDestrenderLinkTitle 组成。

也就是说,要将 markdown 的链接代码渲染为 html 超链接,Lute 引擎要调用以上所有函数。

简单分析一下调用链及函数之间的关系。

所有的函数都有两个参数,分别为 node 和 entering。node 为当前遍历到的节点,保存着当前节点的信息(renderLink 函数时,node 为根节点。renderLinkText 函数时,node 为子节点对象,保存着链接文本信息,renderOpenBracket 函数时,node 是另一个子节点对象,保存的是超链接 markdown 标识符 '[',其它函数都对应着超链接 markdown 代码的某部分的标识符,详见:链接)。entering 为遍历是否为进入节点。Lute 采用深度优先算法来遍历树,在进入节点时 entering 为 true,离开节点时为 false,我们可以简单理解为每个 render 函数都会被调用两次,entering 为 true 时表示第一次调用,false 时为第二次调用。如图:

render...renderLinkTextrenderLinkLuterender...renderLinkTextrenderLinkLute1. 进入(entering:true 第1次调)12. 进入(entering:true 第1次调)23. 离开(entering:false 第2次调)34. 进入(entering:true 第1次调)45. 离开(entering:false 第2次调)56. 进入(entering:false 第2次调)6

回到最初的代码,我在 entering 为 true 时返回了 <a href='${node.TokensStr()}' my-attr='自定义属性'>,为 false 时返回了 </a>,再由 Lute 将 renderLinkText 函数渲染的链接文字拼接在这两次调用的中间,就组成了完整的 html 超链接代码。可是,为什么中 renderLink 函数中,${node.TokenStr()} 取不到值呢?

按照上面的分析得知,在遍历不同 render 函数时,node 都是不同的对象。因此不能在 renderLink 函数的 node 中取到 renderLinkDest 函数解析后的值。

又因为 node 是嵌套引用结构(node -> node ->node),我们是不是也可以通过遍历 node 从中获取到正确的链接地址呢?遗憾的是,我没找到 node 有获取 child 和 parent 的属性/函数,望 V 大/D 大看到后能告知。这种情况下,我们又该如何实现目标呢?

根据 render 函数的调用顺序及相关特性,我的做法如下:

1. renderLinkDest 函数

该函数用于渲染超链接地址,可以在 node 参数中获取到链接地址。因此我将此时获取到的链接地址临时保存在变量 myLink 中,便于 renderLink 函数中使用。

renderLinkDest: (node, entering) => { if (entering) { this.myLink = node.TokensStr() return [``, Lute.WalkContinue] } return [``, Lute.WalkContinue] }

2. renderLink 函数

按照上面图中的调用顺序,在 entering 为 true 时,renderLinkDest 函数还没有执行,因此 myLink 为空,此时我们返回空 [``, Lute.WalkContinue]。而在 entering 为 false 时,renderLinkDest 已经执行完毕,因此变量 myLink 已有值,我们在此时返回完整的 html 超链接代码。

renderLink: (node, entering) => { if (entering) { return [``, Lute.WalkContinue] } else { return [`<a href='${this.myLink}' my-attr='自定义属性'>${node.Text()}</a>`, Lute.WalkContinue] } }

按照以上方法处理后,会渲染出如下代码:

<p>"觅好图"<a href='http://mihaotu.com' my-attr='自定义属性'>觅好图</a></p>

页面上多了一个链接文本,这是由于调用链中默认的 renderLinkText 函数渲染的结果。我们只要在该函数中返回空即可。如下:

renderLinkText: (node, entering) => { if (entering) { return ['', Lute.WalkContinue] } else { return ['', Lute.WalkContinue] } },

到此,在超链接中增加自定义属性的需求算是完成了。但有几个遗留问题:

  1. renderLink 在函数中无法通过 node.TokensStr() 获取链接地址,却可以通过 node.TokenLen() 获取字符个数(不清楚此时字符是什么);
  2. 无论 entering 为 true 还是 false,renderLink 函数中都可以通过 node.Text() 获取到链接文本。如果 node 是嵌套链结构,那应该取不到任何内容呀!
  3. node 的 child 和 parent 到底该如何获得?

建议:

为了便于开发,建议在所有 render 下级函数执行完毕后,都能将结果同步到根 note 中。或提供函数,获取相应的值。

题外话:

由于 method.min.js 和 index.min.js 不可同时引入,如果页面上有 Vditor 编辑器时该如何自定义渲染呢?官方文档好像没的写,这里记录一下,只要在 options.after() 中加入如下代码即可:

after: () => { this.contentEditor.vditor.lute.SetJSRenderers({ renderers: { Md2VditorIRDOM: { // 请根据不同的模式选择不同的渲染对象 renderLink: (node, entering) => { if (entering) { return [``, Lute.WalkContinue] } return [``, Lute.WalkContinue] } }, } }) }
  • Vditor

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

    366 引用 • 1842 回帖

相关帖子

欢迎来到这里!

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

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

    Lute 里面有很多 vditor 文档里面没有表现的节点信息,具体可以参考 lute 源码里面的渲染器 https://github.com/88250/lute/blob/master/render/html_renderer.go#L33

  • 其他回帖
  • Yu-Core

    node 有一个 ChildByType 函数,但是不知道怎么用。

    只能参考源码写了一个简单的

    41 是 renderLinkDest 的 node 的 Type 值,下面的链接里有各种对应

    lute/ast/node.go at master · 88250/lute (github.com)

    可惜 js 和 go 都不太会,没找到更好的办法。

    let c = ChildByType(node, 41); console.log(c.TokensStr()); function ChildByType(node, type) { let c = node.__internal_object__.FirstChild; while (c !== null) { if (c.Type === type) { return c; } c = c.Next; } return null; }
  • chaobei

    大佬,这个写法能用来保留 p 标签的 id 以及 class 属性吗?

  • HerbertHe

    Lute 的渲染逻辑是先分块级元素,再分行级元素,然后逐层嵌套进行渲染的。所有的文本内容到最后其实都是 renderText() 这个做的文本渲染,建议逐层自定义的方式进行实现。node.Text() 的属性是包含所有子节点的文本内容,并不能拿到链接。在给 Lute PR 的时候发现了这个问题,如果是列表的话,这个会拿到所有的子项源数据。node 并不完全是嵌套结构,标识符渲染、文本渲染其实是平级的。

推荐标签 标签

  • 脑图

    脑图又叫思维导图,是表达发散性思维的有效图形思维工具 ,它简单却又很有效,是一种实用性的思维工具。

    31 引用 • 97 回帖
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 109 关注
  • 反馈

    Communication channel for makers and users.

    126 引用 • 930 回帖 • 274 关注
  • 开源中国

    开源中国是目前中国最大的开源技术社区。传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。目前开源中国社区已收录超过两万款开源软件。

    7 引用 • 86 回帖 • 2 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 86 关注
  • 小说

    小说是以刻画人物形象为中心,通过完整的故事情节和环境描写来反映社会生活的文学体裁。

    31 引用 • 108 回帖
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 5 关注
  • OAuth

    OAuth 协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是 oAuth 的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此 oAuth 是安全的。oAuth 是 Open Authorization 的简写。

    36 引用 • 103 回帖 • 32 关注
  • V2Ray
    1 引用 • 15 回帖
  • wolai

    我来 wolai:不仅仅是未来的云端笔记!

    2 引用 • 14 回帖 • 2 关注
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    432 引用 • 1250 回帖 • 597 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖 • 1 关注
  • 支付宝

    支付宝是全球领先的独立第三方支付平台,致力于为广大用户提供安全快速的电子支付/网上支付/安全支付/手机支付体验,及转账收款/水电煤缴费/信用卡还款/AA 收款等生活服务应用。

    29 引用 • 347 回帖
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 30 关注
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3196 引用 • 8215 回帖
  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 724 关注
  • iOS

    iOS 是由苹果公司开发的移动操作系统,最早于 2007 年 1 月 9 日的 Macworld 大会上公布这个系统,最初是设计给 iPhone 使用的,后来陆续套用到 iPod touch、iPad 以及 Apple TV 等产品上。iOS 与苹果的 Mac OS X 操作系统一样,属于类 Unix 的商业操作系统。

    88 引用 • 139 回帖 • 1 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 2 关注
  • webpack

    webpack 是一个用于前端开发的模块加载器和打包工具,它能把各种资源,例如 JS、CSS(less/sass)、图片等都作为模块来使用和处理。

    42 引用 • 130 回帖 • 252 关注
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    124 引用 • 74 回帖 • 1 关注
  • 工具

    子曰:“工欲善其事,必先利其器。”

    298 引用 • 762 回帖
  • abitmean

    有点意思就行了

    38 关注
  • QQ

    1999 年 2 月腾讯正式推出“腾讯 QQ”,在线用户由 1999 年的 2 人(马化腾和张志东)到现在已经发展到上亿用户了,在线人数超过一亿,是目前使用最广泛的聊天软件之一。

    45 引用 • 557 回帖
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    79 引用 • 431 回帖
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    133 引用 • 1124 回帖 • 117 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    315 引用 • 547 回帖