Cirry's Blog

Astro开发自定义功能插件mdast-util-directive

2024-11-01
技术 astro
6分钟
1025字

说明

在冲浪的时候,发现别人的博客有各种不错的小功能,最近想给博客添加一个github-card功能,所以先从这开始给博客写点小功能吧。

之前使用过hexo主题,他们在实现功能的方式,就是在md的文档中用过花括号作为标记,就可以实现在文档中插入b站的视频,类似如下:

1
{% dplayer key=value ... %}

实现

在Astro中我们也可以有自己的标记来实现类似的功能。

比如这次我们要实现的就是使用如下的方式在md文档中添加github-card功能:

1
::github{repo="cirry/astro-yi"}

接下来说说mdast-util-directive,Astro已经默认集成了它,所以我们不用再去单独安装。

这个包可以帮我们识别md文档中的::::::开头的标记,分别对应的是文本指令,节点指令和容器指令。

本博客主题的旁白标记就是使用容器指令完成了,这次只说节点指令。

remark-github-card.js
1
export function remarkGithubCard() {
2
const transformer = (tree) => {
3
visit(tree, (node, index, parent) => {
4
// 查找md中的节点指令
5
if (node.type !== "leafDirective") {
6
return;
7
}
8
// 不为空节点,才能正常渲染需要替换的文本内容
9
if (!parent || index === undefined) {
10
return;
11
}
12
// 其中的github就是节点的名称,找到指定的节点内容进行替换
13
if (node.name !== "github") {
14
return;
15
}
26 collapsed lines
16
17
/**
18
* ::github{repo="cirry/astro"}
19
* 调试需要在 npm run build中的打印信息才能看到节点指令的具体参数
20
* 类型是leafDirective,
21
* 名称是github
22
* 传入的属性{ repo: 'cirry/astro-yi'}
23
* 没有子节点
24
* {
25
* type: 'leafDirective',
26
* name: 'github',
27
* attributes: { repo: 'cirry/astro-yi' },
28
* children: [],
29
* position: {
30
* start: { line: 6, column: 1, offset: 49 },
31
* end: { line: 6, column: 32, offset: 80 }
32
* }
33
* }
34
*/
35
36
// ... 省略了大段替换指令文本的代码
37
});
38
};
39
40
return () => transformer;
41
}

将暴露的remarkGithubCard,添加到astro.config.mjs中的remarkPlugins中:

astro-config.mjs
1
export default defineConfig({
2
// ... other config
3
markdown:{
4
remarkPlugins:[...otherPlugins, remarkGithubCard()]
5
}
6
7
})

拓展

根据以上功能,我们使用类似的方式来实现更多的功能,比如下面的功能等等。

1
::video[bilibili]{id="xxxxxxx"}
2
::video[youtube]{id="xxxxxx"}

附录

remark-github-card.js 源码
1
import {h as _h, s as _s} from "hastscript";
2
import {visit} from "unist-util-visit";
3
4
/** Hacky function that generates an mdast HTML tree ready for conversion to HTML by rehype. */
5
function h(el, attrs = {}, children = []) {
6
const {tagName, properties} = _h(el, attrs);
7
return {
8
type: "paragraph",
9
data: {hName: tagName, hProperties: properties},
10
children,
11
};
12
}
13
14
15
export function remarkGithubCard() {
130 collapsed lines
16
const transformer = (tree) => {
17
visit(tree, (node, index, parent) => {
18
if (node.type !== "leafDirective") {
19
return;
20
}
21
if (!parent || index === undefined) {
22
return;
23
}
24
if (node.name !== "github") {
25
return;
26
}
27
/**
28
* {
29
* type: 'leafDirective',
30
* name: 'github',
31
* attributes: { repo: 'cirry/astro-yi' },
32
* children: [],
33
* position: {
34
* start: { line: 6, column: 1, offset: 49 },
35
* end: { line: 6, column: 32, offset: 80 }
36
* }
37
* }
38
*/
39
40
const repo = node.attributes.repo ? node.attributes.repo : ''
41
if (!repo || !repo.includes('/')) {
42
return h(
43
'div',
44
{class: 'hidden'},
45
'Invalid repository. ("repo" attributte must be in the format "owner/repo")',
46
)
47
}
48
const author = repo.split('/')[0]
49
const repoName = repo.split('/')[1]
50
51
const cardUuid = `GC${Math.random().toString(36).slice(-6)}` // Collisions are not important
52
53
const nAvatar = h(`img#${cardUuid}-avatar`, {class: 'github-avatar mr-4',})
54
55
56
const nTitle = h('div', {class: 'flex items-center justify-between'}, [
57
h('a', {class: 'flex items-center text-inherit text-xl', href: `https://github.com/${repo}`, target: '_blank',}, [
58
nAvatar,
59
h('div', {class: ''}, [{type: "text", value: author}]),
60
h('div', {class: 'mx-1'}, [{type: "text", value: '/'}]),
61
h('div', {class: 'font-bold break-all truncate',}, [{type: "text", value: repoName}]),
62
]),
63
])
64
65
const nDescription = h(
66
`div#${cardUuid}-description`,
67
{class: 'my-2'}, [
68
{type: "text", value: 'Waiting for api.github.com...',},
69
]
70
)
71
72
const nStars = h('div', {class: 'flex items-center'}, [
73
h('i', {class: 'ri-star-line',}, []),
74
h(`div#${cardUuid}-stars`, {class: 'ml-1 mr-4'}, [{type: "text", value: "Waiting"}])
75
])
76
const nForks = h('div', {class: 'flex items-center'}, [
77
h('i', {class: 'ri-git-fork-line',}, []),
78
h(`div#${cardUuid}-forks`, {class: 'ml-1 mr-4'}, [{type: "text", value: "Waiting"}])
79
])
80
81
const nLicense = h('div', {class: 'flex items-center'}, [
82
h('i', {class: 'ri-copyright-line',}, []),
83
h(`div#${cardUuid}-license`, {class: 'ml-1 mr-4'}, [{type: "text", value: "Waiting"}])
84
])
85
const nScript = h(
86
`script#${cardUuid}-script`,
87
{type: 'text/javascript', defer: true},
88
[
89
{
90
type: "script", value: `
91
fetch('https://api.github.com/repos/${repo}', { referrerPolicy: "no-referrer" }).then(response => response.json()).then(data => {
92
if (data.description) {
93
document.getElementById('${cardUuid}-description').innerText = data.description.replace(/:[a-zA-Z0-9_]+:/g, '');
94
} else {
95
document.getElementById('${cardUuid}-description').innerText = "Description not set"
96
}
97
document.getElementById('${cardUuid}-forks').innerText = data.forks || 0;
98
document.getElementById('${cardUuid}-stars').innerText = data.watchers || 0;
99
const avatarEl = document.getElementById('${cardUuid}-avatar');
100
avatarEl.setAttribute("src", data.owner.avatar_url)
101
if (data.license?.spdx_id) {
102
document.getElementById('${cardUuid}-license').innerText = data.license?.spdx_id
103
} else {
104
document.getElementById('${cardUuid}-license').innerText = "No License"
105
};
106
document.getElementById('${cardUuid}-card').classList.remove("fetch-waiting");
107
console.log("[GITHUB-CARD] Loaded card for ${repo} | ${cardUuid}.")
108
}).catch(err => {
109
const c = document.getElementById('${cardUuid}-card');
110
c.classList.add("fetch-error");
111
console.warn("[GITHUB-CARD] (Error) Loading card for ${repo} | ${cardUuid}.")
112
}) `,
113
}]
114
)
115
116
// remove(node, (child) => {
117
// if (child.data && "directiveLabel" in child.data && child.data.directiveLabel) {
118
// return true;
119
// }
120
// });
121
// remove(node,child => {
122
// return true
123
// });
124
125
126
parent.children[index] = h(
127
`div#${cardUuid}-card`,
128
{
129
class: 'shadow w-auto flex flex-col bg-skin-card p-4 my-4 rounded-lg',
130
href: `https://github.com/${repo}`,
131
target: '_blank',
132
repo,
133
},
134
[
135
nTitle,
136
nDescription,
137
h('div', {class: 'flex'}, [nStars, nForks, nLicense]),
138
nScript
139
],
140
)
141
});
142
};
143
144
return () => transformer;
145
}
本文标题:Astro开发自定义功能插件mdast-util-directive
文章作者:Cirry
发布时间:2024-11-01
感谢大佬送来的咖啡☕
alipayQRCode
wechatQRCode