UNPKG

@mingshuisheng/vitepress-plugin-auto-sidebar-nav

Version:

Automatically generate sidebars and navigation

230 lines (220 loc) 6.38 kB
'use strict'; const path = require('path'); const fs = require('fs'); const matter = require('gray-matter'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } function _interopNamespaceCompat(e) { if (e && typeof e === 'object' && 'default' in e) return e; const n = Object.create(null); if (e) { for (const k in e) { n[k] = e[k]; } } n.default = e; return n; } const path__namespace = /*#__PURE__*/_interopNamespaceCompat(path); const fs__default = /*#__PURE__*/_interopDefaultCompat(fs); const matter__default = /*#__PURE__*/_interopDefaultCompat(matter); async function readFile(path) { return new Promise((resolve, reject) => { fs__default.readFile(path, "utf8", (err, data) => { if (err) { reject(err); } else { resolve(data); } }); }); } async function readdir(path) { return new Promise((resolve, reject) => { fs__default.readdir(path, (err, files) => { if (err) { reject(err); } else { resolve(files); } }); }); } async function stat(path) { return new Promise((resolve, reject) => { fs__default.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__namespace.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__namespace.join(dirPath, f).startsWith(exclude)) ); const result = {}; for (let dir of filterDirs) { const curDirPath = path__namespace.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__namespace.extname(dir) === ".md") return true; const subHasMarkdown = await hasMarkdown(path__namespace.join(dirPath, dir)); if (subHasMarkdown) return true; } return false; } async function getSidebar(docsPath, excludes) { const markdownFiles = await readAllFile( docsPath, ".md", excludes.map((f) => path__namespace.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__namespace.join(dirPath, f).startsWith(exclude)) ); const nextDirs = []; for (let cur of current) { const state = await stat(path__namespace.join(dirPath, cur)); if (state.isDirectory()) { nextDirs.push(cur); } } const files = current.filter((f) => f.endsWith(endswith)).map((f) => path__namespace.join(dirPath, f)); if (nextDirs.length > 0) { for (const dir of nextDirs) { const subFiles = await readAllFile( path__namespace.join(dirPath, dir), endswith, excludes ); files.push(...subFiles); } } return files; } async function getFileInfo(filePath) { const content = await readFile(filePath); return { sidebarText: matter__default(content).data.sidebarText, filePath, fileName: path__namespace.basename(filePath), fileDir: path__namespace.dirname(filePath), fileNameWithoutExt: path__namespace.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; } module.exports = VitePluginAutoSidebarAndNav;