@jiek/vite-plugin-vitepress-auto-sidebar
Version:
The vite plugin that automatically generates sidebar data by scanning directories, based on vitepress
234 lines (227 loc) • 7.53 kB
JavaScript
;
const node_path = require('node:path');
const node_fs = require('node:fs');
const fs = require('fs');
const fm = require('front-matter');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
const fm__default = /*#__PURE__*/_interopDefaultLegacy(fm);
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 (!fs.existsSync(realFileName)) {
return void 0;
}
const fileExtension = realFileName.substring(
realFileName.lastIndexOf(".") + 1
);
if (fileExtension !== "md" && fileExtension !== "MD") {
return void 0;
}
const data = fs.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 (!fs.existsSync(realFileName)) {
return void 0;
}
const regex = /\.md$/i;
if (!regex.test(realFileName)) {
return void 0;
}
const data = fs.readFileSync(realFileName, { encoding: "utf-8" });
const content = fm__default(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 = node_fs.readdirSync(node_path.join(targetPath, ...rest)).reduce(
(acc, cur) => [
"index.md",
"index.MD"
].includes(cur) ? [cur, ...acc] : [...acc, cur],
[]
);
const node = beforeCreateSideBarItems?.(rawNode) ?? rawNode;
const currentDir = node_path.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 (node_fs.statSync(node_path.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 = node_path.join(currentDir, fname, metaFilename2);
const meta2 = node_fs.existsSync(metaPath2) ? JSON.parse(node_fs.readFileSync(metaPath2, { encoding: "utf-8" })) : void 0;
const items = createSideBarItems(node_path.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 = node_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 = node_path.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 = node_path.join(currentDir, metaFilename);
const meta = node_fs.existsSync(metaPath) ? JSON.parse(node_fs.readFileSync(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 = node_fs.readdirSync(path).filter(
(n) => node_fs.statSync(node_path.join(path, n)).isDirectory() && !il.includes(n)
);
for (const k of node) {
data[`${calcPrefix}/${k}/`] = createSideBarGroups(
node_path.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 = node_path.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(node_path.join(docsPath, lang), lang);
}
} else {
site.themeConfig.sidebar = createSidebarMulti(docsPath);
}
log("injected sidebar data successfully");
return config;
}
};
}
module.exports = VitePluginVitePressAutoSidebar;