vite-plugin-vitepress-auto-sidebar
Version:
The vite plugin that automatically generates sidebar data by scanning directories, based on vitepress
210 lines (203 loc) • 6.17 kB
JavaScript
;
const path = require('path');
const fs = require('fs');
const c = require('picocolors');
const fm = require('front-matter');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
const c__default = /*#__PURE__*/_interopDefaultCompat(c);
const fm__default = /*#__PURE__*/_interopDefaultCompat(fm);
const DEFAULT_IGNORE_FOLDER = ["scripts", "components", "assets", ".vitepress"];
function log(...info) {
console.log(c__default.bold(c__default.cyan("[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;
}
function extractTitleFn({ titleFromFile = false, titleFromFileByYaml = false }) {
if (titleFromFile) {
return getTitleFromFile;
}
if (titleFromFileByYaml) {
return getTitleFromFileByYaml;
}
return void 0;
}
let option;
function createSideBarItems(targetPath, path$1, recursive = true) {
const {
ignoreIndexItem,
deletePrefix,
collapsed,
sideBarItemsResolved,
beforeCreateSideBarItems,
ignoreList = [],
titleFromFile = false,
titleFromFileByYaml = false
} = option;
const rawNode = fs.readdirSync(path.join(targetPath, ...path$1));
const node = beforeCreateSideBarItems?.(rawNode) ?? rawNode;
const currentDir = path.join(targetPath, ...path$1);
if (ignoreIndexItem && node.length === 1 && node[0] === "index.md") {
return [];
}
const result = [];
const exec = extractTitleFn({ titleFromFile, titleFromFileByYaml });
for (const fname of node) {
if (recursive && fs.statSync(path.join(targetPath, ...path$1, fname)).isDirectory()) {
if (ignoreList.some((item) => item === fname || item instanceof RegExp && item.test(fname))) {
continue;
}
const items = createSideBarItems(path.join(targetPath), [...path$1, fname]);
let text = fname;
if (exec) {
const filenames = [
path.join(currentDir, fname, "index.md"),
path.join(currentDir, fname, "index.MD"),
path.join(currentDir, fname, fname + ".md")
];
for (const filename of filenames) {
const title = exec(filename);
if (title) {
text = title;
break;
}
}
}
if (deletePrefix) {
text = removePrefix(text, deletePrefix);
}
if (items.length > 0) {
const sidebarItem = {
text,
items
};
sidebarItem.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 = path.join(currentDir, fname);
if (exec) {
const title = exec(realFileName);
if (title) {
text = title;
}
}
const item = {
text,
link: "/" + [...path$1.filter(Boolean), `${fileName}.html`].join("/")
};
result.push(item);
}
}
return sideBarItemsResolved?.(result) ?? result;
}
function createSideBarGroups(targetPath, folder, recursive = true) {
return [
{
items: createSideBarItems(targetPath, [folder], recursive)
}
];
}
function createSidebarMulti(path$1) {
const {
ignoreList = [],
ignoreIndexItem = false,
sideBarResolved,
scanRootMdFiles = true
} = option;
const il = [...DEFAULT_IGNORE_FOLDER, ...ignoreList];
const data = {};
const node = fs.readdirSync(path$1).filter(
(n) => fs.statSync(path.join(path$1, n)).isDirectory() && !il.includes(n)
);
if (scanRootMdFiles) {
data["/"] = createSideBarGroups(path$1, "", false);
}
for (const k of node) {
data[`/${k}/`] = createSideBarGroups(path$1, k);
}
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",
configureServer({
watcher,
restart
}) {
const fsWatcher = watcher.add("*.md");
fsWatcher.on("all", async (event, path) => {
if (event !== "change") {
log(`${event} ${path}`);
try {
await restart();
log("update sidebar...");
} catch {
log(`${event} ${path}`);
log("update sidebar failed");
}
}
});
},
config(config) {
option = opt;
const { path: path$1 = "/docs" } = option;
const docsPath = path.join(process.cwd(), path$1);
const { themeConfig } = config.vitepress.site;
themeConfig.sidebar = createSidebarMulti(docsPath);
log("injected sidebar data successfully");
return config;
}
};
}
module.exports = VitePluginVitePressAutoSidebar;