Docusaurusのサイドバーに記事数を自動計算させる
· 約5分
Docusaurus はデフォルトで非常に豊富な機能を持つサイドバーを提供していますが、正直に言えば、少し物足りません。
そこで、今回は手を加えて改良してみます。
目標はシンプルです。既存のサイドバーでは、指定したカテゴリが表示され、起動時に各階層の_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 の公式ドキュメントを参照します:
デフォルトのサイドバーは、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 = {
// デフォルトでは、Docusaurusはdocsフォルダ構造からサイドバーを生成
tutorialSidebar: [{ type: "autogenerated", dirName: "." }],
// 手動でサイドバーを作成する場合
/*
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} */
function generateSidebar() {
const baseDir = path.join(__dirname, "papers");
const categories = fs.readdirSync(baseDir).filter((item) => {
const itemPath = path.join(baseDir, item);
return fs.statSync(itemPath).isDirectory() && !item.startsWith(".");
});
const sidebarItems = [];
sidebarItems.push("intro");
categories.forEach((category) => {
const categoryPath = path.join(baseDir, category);
const count = countItemsInDirectory(categoryPath);
const categoryJsonPath = path.join(categoryPath, "_category_.json");
let label = category;
let link = undefined;
if (fs.existsSync(categoryJsonPath)) {
const categoryJson = JSON.parse(
fs.readFileSync(categoryJsonPath, "utf8")
);
label = categoryJson.label || category;
link = categoryJson.link;
}
label = `${label} (${count})`;
const sidebarItem = {
type: "category",
label: label,
items: [{ type: "autogenerated", dirName: category }],
};
if (link) {
sidebarItem.link = link;
}
sidebarItems.push(sidebarItem);
});
return {
papersSidebar: sidebarItems,
};
}
function countItemsInDirectory(dirPath) {
const items = fs.readdirSync(dirPath).filter((item) => {
const itemPath = path.join(dirPath, item);
if (item === "_category_.json" || item.startsWith(".")) return false;
const stat = fs.statSync(itemPath);
return stat.isDirectory() || (stat.isFile() && item.endsWith(".md"));
});
return items.length;
}
export default generateSidebar();