Obsidian 中的双括号链接是如何实现的?

前言

Obsidian 是一款非常流行的 markdown 本地笔记软件,其中有一个功能是其他笔记软件没有的,那就是双链笔记,通过链接在笔记之间建立关系,再通过可视化笔记关系,形成知识图谱,Obsidian 也因这个功能而知名。因此,我在想,是否能够让 MDX Editor 也拥有这个功能。

Obsidian 中各个笔记之间是通过双括号链接建立关系的,但双括号链接并不是标准的 markdown 语法,那在 markdown 中要如何实现这个功能呢?

Markdown 编译过程

Markdown 的解析过程需要用到 remarkrehype

  1. Markdown 文本被解析为 Markdown AST

  2. Markdown AST 可以由多个 remark plugins 操作

  3. Markdown AST 转换为 HTML AST

  4. 出于安全原因,HTML AST 需要被 sanitized

  5. HTML AST 可以由多个 rehype plugins 操作

  6. HTML AST 被字符串化为 HTML

  7. HTML 渲染后的一些特除操作处理

整个编译过程如以下流程图:

实现

通过 astexplorer 这个网站,可以直接查看 Markdown AST。

比如有以下一个最简单的 Markdown 文本。

注意在顶部选择解析器为 Markdown 和 remark。

在右侧可以直接查看 Markdown AST。

在其底部有个最简单的 remark 插件示例

js 复制代码
// available utilities are: "unist-util-is", "unist-util-visit", and "unist-util-visit-parents"
const visit = require("unist-util-visit");

module.exports = function attacher(options) {
  return function transformer(tree, vfile) {
    // add a level to headings, for example `# heading` to `## heading`
    visit(tree, "heading", (node) => {
      node.depth += 1
    });
    return tree;
  };
};

这个插件可以将 标题一(H1) 转换成标题二(H2)

双括号文本转换

一个双括号链接的文本转换成 AST

如下图所示

因此我们可以通过一个 remark Plugin 来实现转换

也就是需要转换成如下 md

转换成如下图这样一个 AST 结构

通过一个正则表达式来匹配 AST 中的 Text 节点

js 复制代码
const parenthesisRegexExclusive = /(?<=\[\[).*?(?=\]\])/g
const matches = value.match(parenthesisRegexExclusive)

我们可以实现一个 remark 插件,将双括号中的文本转为链接

js 复制代码
const visit = require("unist-util-visit");

module.exports = function attacher(options) {
  return function transformer(tree, vfile) {
    visit(tree, 'text', (node, index, parent) => {
      const value = node.value

      if (
        typeof value !== 'string' ||
        !parent ||
        !Array.isArray(parent.children) ||
        parent.type === 'link' ||
        parent.type === 'linkReference'
      ) {
        return
      }

      const parenthesisRegexExclusive = /(?<=\[\[).*?(?=\]\])/g
      const matches = value.match(parenthesisRegexExclusive)

      if (!matches) {
        return
      }

      const children = [value]
      matches.forEach((match) => {
        const last = children.pop()
        if (typeof last !== 'string') {
          return
        }
        const split = `[[${match}]]`
        const [first, ...rest] = last.split(split)
        children.push(first, { id: match }, rest.join(split))
      })

      parent.children.splice(
        index,
        1,
        ...children.map((child) => {
          if (typeof child === 'string') {
            return {
              type: 'text',
              value: child,
            }
          }
          return {
            type: 'link',
            url: child.id,
            children: [{ type: 'text', value: child.id }],
          }
        })
      )
    })
    return tree;
  };
};

实现地址

astexplorer.net/#/gist/5b6a...

最后可以在渲染的 html 的中添加脚本,阻止非 HTTP 开头的链接。

js 复制代码
document.body.addEventListener("click", (event) => {
  event.preventDefault();
  let el = event.target;
  if (!el || el.nodeName !== "A" || !el.getAttribute("href")) return;

  const href = el.getAttribute("href");
  // http 开头是远程地址
  if (/^https?:\/\//.test(href)) {
    openLink(href);
  } else {
      // 通过文件名找当前的目录地址
  }
});

这样就实现了双括号链接。

最后

MDX Editor 桌面 App 已实现了双括号链接,可以在 Github 下载桌面版体验。如果你对实现过程感兴趣,也可以直接查看源码

相关推荐
1024小神17 分钟前
更改github action工作流的权限
前端·javascript
Epicurus21 分钟前
JavaScript无阻塞加载的方式
前端·javascript
1024小神24 分钟前
tauri程序使用github action发布linux中arm架构
前端·javascript
LAM LAB29 分钟前
【VBA】WPS/PPT设置标题字体
javascript·powerpoint·vba·wps
JYeontu30 分钟前
实现一个带@功能的输入框组件
前端·javascript·vue.js
前端大卫1 小时前
Echarts 饼图的创新绘制技巧(附 Demo 和源码)
前端·javascript·echarts
jeff渣渣富1 小时前
使用 AST 处理输入字段的关联计算问题
前端·javascript
百度地图开放平台1 小时前
LBS 开发微课堂|智能调度API升级:解决循环取货场景下的调度难题
前端·javascript
光影少年3 小时前
es6+新增特性有哪些
前端·javascript·es6
GDAL3 小时前
Better-SQLite3 参数绑定详解
javascript·sqlite3