跳到主要内容

用 Giscus 给 Docusaurus 博客增加评论区

· 阅读需 11 分钟
Yikai Cai
不知道该写什么的写博客爱好者

使用 Giscus 为你在 GitHub 上的博客添加一个评论区,同时尽量少地引入 GitHub 以外的第三方。

顺便介绍了通过 Docusaurus 的 API ,在切换博客的亮/暗模式时,同步切换 Giscus 主题的方式。


我想给我的博客增加一个评论区了。

既然我的博客文章全部存储 GitHub 上,我希望它的评论系统也能尽量基于 GitHub ,尽量不要引入 GitHub 之外的第三方。

选择 Giscus 的原因是 Giscus 以 GitHub Discussion 作为唯一数据源,而且评论者身份和 GitHub 账号绑定,不像其他评论系统可能需要另外部署数据库或者使用第三方 Auth 。

如果能做到纯客户端实现,只在用户侧调用 GitHub API 来实现所有功能,就更好了。可惜 Giscus 仍然需要在第三方 Host 一个服务来做这件事,但是算了,无所谓。

如果你没有听说过 Giscus​

Giscus 是一个基于 GitHub Discussion 的评论系统。而 giscus.app 是 Giscus 的文档兼脚本生成器。

跟随 giscus.app 上的指引,我们最终会得到一个 HTML 元素,将这个元素插入到网页源代码中,Giscus 就出现啦。

如果你不喜欢直接写 HTML 元素,或者正在使用某个前端框架,你也可以使用 Giscus Component 。比如你现在正在看的这个博客使用的框架是 Docusaurus ,而这个框架是基于 React 的,所以我使用了 @giscus/react 。

所以我们总共需要做三件事:

  1. 在 GitHub 上的,我们的博客仓库中,启用 Discussions ,并且(可选)创建一个 Giscus 专属的 Discussion Category 来将 Giscus 发布的内容与其他内容区分开。这个 Category 最好是 Announcement 类型的(其中的内容只能由仓库维护者和 Giscus 创建)。
  2. 使用 giscus.app 来为我们在 GitHub 上的博客仓库生成一个对应的评论区 HTML 元素代码;
  3. 把这个 HTML 代码翻译成对应的 Giscus Component ,嵌入我们的博客中。

Giscus 的配置该怎么选​

giscus.app 上对于每个配置项都有详细的描述,这里就不全部展开了,挑重点说。

页面和 Discussion 的对应关系​

我选择的是 URL ,原因是我的博客可能有多份部署(比如 PR Preview 部署在 Vercel 上),而我希望将不同部署之间的评论区互相隔离。

在 Giscus 提供的选项中,只有 URL 恰好支持了这个需求。不过代价是 GitHub Discussion 的标题会是 Blog Post 的 URL 而不是标题,在 GitHub 里面看起来比较丑(不过又有谁会在 GitHub 中查看博客的评论区,而不是直接在博客中查看呢)。

主题​

这个选项改动之后,在页面下方可以看到实时预览。

我们的博客目前有明亮和暗黑两种颜色模式,我们希望读者切换 Docusaurus 的日夜模式后,Giscus 也能切换到对应的主题。

实现这个功能的方法下文会描述,这里我们先尝试下所有选项,分别为明亮和暗黑模式找到对应的合适的主题,然后记住下面代码中的 theme ,等下要用。

在 Docusaurus 中嵌入我们的评论区组件​

Giscus Component 的文档详细描述了将 HTML 元素翻译成对应组件的方法。

对照着文档和上面生成的 HTML 元素代码填空之后,我们得到了一个 Giscus React 组件:

import Giscus from '@giscus/react';

const GiscusContainer = (
<Giscus
{/* ...... */}
mapping="url"
theme={/* light or dark_dimmed */}
/>
);

接下来要做的事情就是将这个组件插入到 Docusaurus 中。如果你也在使用 Docusaurus ,并且对于魔改 Docusaurus 组件的方法并不熟悉,你可以参考 Docusaurus 官方魔改教程。

Docusaurus 提供了两种魔改的方式:Ejecting 和 Wrapping 。简单来说就是:

  • Ejecting :将 Docusaurus 某个组件的代码复制一份,开发者在其上进行修改。这是真正的魔改,自由度最高,但缺点是大量的组件代码被复制出来,会造成代码仓库体积的膨胀。
  • Wrapping :在 Docusaurus 某个组件的外层创建一个 Wrapper ,比较优雅。大部分时候 Wrapping 就够用了。

这里,我们希望在博客页面的下方添加我们的评论区组件,所以我们可以 Wrap 展示「博客内容」的组件,在博客内容的下方加上 Giscus 的代码。

我们使用的是 Docusaurus 默认的博客插件,需要 Wrap @docusaurus/theme-classic 这个主题的 BlogPostItem 组件。

# 以 npm 为例
npm run -- docusaurus swizzle @docusaurus/theme-classic BlogPostItem --wrap

项目根目录中会出现这些文件:

src
└── theme
└── BlogPostItem
└── index.tsx

打开 src/theme/BlogPostItem/index.tsx ,在 BlogPostItem 之后添加我们的 Giscus React 组件,打开我们的 Docusaurus 本地预览,我们就能看到博文下方的评论区啦!

然而预览之后会发现,博文下方确实出现了评论区,但是首页(博文目录)的每一个摘要下方也出现了评论区…… 🤡

限制 Giscus 只在具体博文下方显示​

Docusaurus 默认主题提供了一个 Internal API 叫做 useBlogPost() ,可以用来查询当前页面属于博文目录还是具体的博文页面。

因为是 Internal API ,所以文档里面没有关于这个 API 的描述 🤡 ,我在 Docusaurus 的 GitHub Repo 里面搜罗了一圈才找到相关的内容:

我们通过这个 API 来拿到当前页面所属的类别,并在此基础上控制 Giscus 的显示:

// other imports......
import { useBlogPost } from '@docusaurus/plugin-content-blog/client';

export default function BlogPostItemWrapper(props: Props): JSX.Element {
const { isBlogPostPage } = useBlogPost();
const GiscusContainer = (
<Giscus
{/* ...... */}
/>
);
return (
<>
<BlogPostItem {...props} />
{/* Prevent giscus to show up on blog post menu */}
{isBlogPostPage && GiscusContainer}
</>
);
}

再次打开预览,此时的 Giscus 应该仅在博文页面显示。我们也可以尝试写一些评论,会发现 GitHub 中出现了标题为 http://localhost:3000/...... 的 Discussions ,符合预期。接下来只需要加上我们的日夜模式切换功能,就大功告成啦。

根据当前颜色模式切换 Giscus Theme​

这个方法比较省力。这个博客(20240826)使用的也是这个方法。下文还会介绍一种可能更加优雅的方法。

Docusaurus 提供了 useColorMode() 来获取当前读者选择的颜色模式。

这个 API 的文档藏在默认主题的文档里,而不是默认主题的博客插件的文档里,导致我读的时候直接略过了……最后又是到 Docusaurus 的 GitHub Repo 里找了一通,才找到了相关内容:

我们先定义好 Docusaurus ColorMode和 Giscus Theme 之间的映射,再通过这个 API 来拿到当前读者选择的 ColorMode ,并让 Giscus 使用与之对应 Theme :

// other imports......
import { useColorMode, ColorMode } from '@docusaurus/theme-common';
import Giscus, { Theme } from '@giscus/react';

const DocusaurusColorModeToGiscusTheme: Record<ColorMode, Theme> = {
light: 'light',
dark: 'dark_dimmed'
};

export default function BlogPostItemWrapper(props: Props): JSX.Element {
// ......

const { colorMode } = useColorMode();
const giscusTheme = DocusaurusColorModeToGiscusTheme[colorMode];

const GiscusContainer = (
<Giscus
{/* ...... */}
theme={giscusTheme}
/>
);

// ......
}

再次打开预览测试,此时 Giscus Theme 应该会随着 Docusaurus ColorMode 的切换而切换。唯一的一点小瑕疵是 Giscus 组件的颜色变换是瞬间完成的,不像 Docusaurus 内部其他组件一样有过渡动画。

另一种让 Giscus 变色的方式​

这个方法是我写这篇文章的时候脑补出来的,还没实现过,留作习题供读者尝试(doge)。

简单来说就是 Giscus 的主题可以是自定义的 CSS 。而我们知道在 CSS 中实现日夜切换的常规操作是通过 CSS 变量,让组件在不同模式下继承不同的变量得到不同的前景色和背景色。

那么这里理论上更优雅的实现方式,是我们为 Giscus 编写一套 Docusaurus Theme ,将 Giscus 所有颜色交由 Docusaurus 管理,这样可以让 Giscus 和 Docusaurus 更加融合统一。

结语​

我们使用 Docusaurus Swizzling 将 Giscus 嵌入了 Docusaurus 中,并添加了日夜颜色切换的功能,接下来就可以通过这个评论区愉快地和小伙伴们聊天啦!(现实是根本没有小伙伴 QAQ)