讓 Docusaurus 的 Sidebar 自動計算文章數量
· 閱讀時間約 6 分鐘
Docusaurus 預設提供了功能非常豐富的 Sidebar,但不合用。
這次讓我們動手改一改。
目標很簡單,原本的 Sidebar 會顯示我們指定的類別,網頁啟動時會去查看每個層級的 _category_.json
,內容像是:
{
"label": "Classic CNNs",
"link": {
"type": "generated-index"
}
}
這時會顯示:
- Classic CNN
- ...(其他類別)
我希望可以計算每個資料夾底下的數目,直接顯示在網頁上,像這樣:
- Classic CNNs (8)
- ...(其他類別)
那是不是可以直接在 _category_.json
檔案裡面加上數量呢?
{
"label": "Classic CNNs (8)",
"link": {
"type": "generated-index"
}
}
然後每次更新文章,就手動更新數量?
不行!我們不能寫出這種丟人的程式。
參考資料
為了解決這個問題,照慣例,我們往 Docusaurus 的官方文件找找看:
原本預設的 Sidebar,透過 autogenerated
來自動生成:
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
*/
};
export default sidebars;
看了一輪,好像沒有現成的可以用,那就自己寫吧。
實作
我們直接把註解寫在程式碼裡面,如果你有需要,有幾個需要改動的位置需要注意:
-
第 8 行:
const baseDir = path.join(__dirname, "papers");
這裡的
papers
是我們資料夾名稱,請確保你的資料夾路徑正確。
-
第 20 行:
sidebarItems.push("intro");
這裡的
intro
是我們的首頁的名稱,請確保你的首頁名稱正確。如果沒有首頁,可以刪掉這行。
-
第 72 行:
return stat.isDirectory() || (stat.isFile() && item.endsWith(".md"));
這裡的
.md
是我們的文章格式,記得改成你要的格式。
程式碼實作如下,執行結果請直接看網頁:Papers。
/sidebars.js
const fs = require("fs"); // 引入 Node.js 的檔案系統模組,用來操作檔案和目錄
const path = require("path"); // 引入 Node.js 的路徑模組,用來處理路徑
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
// 這是一個 Docusaurus 側邊欄配置的類型定義,方便 IDE 提供提示
function generateSidebar() {
// 設定基本路徑,這裡指向 'papers' 目錄,可以根據實際情況修改
const baseDir = path.join(__dirname, "papers");
// 讀取 'papers' 目錄下的所有子目錄,過濾掉隱藏的目錄(以 . 開頭的)
const categories = fs.readdirSync(baseDir).filter((item) => {
const itemPath = path.join(baseDir, item);
// 確認是目錄且不是隱藏的
return fs.statSync(itemPath).isDirectory() && !item.startsWith(".");
});
const sidebarItems = []; // 用來存放側邊欄項目的陣列
// 首先加入一個固定的 'intro' 項目
sidebarItems.push("intro");
// 迭代所有類別目錄
categories.forEach((category) => {
const categoryPath = path.join(baseDir, category); // 取得每個類別的完整路徑
const count = countItemsInDirectory(categoryPath); // 計算該目錄中的項目數量
// 嘗試讀取該類別目錄中的 '_category_.json' 檔案,來獲取類別的標籤和連結
const categoryJsonPath = path.join(categoryPath, "_category_.json");
let label = category; // 預設標籤為目錄名稱
let link = undefined; // 預設連結為 undefined
if (fs.existsSync(categoryJsonPath)) {
// 如果 '_category_.json' 存在
const categoryJson = JSON.parse(
fs.readFileSync(categoryJsonPath, "utf8")
); // 讀取並解析 JSON
label = categoryJson.label || category; // 使用 JSON 中的 label,若無則使用目 錄名稱
link = categoryJson.link; // 使用 JSON 中的 link
}
// 在標籤後附加項目數量
label = `${label} (${count})`;
// 創建側邊欄項目,type 為 'category',表示這是一個分類
const sidebarItem = {
type: "category",
label: label, // 顯示的標籤
items: [{ type: "autogenerated", dirName: category }], // 自動生成分類下的文件項目
};
if (link) {
// 如果有連結,則添加到該分類中
sidebarItem.link = link;
}
sidebarItems.push(sidebarItem); // 將該分類項目加入側邊欄陣列
});
// 返回一個包含側邊欄配置的物件
return {
papersSidebar: sidebarItems,
};
}
// 計算指定目錄中的有效項目數量(包含子目錄和 Markdown 檔案)
function countItemsInDirectory(dirPath) {
const items = fs.readdirSync(dirPath).filter((item) => {
const itemPath = path.join(dirPath, item);
// 過濾掉 '_category_.json' 檔案和隱藏檔案(以 . 開頭)
if (item === "_category_.json" || item.startsWith(".")) return false;
const stat = fs.statSync(itemPath);
// 只計算目錄和 .md 檔案
return stat.isDirectory() || (stat.isFile() && item.endsWith(".md"));
});
return items.length; // 返回計算的項目數量
}
// 將產生的側邊欄配置匯出,讓 Docusaurus 使用
export default generateSidebar();