@jiek/vite-plugin-vitepress-auto-sidebar
Version:
The vite plugin that automatically generates sidebar data by scanning directories, based on vitepress
228 lines (224 loc) • 7.23 kB
JavaScript
import { join } from 'node:path';
import { readdirSync, statSync, existsSync as existsSync$1, readFileSync as readFileSync$1 } from 'node:fs';
import { existsSync, readFileSync } from 'fs';
import fm from 'front-matter';
const DEFAULT_IGNORE_FOLDER = ["scripts", "components", "assets", ".vitepress"];
function log(...info) {
console.log("[auto-sidebar]", ...info);
}
function removePrefix(str, identifier) {
return str.replace(identifier, "");
}
function getTitleFromFile(realFileName) {
if (!existsSync(realFileName)) {
return void 0;
}
const fileExtension = realFileName.substring(
realFileName.lastIndexOf(".") + 1
);
if (fileExtension !== "md" && fileExtension !== "MD") {
return void 0;
}
const data = readFileSync(realFileName, { encoding: "utf-8" });
const lines = data.split(/\r?\n/);
for (const line of lines) {
if (line.startsWith("# ")) {
return line.substring(2);
}
}
return void 0;
}
function getTitleFromFileByYaml(realFileName) {
if (!existsSync(realFileName)) {
return void 0;
}
const regex = /\.md$/i;
if (!regex.test(realFileName)) {
return void 0;
}
const data = readFileSync(realFileName, { encoding: "utf-8" });
const content = fm(data);
return content.attributes?.title || void 0;
}
let option;
function extractTitleFn({ titleFromFile = false, titleFromFileByYaml = false }) {
if (titleFromFile) {
return getTitleFromFile;
} else if (titleFromFileByYaml) {
return getTitleFromFileByYaml;
}
return void 0;
}
function createSideBarItems(targetPath, ...rest) {
const {
ignoreIndexItem,
deletePrefix,
collapsed,
sideBarItemsResolved,
beforeCreateSideBarItems,
ignoreList = [],
titleFromFile = false,
titleFromFileByYaml = false
} = option;
const rawNode = readdirSync(join(targetPath, ...rest)).reduce(
(acc, cur) => [
"index.md",
"index.MD"
].includes(cur) ? [cur, ...acc] : [...acc, cur],
[]
);
const node = beforeCreateSideBarItems?.(rawNode) ?? rawNode;
const currentDir = join(targetPath, ...rest);
if (ignoreIndexItem && node.length === 1 && node[0] === "index.md") {
return [];
}
let result = [];
const exec = extractTitleFn({ titleFromFile, titleFromFileByYaml });
for (const fname of node) {
if (statSync(join(targetPath, ...rest, fname)).isDirectory()) {
if (ignoreList.some((item) => item === fname || item instanceof RegExp && item.test(fname))) {
continue;
}
const metaFilename2 = option.metaFilename ?? ".sidebar.meta.json";
const metaPath2 = join(currentDir, fname, metaFilename2);
const meta2 = existsSync$1(metaPath2) ? JSON.parse(readFileSync$1(metaPath2, { encoding: "utf-8" })) : void 0;
const items = createSideBarItems(join(targetPath), ...rest, fname);
let exsistIndex = false;
let text = fname;
if (meta2) {
text = meta2.title ?? text;
} else {
if (exec) {
const filenames = [
"index.md",
"index.MD",
`${fname}.md`
];
for (const filename of filenames) {
const path = join(currentDir, fname, filename);
const title = exec(path);
if (title) {
text = title;
if (filename === "index.md" || filename === "index.MD") {
const index = items.findIndex((i) => i.link?.endsWith("index.html"));
if (index !== -1) {
items.splice(index, 1);
exsistIndex = true;
}
}
break;
}
}
}
if (deletePrefix) {
text = removePrefix(text, deletePrefix);
}
}
if (items.length > 0) {
const sidebarItem = {
text,
fname,
link: exsistIndex ? `/${[...rest, fname].join("/")}/` : void 0,
items
};
sidebarItem.collapsed = meta2?.collapsed ?? collapsed;
result.push(sidebarItem);
}
} else {
if (ignoreIndexItem && fname === "index.md" || /^-.*\.(md|MD)$/.test(fname) || ignoreList.some((item2) => item2 === fname || item2 instanceof RegExp && item2.test(fname)) || !fname.endsWith(".md")) {
continue;
}
const fileName = fname.replace(/\.md$/, "");
let text = fileName;
if (deletePrefix) {
text = removePrefix(text, deletePrefix);
}
const realFileName = join(currentDir, fname);
if (exec) {
const title = exec(realFileName);
if (title) {
text = title;
}
}
const item = {
text,
fname,
link: "/" + [...rest, `${fileName}.html`].join("/")
};
result.push(item);
}
}
const metaFilename = option.metaFilename ?? ".sidebar.meta.json";
const metaPath = join(currentDir, metaFilename);
const meta = existsSync$1(metaPath) ? JSON.parse(readFileSync$1(metaPath, { encoding: "utf-8" })) : void 0;
if (meta) {
const newResult = meta.order ? meta.order.map((item) => result.find((i) => i.fname === item)).filter((i) => !!i) : result;
if (result.length !== newResult.length) {
console.warn(`Some items in the \`${metaPath}\` file are not found`);
}
result = newResult;
}
return sideBarItemsResolved?.(result) ?? result;
}
function createSideBarGroups(targetPath, base = "") {
return {
base,
items: createSideBarItems(targetPath)
};
}
function createSidebarMulti(path, prefix) {
const calcPrefix = prefix ? `/${prefix}` : "";
const {
ignoreList = [],
ignoreIndexItem = false,
sideBarResolved
} = option;
const il = [...DEFAULT_IGNORE_FOLDER, ...ignoreList];
const data = {};
const node = readdirSync(path).filter(
(n) => statSync(join(path, n)).isDirectory() && !il.includes(n)
);
for (const k of node) {
data[`${calcPrefix}/${k}/`] = createSideBarGroups(
join(path, k),
prefix !== void 0 ? `${calcPrefix}/${k}/` : void 0
);
}
if (ignoreIndexItem) {
for (const i in data) {
let obj = data[i];
if (Array.isArray(obj)) {
obj = obj.filter((i2) => i2.items != null && i2.items.length > 0);
if (obj.length === 0) {
Reflect.deleteProperty(data, i);
}
}
}
}
return sideBarResolved?.(data) ?? data;
}
function VitePluginVitePressAutoSidebar(opt = {}) {
return {
name: "vite-plugin-vitepress-auto-sidebar",
config(config) {
option = opt;
const { path = "/docs" } = option;
const docsPath = join(process.cwd(), path);
const { vitepress: { site } } = config;
if (site.locales && Object.keys(site.locales).length > 0) {
for (const key in site.locales) {
const { themeConfig, lang } = site.locales[key];
if (!lang) {
throw new Error("`lang` is required in locale config");
}
themeConfig.sidebar = createSidebarMulti(join(docsPath, lang), lang);
}
} else {
site.themeConfig.sidebar = createSidebarMulti(docsPath);
}
log("injected sidebar data successfully");
return config;
}
};
}
export { VitePluginVitePressAutoSidebar as default };