UNPKG

@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
'use strict'; 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;