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

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

业务需要,须在 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 时为第二次调用。如图:

sequenceDiagram Lute->>renderLink: 1. 进入(entering:true 第1次调) Lute->>renderLinkText: 2. 进入(entering:true 第1次调) Lute->>renderLinkText: 3. 离开(entering:false 第2次调) Lute->>render...: 4. 进入(entering:true 第1次调) Lute->>render...: 5. 离开(entering:false 第2次调) Lute->>renderLink: 6. 进入(entering:false 第2次调)

回到最初的代码,我在 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。

    355 引用 • 1825 回帖 • 1 关注

相关帖子

欢迎来到这里!

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

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

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

  • 其他回帖
  • HerbertHe

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

  • 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 属性吗?

推荐标签 标签

  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    20 引用 • 7 回帖 • 2 关注
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖 • 3 关注
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 60 关注
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    110 引用 • 54 回帖 • 4 关注
  • IBM

    IBM(国际商业机器公司)或万国商业机器公司,简称 IBM(International Business Machines Corporation),总公司在纽约州阿蒙克市。1911 年托马斯·沃森创立于美国,是全球最大的信息技术和业务解决方案公司,拥有全球雇员 30 多万人,业务遍及 160 多个国家和地区。

    17 引用 • 53 回帖 • 140 关注
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 3 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 535 关注
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    23089 引用 • 92929 回帖
  • Spring

    Spring 是一个开源框架,是于 2003 年兴起的一个轻量级的 Java 开发框架,由 Rod Johnson 在其著作《Expert One-On-One J2EE Development and Design》中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。框架的主要优势之一就是其分层架构,分层架构允许使用者选择使用哪一个组件,同时为 JavaEE 应用程序开发提供集成的框架。

    943 引用 • 1460 回帖 • 1 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    728 引用 • 1275 回帖
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 25 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    149 引用 • 257 回帖
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    98 引用 • 344 回帖
  • Lute

    Lute 是一款结构化的 Markdown 引擎,支持 Go 和 JavaScript。

    26 引用 • 196 回帖 • 17 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    200 引用 • 120 回帖
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    83 引用 • 37 回帖
  • 996
    13 引用 • 200 回帖 • 11 关注
  • wolai

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

    2 引用 • 14 回帖 • 1 关注
  • OpenResty

    OpenResty 是一个基于 NGINX 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    17 引用 • 40 关注
  • GraphQL

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

    4 引用 • 3 回帖 • 8 关注
  • Notion

    Notion - The all-in-one workspace for your notes, tasks, wikis, and databases.

    7 引用 • 40 回帖
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    209 引用 • 358 回帖
  • 导航

    各种网址链接、内容导航。

    42 引用 • 175 回帖 • 2 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 684 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖 • 2 关注
  • CodeMirror
    1 引用 • 2 回帖 • 130 关注
  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    230 引用 • 1454 回帖