Skip to content

你好 AstroPaper

Updated: at 18:41

我可以说自己也是颜狗吗?一个独立博客的界面是否好看,多多少少都显示出博主本人的审美和品味。

举例来说,陈皓先生的酷壳,可以看到中年男人特有的内敛与落落大方的举止;椒盐豆豉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">&nbsp;at&nbsp;</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.astroAboutLayout.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 markedyarn 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 声明等。

此外,还编辑了一个新的页面模板,用于博客的一些独立页面。

整体而言,此次迁移博客的框架还算顺利,中间虽然有些曲折,好在大多数都解决了。余下的也并不影响使用,留待日后慢慢解决吧。