Cirry's Blog

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

Nov 1, 2024
技术 astro
6分钟
1025字

说明

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

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

{% dplayer key=value ... %}

实现

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

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

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

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

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

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

remark-github-card.js
export function remarkGithubCard() {
const transformer = (tree) => {
visit(tree, (node, index, parent) => {
// 查找md中的节点指令
if (node.type !== "leafDirective") {
return;
}
// 不为空节点,才能正常渲染需要替换的文本内容
if (!parent || index === undefined) {
return;
}
// 其中的github就是节点的名称,找到指定的节点内容进行替换
if (node.name !== "github") {
return;
}
26 collapsed lines
/**
* ::github{repo="cirry/astro"}
* 调试需要在 npm run build中的打印信息才能看到节点指令的具体参数
* 类型是leafDirective,
* 名称是github
* 传入的属性{ repo: 'cirry/astro-yi'}
* 没有子节点
* {
* type: 'leafDirective',
* name: 'github',
* attributes: { repo: 'cirry/astro-yi' },
* children: [],
* position: {
* start: { line: 6, column: 1, offset: 49 },
* end: { line: 6, column: 32, offset: 80 }
* }
* }
*/
// ... 省略了大段替换指令文本的代码
});
};
return () => transformer;
}

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

astro-config.mjs
export default defineConfig({
// ... other config
markdown:{
remarkPlugins:[...otherPlugins, remarkGithubCard()]
}
})

拓展

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

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

附录

remark-github-card.js 源码
import {h as _h, s as _s} from "hastscript";
import {visit} from "unist-util-visit";
/** Hacky function that generates an mdast HTML tree ready for conversion to HTML by rehype. */
function h(el, attrs = {}, children = []) {
const {tagName, properties} = _h(el, attrs);
return {
type: "paragraph",
data: {hName: tagName, hProperties: properties},
children,
};
}
export function remarkGithubCard() {
130 collapsed lines
const transformer = (tree) => {
visit(tree, (node, index, parent) => {
if (node.type !== "leafDirective") {
return;
}
if (!parent || index === undefined) {
return;
}
if (node.name !== "github") {
return;
}
/**
* {
* type: 'leafDirective',
* name: 'github',
* attributes: { repo: 'cirry/astro-yi' },
* children: [],
* position: {
* start: { line: 6, column: 1, offset: 49 },
* end: { line: 6, column: 32, offset: 80 }
* }
* }
*/
const repo = node.attributes.repo ? node.attributes.repo : ''
if (!repo || !repo.includes('/')) {
return h(
'div',
{class: 'hidden'},
'Invalid repository. ("repo" attributte must be in the format "owner/repo")',
)
}
const author = repo.split('/')[0]
const repoName = repo.split('/')[1]
const cardUuid = `GC${Math.random().toString(36).slice(-6)}` // Collisions are not important
const nAvatar = h(`img#${cardUuid}-avatar`, {class: 'github-avatar mr-4',})
const nTitle = h('div', {class: 'flex items-center justify-between'}, [
h('a', {class: 'flex items-center text-inherit text-xl', href: `https://github.com/${repo}`, target: '_blank',}, [
nAvatar,
h('div', {class: ''}, [{type: "text", value: author}]),
h('div', {class: 'mx-1'}, [{type: "text", value: '/'}]),
h('div', {class: 'font-bold break-all truncate',}, [{type: "text", value: repoName}]),
]),
])
const nDescription = h(
`div#${cardUuid}-description`,
{class: 'my-2'}, [
{type: "text", value: 'Waiting for api.github.com...',},
]
)
const nStars = h('div', {class: 'flex items-center'}, [
h('i', {class: 'ri-star-line',}, []),
h(`div#${cardUuid}-stars`, {class: 'ml-1 mr-4'}, [{type: "text", value: "Waiting"}])
])
const nForks = h('div', {class: 'flex items-center'}, [
h('i', {class: 'ri-git-fork-line',}, []),
h(`div#${cardUuid}-forks`, {class: 'ml-1 mr-4'}, [{type: "text", value: "Waiting"}])
])
const nLicense = h('div', {class: 'flex items-center'}, [
h('i', {class: 'ri-copyright-line',}, []),
h(`div#${cardUuid}-license`, {class: 'ml-1 mr-4'}, [{type: "text", value: "Waiting"}])
])
const nScript = h(
`script#${cardUuid}-script`,
{type: 'text/javascript', defer: true},
[
{
type: "script", value: `
fetch('https://api.github.com/repos/${repo}', { referrerPolicy: "no-referrer" }).then(response => response.json()).then(data => {
if (data.description) {
document.getElementById('${cardUuid}-description').innerText = data.description.replace(/:[a-zA-Z0-9_]+:/g, '');
} else {
document.getElementById('${cardUuid}-description').innerText = "Description not set"
}
document.getElementById('${cardUuid}-forks').innerText = data.forks || 0;
document.getElementById('${cardUuid}-stars').innerText = data.watchers || 0;
const avatarEl = document.getElementById('${cardUuid}-avatar');
avatarEl.setAttribute("src", data.owner.avatar_url)
if (data.license?.spdx_id) {
document.getElementById('${cardUuid}-license').innerText = data.license?.spdx_id
} else {
document.getElementById('${cardUuid}-license').innerText = "No License"
};
document.getElementById('${cardUuid}-card').classList.remove("fetch-waiting");
console.log("[GITHUB-CARD] Loaded card for ${repo} | ${cardUuid}.")
}).catch(err => {
const c = document.getElementById('${cardUuid}-card');
c.classList.add("fetch-error");
console.warn("[GITHUB-CARD] (Error) Loading card for ${repo} | ${cardUuid}.")
}) `,
}]
)
// remove(node, (child) => {
// if (child.data && "directiveLabel" in child.data && child.data.directiveLabel) {
// return true;
// }
// });
// remove(node,child => {
// return true
// });
parent.children[index] = h(
`div#${cardUuid}-card`,
{
class: 'shadow w-auto flex flex-col bg-skin-card p-4 my-4 rounded-lg',
href: `https://github.com/${repo}`,
target: '_blank',
repo,
},
[
nTitle,
nDescription,
h('div', {class: 'flex'}, [nStars, nForks, nLicense]),
nScript
],
)
});
};
return () => transformer;
}
本文标题:Astro开发自定义功能插件mdast-util-directive
文章作者:Cirry
发布时间:Nov 1, 2024
感谢大佬送来的咖啡☕
alipayQRCode
wechatQRCode
总访问量
总访客数人次