@mingshuisheng/vitepress-plugin-auto-sidebar-nav
Version:
Automatically generate sidebars and navigation
210 lines (204 loc) • 5.64 kB
JavaScript
import * as path from 'path';
import fs from 'fs';
import matter from 'gray-matter';
async function readFile(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, "utf8", (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
async function readdir(path) {
return new Promise((resolve, reject) => {
fs.readdir(path, (err, files) => {
if (err) {
reject(err);
} else {
resolve(files);
}
});
});
}
async function stat(path) {
return new Promise((resolve, reject) => {
fs.stat(path, (err, stats) => {
if (err) {
reject(err);
} else {
resolve(stats);
}
});
});
}
async function getNav(docsPath, excludes) {
const root = await readAllDir(
docsPath,
excludes.map((f) => path.join(docsPath, f))
);
const rootNav = objToNav(root);
return rootNav;
}
async function readAllDir(dirPath, excludes) {
const subDirs = await readdir(dirPath);
const filterDirs = subDirs.filter(
(f) => !excludes.some((exclude) => path.join(dirPath, f).startsWith(exclude))
);
const result = {};
for (let dir of filterDirs) {
const curDirPath = path.join(dirPath, dir);
const state = await stat(curDirPath);
if (state.isDirectory() && await hasMarkdown(curDirPath)) {
result[dir] = await readAllDir(curDirPath, excludes);
}
}
return result;
}
function objToNav(parent, parentLink = "") {
return Object.keys(parent).reduce((prev, key) => {
if (Object.keys(parent[key]).length <= 0) {
prev.push({ text: key, link: `${parentLink}/${key}/` });
} else {
prev.push({
text: key,
items: objToNav(parent[key], `${parentLink}/${key}`)
});
}
return prev;
}, []);
}
async function hasMarkdown(dirPath) {
const state = await stat(dirPath);
if (state.isFile())
return false;
const subDirs = await readdir(dirPath);
for (let dir of subDirs) {
if (path.extname(dir) === ".md")
return true;
const subHasMarkdown = await hasMarkdown(path.join(dirPath, dir));
if (subHasMarkdown)
return true;
}
return false;
}
async function getSidebar(docsPath, excludes) {
const markdownFiles = await readAllFile(
docsPath,
".md",
excludes.map((f) => path.join(docsPath, f))
);
const fileInfos = [];
for (const file of markdownFiles) {
fileInfos.push(await getFileInfo(file));
}
const sidebar = fileInfos.map((info) => ({
parent: info.fileDir.replace(docsPath, "").replace(/\\/g, "/"),
text: info.sidebarText ?? info.fileNameWithoutExt,
link: info.fileDir.replace(docsPath, "").replace(/\\/g, "/") + "/" + info.fileNameWithoutExt
})).reduce((sidebar2, cur) => {
if (!sidebar2[cur.parent]) {
sidebar2[cur.parent] = [];
}
sidebar2[cur.parent].push({
text: cur.text,
link: cur.link
});
return sidebar2;
}, {});
return {
sidebar,
cache: fileInfos.reduce((map, cur) => {
map.set(cur.filePath, cur);
return map;
}, /* @__PURE__ */ new Map())
};
}
async function readAllFile(dirPath, endswith, excludes) {
const current = (await readdir(dirPath)).filter(
(f) => !excludes.some((exclude) => path.join(dirPath, f).startsWith(exclude))
);
const nextDirs = [];
for (let cur of current) {
const state = await stat(path.join(dirPath, cur));
if (state.isDirectory()) {
nextDirs.push(cur);
}
}
const files = current.filter((f) => f.endsWith(endswith)).map((f) => path.join(dirPath, f));
if (nextDirs.length > 0) {
for (const dir of nextDirs) {
const subFiles = await readAllFile(
path.join(dirPath, dir),
endswith,
excludes
);
files.push(...subFiles);
}
}
return files;
}
async function getFileInfo(filePath) {
const content = await readFile(filePath);
return {
sidebarText: matter(content).data.sidebarText,
filePath,
fileName: path.basename(filePath),
fileDir: path.dirname(filePath),
fileNameWithoutExt: path.basename(filePath, ".md")
};
}
function VitePluginAutoSidebarAndNav(options) {
const opts = normalizeOptions(options);
let timer;
function clear() {
clearTimeout(timer);
}
function schedule(fn) {
clear();
timer = setTimeout(fn, 500);
}
let cacheMap;
return {
name: "vitepress-plugin-auto-sidebar-and-nav",
config: async (config) => {
const { sidebar, cache } = await getSidebar(
opts.docsDir,
opts.exclude ?? []
);
cacheMap = cache;
config.vitepress.userConfig.themeConfig.sidebar = sidebar;
config.vitepress.userConfig.themeConfig.nav = await getNav(
opts.docsDir,
opts.exclude ?? []
);
return config;
},
configureServer: ({ watcher, restart }) => {
const fsWatcher = watcher.add("*.md");
const restartServer = () => schedule(restart);
fsWatcher.on("add", restartServer);
fsWatcher.on("unlink", restartServer);
fsWatcher.on("change", async (path) => {
const oldInfo = cacheMap?.get(path);
if (!oldInfo) {
restartServer();
return;
}
const markdownInfo = await getFileInfo(path);
const oldText = oldInfo.sidebarText ?? oldInfo.fileNameWithoutExt;
const newText = markdownInfo.sidebarText ?? markdownInfo.fileNameWithoutExt;
if (oldText !== newText) {
restartServer();
return;
}
});
}
};
}
function normalizeOptions(options) {
return options;
}
export { VitePluginAutoSidebarAndNav as default };