跳至主要内容

幫 Docusaurus 的 Docs 加上作者資訊

如果你也在用 Docusaurus 寫網站,那你一定知道 Docusaurus 有兩種主要的內容類型:

  • 部落格使用的插件是:@docusaurus/plugin-content-blog
  • 技術文件的部分則是: @docusaurus/plugin-content-docs

其中,只有 blog 有作者資訊的功能,而 docs 沒有。

哎呀,晴天霹靂!

先去問問原廠

我們首先到 Docusaurus 的 GitHub 上提出問題,看看原廠有沒有支援這個功能。

或許 Docusaurus 的作者可以大發慈悲,幫我們加上這個功能。

但等了一段時間,得到原廠的回覆:

docusaurus-reply

簡單來說,就是叫你自己想辦法,原廠不支援。

看來求人不如求己,我們只好硬著頭皮上了。

新增作者資訊

在原本的設計中,作者資訊放在 blog/authors.yml 這個檔案中,裡面內容大概長這樣:

Zephyr:
name: Zephyr
title: Dosaid maintainer, Full-Stack AI Engineer
url: https://github.com/zephyr-sh
image_url: https://github.com/zephyr-sh.png
socials:
github: "zephyr-sh"

我們先在相同路徑下,建立一個新的檔案 blog/authors.json,把同樣的內容改寫成 json 格式:

{
"Zephyr": {
"name": "Zephyr",
"title": "Dosaid maintainer, Full-Stack AI Engineer",
"url": "https://github.com/zephyr-sh",
"image_url": "https://github.com/zephyr-sh.png",
"socials": {
"github": "zephyr-sh"
}
}
}
資訊

我們在開發時,發現解析 YML 檔案的方式很麻煩,經過一輪測試,直接改 JSON 格式最簡單。

提示

雖然這是要給 docs 用的檔案,但我們還是把它放在 blog 資料夾內,這樣更新時才會記得一起處理。

提取 DocItem/Content

注意

從這個步驟開始,我們得去修改 Docusaurus 的原始碼。

之後 Docusaurus 若有破壞性的版本更新,該修改可能會導致網站無法正常運行,請確保你有維護網站的能力,再繼續進行。

我們先把 DocItemContent 程式碼提取出來,請執行以下指令:

npx docusaurus swizzle @docusaurus/theme-classic DocItem/Content

執行後會遇到幾個問題:

  1. Which language do you want to use?

    我們選擇 JavaScript

  2. Which swizzle action do you want to do?

    我們選擇 Eject.

  3. Do you really want to swizzle this unsafe internal component?

    我們選擇 YES: I know what I am doing!.


現在你可以找到一個路徑: src/theme/DocItem/Content,裡面有一個 index.js 檔案,這就是我們要修改的地方。

修改後的程式碼如下:

import { useDoc } from "@docusaurus/plugin-content-docs/client";
import { ThemeClassNames } from "@docusaurus/theme-common";
import DocItemAuthors from "@theme/DocItem/Authors";
import Heading from "@theme/Heading";
import MDXContent from "@theme/MDXContent";
import clsx from "clsx";
import React from "react";

function useSyntheticTitle() {
const { metadata, frontMatter, contentTitle } = useDoc();
const shouldRender =
!frontMatter.hide_title && typeof contentTitle === "undefined";
if (!shouldRender) {
return null;
}
return metadata.title;
}

export default function DocItemContent({ children }) {
const syntheticTitle = useSyntheticTitle();

return (
<div className={clsx(ThemeClassNames.docs.docMarkdown, "markdown")}>
{syntheticTitle ? (
<header>
<Heading as="h1">{syntheticTitle}</Heading>
<DocItemAuthors />
<MDXContent>{children}</MDXContent>
</header>
) : (
<>
<DocItemAuthors />
<MDXContent>{children}</MDXContent>
</>
)}
</div>
);
}

主要新增一個模組

  • import DocItemAuthors from "@theme/DocItem/Authors";

這個部分我們稍後實作。

實作 DocItem/Authors

現在我們來實作 Authors 這個元件,請執行以下指令:

mkdir -p src/theme/DocItem/Authors
touch src/theme/DocItem/Authors/index.js
touch src/theme/DocItem/Authors/styles.module.css

這個部分,我們是參考 BlogAuthors 元件,依樣畫葫蘆地實作出來。

其中 DocItem/Authors/index.js 的程式碼如下:

import { useDoc } from "@docusaurus/plugin-content-docs/client";
import authorsData from "@site/blog/authors.json";
import React from "react";
import {
FaEnvelope,
FaGithub,
FaLinkedin,
FaRss,
FaStackOverflow,
FaTwitter,
} from "react-icons/fa";
import styles from "./index.module.css";

function normalizeSocialLink(platform, handleOrUrl) {
const isAbsoluteUrl =
handleOrUrl.startsWith("http://") || handleOrUrl.startsWith("https://");
if (isAbsoluteUrl) {
return handleOrUrl;
}
switch (platform) {
case "x":
return `https://x.com/${handleOrUrl}`;
case "github":
return `https://github.com/${handleOrUrl}`;
case "linkedin":
return `https://www.linkedin.com/in/${handleOrUrl}/`;
case "stackoverflow":
return `https://stackoverflow.com/users/${handleOrUrl}`;
case "newsletter":
return handleOrUrl;
case "email":
return `mailto:${handleOrUrl}`;
default:
return handleOrUrl;
}
}

const socialIconMap = {
x: FaTwitter, // 使用 FaTwitter 代替 X 的圖示
github: FaGithub,
linkedin: FaLinkedin,
stackoverflow: FaStackOverflow,
email: FaEnvelope,
newsletter: FaRss,
};

export default function DocItemAuthors() {
const { frontMatter } = useDoc();
let { authors } = frontMatter;

if (!authors) {
return null;
}

if (typeof authors === "string") {
authors = [authors];
}

const resolvedAuthors = authors
.map((authorKeyOrObj) => {
if (typeof authorKeyOrObj === "string") {
const authorInfo = authorsData[authorKeyOrObj];
if (!authorInfo) {
console.warn(
`No author data found for key '${authorKeyOrObj}' in authors.json`
);
return null;
}
return {
name: authorInfo.name,
title: authorInfo.title,
url: authorInfo.url,
imageURL: authorInfo.image_url,
socials: authorInfo.socials,
description: authorInfo.description,
};
} else {
const { name, title, url, image_url, imageURL, socials, description } =
authorKeyOrObj;
return {
name,
title,
url,
imageURL: imageURL || image_url,
socials,
description,
};
}
})
.filter(Boolean);

if (resolvedAuthors.length === 0) {
return null;
}

return (
<div className={`${styles.docAuthors} margin-bottom--md`}>
{resolvedAuthors.map((author, index) => {
const { name, title, url, imageURL, socials, description } = author;
return (
<div key={index} className={styles.docAuthor}>
{imageURL && (
<img src={imageURL} alt={name} className={styles.docAuthorImg} />
)}
<div>
<div className={styles.docAuthorName}>
{url ? (
<a href={url} target="_blank" rel="noopener noreferrer">
{name}
</a>
) : (
name
)}
</div>
{title && <div className={styles.docAuthorTitle}>{title}</div>}
{description && (
<div className={styles.docAuthorDesc}>{description}</div>
)}

{socials && (
<div className={styles.docAuthorSocials}>
{Object.entries(socials).map(([platform, handleOrUrl]) => {
const SocialIcon = socialIconMap[platform] || FaEnvelope;
const normalizedUrl = normalizeSocialLink(
platform,
handleOrUrl
);
return (
<a
key={platform}
href={normalizedUrl}
target="_blank"
rel="noopener noreferrer"
className={styles.docAuthorSocialLink}
>
<SocialIcon size={20} />
</a>
);
})}
</div>
)}
</div>
</div>
);
})}
</div>
);
}

這裡有用到 react-icons,如果你沒有安裝,請執行以下指令:

yarn add react-icons

要注意到這裡有些地方我們是硬編碼的,例如:

function normalizeSocialLink(platform, handleOrUrl) {
const isAbsoluteUrl =
handleOrUrl.startsWith("http://") || handleOrUrl.startsWith("https://");
if (isAbsoluteUrl) {
return handleOrUrl;
}
switch (platform) {
case "x":
return `https://x.com/${handleOrUrl}`;
case "github":
return `https://github.com/${handleOrUrl}`;
case "linkedin":
return `https://www.linkedin.com/in/${handleOrUrl}/`;
case "stackoverflow":
return `https://stackoverflow.com/users/${handleOrUrl}`;
case "newsletter":
return handleOrUrl;
case "email":
return `mailto:${handleOrUrl}`;
default:
return handleOrUrl;
}
}

const socialIconMap = {
x: FaTwitter, // 使用 FaTwitter 代替 X 的圖示
github: FaGithub,
linkedin: FaLinkedin,
stackoverflow: FaStackOverflow,
email: FaEnvelope,
newsletter: FaRss,
};

如果這些社群網站的網址格式有變動,你可能需要修改這些地方。

最後是 DocItem/Authors/styles.module.css 的程式碼:

.docAuthor {
display: flex;
align-items: center;
margin-bottom: 2rem;
}

.docAuthorImg {
width: 60px;
height: 60px;
border-radius: 50%;
margin-right: 0.75rem;
object-fit: cover;
}

.docAuthorName {
font-weight: 600;
font-size: 1rem;
margin-bottom: 0.25rem;
color: #111;
}

.docAuthorName a {
text-decoration: none;
color: inherit;
}

.docAuthorName a:hover {
text-decoration: underline;
}

.docAuthorTitle {
font-size: 0.85rem;
color: #555;
margin-bottom: 0.25rem;
line-height: 1.2;
}

.docAuthorDesc {
font-size: 0.85rem;
color: #333;
margin-bottom: 0.4rem;
line-height: 1.4;
}

.docAuthorSocials {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
align-items: center;
}

.docAuthorSocialLink {
display: inline-flex;
align-items: center;
text-decoration: none;
color: inherit;
line-height: 1;
}

.docAuthorSocialLink:hover {
color: var(--ifm-color-primary);
}

這裡的實作方式就看個人風格了,我們測了幾次,這個樣式看起來還不錯。

你可以根據自己的需求,修改這些樣式。

調整文件的 FrontMatter

最後,為了讓 docs 的文件可以顯示作者資訊,我們需要在文件的 FrontMatter 中加入作者的資訊。

以我們的網站文章為例:[20.08] HiPPO: 河馬的記憶

這篇文章原本的寫法是這樣:

# [20.08] HiPPO

## 河馬的記憶

[**HiPPO: Recurrent Memory with Optimal Polynomial Projections**](https://arxiv.org/abs/2008.07669)

現在為了加入作者資訊,我們「不可以」使用 # 作為標題,而是要使用 FrontMatter 來定義標題。

所以我們要修改成這樣:

---
title: "[20.08] HiPPO"
authors: Zephyr
---

## 河馬的記憶

[**HiPPO: Recurrent Memory with Optimal Polynomial Projections**](https://arxiv.org/abs/2008.07669)

authors 的地方指定作者的名字,這樣就可以顯示作者資訊。

提示

請確保你的 authors 名字和 authors.json 中的名字一致,否則會顯示不出來。

實作完成

最後,一起來看看網站上的效果:

docusaurus-author

如上圖,我們終於成功的在 docs 的文件中加入了作者資訊,可喜可賀!

以上就是我們的實作過程,希望對你有所幫助。