跳到主要内容

如何添加评论区

获取 Giscus 配置参数

首先根据 Giscus 官网 的步骤配置安装并获取配置后的参数。下载Giscus链接:GitHub Apps - giscus

选择 giscus 连接到的仓库。请确保:

  1. 此仓库是公开的,否则访客将无法查看 discussion。
  2. giscus app 已安装否则访客将无法评论和回应。
  3. Discussions 功能已在你的仓库中启用。

安装所需要的包

首先,如果你不能使用yarn命令行,请先安装

npm install --global yarn

检查

yarn --version

安装包

yarn add @giscus/react mitt

封装评论组件

配置生命周期函数

由于 Docusaurus bug 导致 Giscus 有时获取的仍然是上一篇文章的评论,为解决这一问题我们创建一个 clientModule,在src文件夹新建一个clientModules文件,然后在该文件夹新建routeModules.ts文件

src/clientModules/routeModules.ts

import mitt from 'mitt';
import ExecutionEnvironment from '@docusaurus/ExecutionEnvironment';

const emitter = mitt();

if (ExecutionEnvironment.canUseDOM) {
window.emitter = emitter;
}

export function onRouteDidUpdate() {
if (ExecutionEnvironment.canUseDOM) {
setTimeout(() => {
window.emitter.emit('onRouteDidUpdate');
});
}
// https://github.com/facebook/docusaurus/issues/8278
}

创建一个组件

src/components/comment/index.tsx(同样是新建出来的)

import React, { forwardRef, useEffect, useState } from 'react';
import BrowserOnly from '@docusaurus/BrowserOnly';
import Giscus, { GiscusProps } from '@giscus/react';
import {
useThemeConfig,
useColorMode,
ThemeConfig
} from '@docusaurus/theme-common';
interface CustomThemeConfig extends ThemeConfig {
giscus: GiscusProps & { darkTheme: string };
}

export const Comment = forwardRef<HTMLDivElement>((_props, ref) => {
const { giscus } = useThemeConfig() as CustomThemeConfig;
const { colorMode } = useColorMode();
const { theme = 'light', darkTheme = 'dark_dimmed' } = giscus;
const giscusTheme = colorMode === 'dark' ? darkTheme : theme;
const [routeDidUpdate, setRouteDidUpdate] = useState(false);

useEffect(() => {
function eventHandler(e) {
setRouteDidUpdate(true);
}

window.emitter.on('onRouteDidUpdate', eventHandler);

return () => {
window.emitter.off('onRouteDidUpdate', eventHandler);
};
}, []);

if (!routeDidUpdate) {
return null;
}

return (
<BrowserOnly fallback={<div>Loading Comments...</div>}>
{() => (
<div ref={ref} id="comment" style={{ paddingTop: 50 }}>
<Giscus
id="comments"
mapping="title"
strict="1"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="bottom"
lang="zh-CN"
loading="lazy"
{...giscus}
theme={giscusTheme}
/>
</div>
)}
</BrowserOnly>
);
});

export default Comment;

Swizzling Docusaurus 内部组件

Docusaurus 页面分为文档和博客,自己根据需求 Swizzling 对应的页面组件。

Swizzling 文档页面对应组件

yarn run swizzle @docusaurus/theme-classic DocItem/Layout -- --eject --typescript

Swizzling 后会生成 src/theme/DocItem/Layout 目录,我们需要对 src/theme/DocItem/Layout/index.tsx 进行修改

src/theme/DocItem/Layout/index.tsx

import React from 'react';
import clsx from 'clsx';
import { useWindowSize } from '@docusaurus/theme-common';
// @ts-ignore
import { useDoc } from '@docusaurus/theme-common/internal';
import DocItemPaginator from '@theme/DocItem/Paginator';
import DocVersionBanner from '@theme/DocVersionBanner';
import DocVersionBadge from '@theme/DocVersionBadge';
import DocItemFooter from '@theme/DocItem/Footer';
import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
import DocItemContent from '@theme/DocItem/Content';
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
import type { Props } from '@theme/DocItem/Layout';

import styles from './styles.module.css';
import Comment from '../../../components/comment';

/**
* Decide if the toc should be rendered, on mobile or desktop viewports
*/
function useDocTOC() {
const { frontMatter, toc } = useDoc();
const windowSize = useWindowSize();

const hidden = frontMatter.hide_table_of_contents;
const canRender = !hidden && toc.length > 0;

const mobile = canRender ? <DocItemTOCMobile /> : undefined;

const desktop =
canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? (
<DocItemTOCDesktop />
) : undefined;

return {
hidden,
mobile,
desktop
};
}

export default function DocItemLayout({ children }: Props): JSX.Element {
const docTOC = useDocTOC();
const { frontMatter } = useDoc();
const { hide_comment: hideComment } = frontMatter;

return (
<div className="row">
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
<DocVersionBanner />
<div className={styles.docItemContainer}>
<article>
<DocBreadcrumbs />
<DocVersionBadge />
{docTOC.mobile}
<DocItemContent>{children}</DocItemContent>
<DocItemFooter />
</article>
<DocItemPaginator />
</div>
{!hideComment && <Comment />}
</div>
{docTOC.desktop && <div className="col col--3">{docTOC.desktop}</div>}
</div>
);
}

主要是17,45,46,62行需要修改。

Swizzling 博客页面对应组件

yarn run swizzle @docusaurus/theme-classic BlogPostPage -- --eject --typescript

同样修改文件

src/theme/BlogPostPage/index.tsx

import React, { type ReactNode } from 'react';
import clsx from 'clsx';
import {
HtmlClassNameProvider,
ThemeClassNames
} from '@docusaurus/theme-common';

import {
BlogPostProvider,
useBlogPost
// @ts-ignore
} from '@docusaurus/theme-common/internal';
import BlogLayout from '@theme/BlogLayout';
import BlogPostItem from '@theme/BlogPostItem';
import BlogPostPaginator from '@theme/BlogPostPaginator';
import BlogPostPageMetadata from '@theme/BlogPostPage/Metadata';
import TOC from '@theme/TOC';
import type { Props } from '@theme/BlogPostPage';
import type { BlogSidebar } from '@docusaurus/plugin-content-blog';
import Comment from '../../components/comment';

function BlogPostPageContent({
sidebar,
children
}: {
sidebar: BlogSidebar;
children: ReactNode;
}): JSX.Element {
const { metadata, toc } = useBlogPost();
const { nextItem, prevItem, frontMatter } = metadata;
const {
hide_table_of_contents: hideTableOfContents,
toc_min_heading_level: tocMinHeadingLevel,
toc_max_heading_level: tocMaxHeadingLevel,
hide_comment: hideComment
} = frontMatter;
return (
<BlogLayout
sidebar={sidebar}
toc={
!hideTableOfContents && toc.length > 0 ? (
<TOC
toc={toc}
minHeadingLevel={tocMinHeadingLevel}
maxHeadingLevel={tocMaxHeadingLevel}
/>
) : undefined
}
>
<BlogPostItem>{children}</BlogPostItem>
{(nextItem || prevItem) && (
<BlogPostPaginator nextItem={nextItem} prevItem={prevItem} />
)}
{!hideComment && <Comment />}
</BlogLayout>
);
}

export default function BlogPostPage(props: Props): JSX.Element {
const BlogPostContent = props.content;
return (
<BlogPostProvider content={props.content} isBlogPostPage>
<HtmlClassNameProvider
className={clsx(
ThemeClassNames.wrapper.blogPages,
ThemeClassNames.page.blogPostPage
)}
>
<BlogPostPageMetadata />
<BlogPostPageContent sidebar={props.sidebar}>
<BlogPostContent />
</BlogPostPageContent>
</HtmlClassNameProvider>
</BlogPostProvider>
);
}

主要是20,35,54行做了修改

配置 Giscus

在docusaurus.config.json文件中加入

module.exports = {
themeConfig: {
giscus: {
repo: 'xxx',
repoId: 'xxx',
category: 'Announcements',
categoryId: 'xxx'
}
},
clientModules: [require.resolve('./src/clientModules/routeModules.ts')]
};

其中,

repo: 这是你的 GitHub 仓库名,格式为 用户名/仓库名,例如 your-username/your-repo

repoId: 这是 GitHub 仓库的唯一 ID。在 Giscus 网站安装过程中会提供给你。

category: 这是你用来存储评论的 GitHub 讨论区分类名,例如 Announcements。你可以在仓库的讨论区中创建或选择一个分类。

categoryId: 这是上述分类的唯一 ID。同样地,在 Giscus 网站安装过程中会提供给你。

如果你不知道repoId,categoryId

你可以通过 GitHub GraphQL API 获取

1,打开 GitHub GraphQL API Explorer: https://docs.github.com/en/graphql/overview/explorer

2,登录并授权访问你的 GitHub 帐户。

3,在 GraphQL 查询框中输入以下查询语句,将 your-usernameyour-repo 替换为你的用户名和仓库名:

{
repository(owner: "your-username", name: "your-repo") {
id
}
}

4,点击“Execute Query”按钮,结果中会显示你的仓库 ID,例如:

{
"data": {
"repository": {
"id": "MDEwOlJlcG9zaXRvcnkxMjM0NTY3OA=="
}
}
}

这个 id 就是你的 repoId

手动获取 categoryId

类似地,你可以通过 GitHub GraphQL API 获取讨论区分类的 categoryId

  1. 在 GraphQL 查询框中输入以下查询语句,将 your-usernameyour-repo 替换为你的用户名和仓库名,将 category-name 替换为你的分类名:

    {
    repository(owner: "your-username", name: "your-repo") {
    discussionCategories(first: 10) {
    nodes {
    id
    name
    }
    }
    }
    }

​ 2,点击“Execute Query”按钮,结果中会显示你的分类信息,例如:

{
"data": {
"repository": {
"discussionCategories": {
"nodes": [
{
"id": "DIC_kwDOA1c2eM4B-9gP",
"name": "Announcements"
}
]
}
}
}
}

这个 id 就是你的 categoryId

示例配置

获取到 repoIdcategoryId 后,你可以将它们填入 Giscus 配置中:

javascript复制代码giscus: {
repo: 'your-username/your-repo',
repoId: 'MDEwOlJlcG9zaXRvcnkxMjM0NTY3OA==',
category: 'Announcements',
categoryId: 'DIC_kwDOA1c2eM4B-9gP'
}

黑夜模式

根据自己喜好修改配置

giscus: {
theme: 'light_high_contrast',
darkTheme: 'dark_tritanopia'
},

本篇文章受 CC BY-NC-SA 4.0 协议保护,转载请注明出处。