我可以说自己也是颜狗吗?一个独立博客的界面是否好看,多多少少都显示出博主本人的审美和品味。
举例来说,陈皓先生的酷壳,可以看到中年男人特有的内敛与落落大方的举止;椒盐豆豉和 Owen 的博客都有一种粗糙中的精细,蕴藏着质朴的知性美;小胡同学的印记,可以看出年轻人的不羁与传统家教的底色在相互碰撞;大宇的 Another Dayu,如同他的摄影作品一样透露着沉着冷静;阿杰的 Jack’s Space 则是一个可爱大男孩儿的既视感……一个人的博客观感,往往在不经意间透露给读者许多轻描淡写的隐喻。
我曾想在原博客框架中复刻 AstroPaper 主题,奈何能力有限,就干脆认真贯彻拿来主义的精神,将博客框架从 Hugo 迁移到了 Astro。
「人类发展的历程往往是波浪式前进与螺旋式上升。」在应用这个主题时,充分印证了这个哲学命题。
Table of contents
Open Table of contents
时区的纠结
使用 Astro 搭建的博客,大多数采用了 ISO 8601 的时间书写格式用于显示文章的发布或修改日期。以我使用的 AstroPaper 主题为例,文章的 frontmatter 中 pubDatatime
的写法是 2024-03-27T12:00:00+08:00
,表示的是国际标准时间(UTC)中的一个具体时刻,即 2024 年 3 月 27 日上午 12 点整。“+08:00” 代表的是东八区,即北京时间。
主题的 src/components/Datetime.tsx
源代码,会根据访问者所在地显示文章的时区。这就导致了如果从中国大陆用美西的代理访问我的博客,显示的文章发布时间为美西时间。而访问者本身并不是在美国,看到的发布时间就会奇怪。
所以,我干脆修改了这段代码,使其强制显示为北京时间。对此,我在 About 中也做了说明。
Because my blog doesn’t feature English content yet, I’ve adjusted the theme’s source code to set the time zone to GMT+8. This ensures that all the timestamps on the articles correspond to Beijing time.
修改后的代码如下:
import { LOCALE } from "@config";
interface DatetimesProps {
pubDatetime: string | Date;
modDatetime: string | Date | undefined | null;
}
interface Props extends DatetimesProps {
size?: "sm" | "lg";
className?: string;
}
export default function Datetime({
pubDatetime,
modDatetime,
size = "sm",
className,
}: Props) {
return (
<div className={`flex items-center space-x-2 opacity-80 ${className}`}>
<svg
xmlns="http://www.w3.org/2000/svg"
className={`${
size === "sm" ? "scale-90" : "scale-100"
} inline-block h-6 w-6 min-w-[1.375rem] fill-skin-base`}
aria-hidden="true"
>
<path d="M7 11h2v2H7zm0 4h2v2H7zm4-4h2v2h-2zm0 4h2v2h-2zm4-4h2v2h-2zm0 4h2v2h-2z"></path>
<path d="M5 22h14c1.103 0 2-.897 2-2V6c0-1.103-.897-2-2-2h-2V2h-2v2H9V2H7v2H5c-1.103 0-2 .897-2 2v14c0 1.103.897 2 2 2zM19 8l.001 12H5V8h14z"></path>
</svg>
{modDatetime && modDatetime > pubDatetime ? (
<span className={`italic ${size === "sm" ? "text-sm" : "text-base"}`}>
Updated:
</span>
) : (
<span className="sr-only">Published:</span>
)}
<span className={`italic ${size === "sm" ? "text-sm" : "text-base"}`}>
<FormattedDatetime
pubDatetime={pubDatetime}
modDatetime={modDatetime}
/>
</span>
</div>
);
}
const FormattedDatetime = ({ pubDatetime, modDatetime }: DatetimesProps) => {
const myDatetime = new Date(
modDatetime && modDatetime > pubDatetime ? modDatetime : pubDatetime
);
const bjDatetime = new Date(
myDatetime.toLocaleString("en-US", { timeZone: "Asia/Shanghai" })
);
const date = bjDatetime.toLocaleDateString(LOCALE.langTag, {
year: "numeric",
month: "short",
day: "numeric",
});
const time = bjDatetime.toLocaleTimeString(LOCALE.langTag, {
hour: "2-digit",
minute: "2-digit",
});
return (
<>
<time dateTime={bjDatetime.toISOString()}>{date}</time>
<span aria-hidden="true"> | </span>
<span className="sr-only"> at </span>
<span className="text-nowrap">{time}</span>
</>
);
};
Open Graph
在主题的教程文档 Dynamic OG image generation in AstroPaper blog posts 中,主题的创作者说 Satori 在自动生成文章的动态 OG Image 时,如果文章标题不是英文的,显示效果可能不好。
这哪里只是不好,简直……一……言……难……尽,好不好!!!
虽然,OG Image 说到底只是在社交媒体进行分享时好看一些罢了,有没有并不关键。但对于主要以中文创作的我来说,这种 OG Image 还是无法忍受的。
我试图修改代码,在 generateOgImages.tsx 中尝试使用 Sarasa Gothic SC 和 Source Han Sans,但是在部署时都出现报错。询问 AI 后,给出的解决办法是将字体下载到 public/font 文件夹中,从本地加载。想起之前折腾 Nobelium 时也是这样操作,会让网页加载变慢,并且消耗很多资源。遂另寻他路。
「只要思想不滑坡,方法总比困难多。」自动生成的 OG Image 无非就是不需要花费时间去为文章制作头图。想必很多博主都有这样的经历,花费不了多少时间就写好了文章,结果制作头图花费的时间比写作还多。想起来就觉得可怕。
想到自己的图库里有几张之前使用 Typecho 时的图片,那我为什么不将它们当成 OG Image 轮流用呢?无非是在文章的 frontmatter 中增加一个 ogImage
的属性而已。frontmatter 也可以通过 Raycast Snippet 设置,并不需要我去记忆。
是不是赏心悦目了起来。
评论组件
这次原本想要使用 Remark42 作为评论组件,奈何我查了很多资料,问了 AI,也还是没有搞清楚到底怎么操作。只能继续沿用我在 Hugo 中使用的 Twikoo。
要在 Astro 中使用 Twikoo 作为评论组件,步骤相对简单,却是我踩坑最多的。具体踩坑的过程就不详述了,只给出方法。
🙏 感谢 1900 提供的帮助。
此段原内容已删除,仅保留新内容。
1900 哥哥给我看了一篇文章,里面提到了通过创建新的组件模板引入评论组件的方式。
首先,在 src/components
中新建 Comments.astro
文件,作为组件模板。
然后添加如下代码:
<div id="tcomment"></div>
<script>
document.addEventListener('astro:page-load', () => {
function loadTwikoo() {
const commentsContainer = document.getElementById('tcomment');
if (commentsContainer) {
const script = document.createElement('script');
script.src = 'https://cdn.staticfile.org/twikoo/1.6.32/twikoo.all.min.js';
script.async = true;
script.onload = () => {
const initScript = document.createElement('script');
initScript.innerHTML = `
twikoo.init({
envId: '您的环境 ID',
el: '#tcomment',
});
`;
document.body.appendChild(initScript);
};
document.body.appendChild(script);
}
}
loadTwikoo();
});
</script>
接着,到需要引入评论组件的模板中插入 <Comments />
组件,例如我在 PostDetails.astro
和 AboutLayout.astro
中做了修改。
---
<!--引入的其他组件-->
import Comments from "@components/Comments.astro";
---
<!--其他代码-->
<Comments /> //这里引入 Comments 组件,放置在 </main> 标签之前
</main>
<Footer />
</Layout>
但是,此时会发现评论组件的显示明显不美观,占据了整个页面的宽度。
所以,此时需要在 Comment.astro
中新增一段 <style>
去规定显示的格式。那么完整的评论模板的代码为:
<div id="tcomment"></div>
<style is:global>
main,
#comment {
@apply mx-auto w-full max-w-3xl px-4 pb-12;
}
</style>
<script>
document.addEventListener('astro:page-load', () => {
function loadTwikoo() {
const commentsContainer = document.getElementById('tcomment');
if (commentsContainer) {
const script = document.createElement('script');
script.src = 'https://cdn.staticfile.org/twikoo/1.6.32/twikoo.all.min.js';
script.async = true;
script.onload = () => {
const initScript = document.createElement('script');
initScript.innerHTML = `
twikoo.init({
envId: '您的环境 ID',
el: '#tcomment',
});
`;
document.body.appendChild(initScript);
};
document.body.appendChild(script);
}
}
loadTwikoo();
});
</script>
这样,评论组件就设置好,并且可以跟随页面一起启用了。
RSS 全文输出
AstroPaper 主题中,RSS 并不输出全文。对于使用 RSS 订阅了博客的读者而言并不友好。所以,我还更改了 src/pages/rss.xml.ts
,并且只获取最新的十篇文章。
这里需要注意的是,我引入了一个新的依赖 marked,需要先在项目文件中通过 npm install marked
或 yarn add marked
安装这个库。
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
import getSortedPosts from "@utils/getSortedPosts";
import { SITE } from "@config";
// 引入 marked 依赖,通过 npm install marked 安装
import { marked } from "marked";
export async function GET() {
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
// 只获取最新的10篇文章
const latestPosts = sortedPosts.slice(0, 10);
return rss({
title: SITE.title,
description: SITE.desc,
site: SITE.website,
items: latestPosts.map(({ data, slug, body }) => {
// 移除 "## Table of Contents" 部分
const cleanedBody = body.replace(
/## Table of Contents\s*([\s\S]*?)(?=\n## |\n# |$)/g,
""
);
return {
link: `posts/${slug}/`,
title: data.title,
description: data.description,
pubDate: new Date(data.modDatetime ?? data.pubDatetime),
content: marked(cleanedBody),
};
}),
});
}
其他的一些调整
余下的一些调整就是很常规的操作。例如,在 src/styles/base.css
中修改主题配色;在 src/components/Header.astro
中,为博客添加 Umami 代码,修改导航栏的内容;在 src/components/Footer.astro
中,增加 Copyright 声明等。
此外,还编辑了一个新的页面模板,用于博客的一些独立页面。
整体而言,此次迁移博客的框架还算顺利,中间虽然有些曲折,好在大多数都解决了。余下的也并不影响使用,留待日后慢慢解决吧。