UNPKG

vitepress-plugin-catalogue

Version:
201 lines (194 loc) 7.6 kB
import { readdirSync, statSync, readFileSync, existsSync } from 'node:fs'; import { basename, resolve, extname, join } from 'node:path'; import matter from 'gray-matter'; import { createLogger } from 'vite'; import picocolors from 'picocolors'; const getTitleFromMd = (mdContent) => { const lines = mdContent.trimStart().split(/\r?\n/); for (const line of lines) { if (line.startsWith("# ")) { return line.substring(2).trim(); } } return void 0; }; const isIllegalIndex = (index) => { return isNaN(index) || index < 0; }; const isMdFile = (filePath) => { return filePath.includes("md") || filePath.includes("MD"); }; const isSome = (arr, name) => { return arr.some((item) => item === name || item instanceof RegExp && item.test(name)); }; const DEFAULT_IGNORE_DIR = ["node_modules", "dist", ".vitepress", "public"]; const catalogueInfo = []; const createCatalogues = (option = {}) => { const { path = "", ignoreList = [] } = option; if (!path) return []; const dirPaths = readDirPaths(path, ignoreList); dirPaths.forEach((dirPath) => scannerMdFile(dirPath, option, basename(dirPath))); return catalogueInfo; }; const readDirPaths = (sourceDir, ignoreList = []) => { const ignoreListAll = [...DEFAULT_IGNORE_DIR, ...ignoreList]; const dirPaths = []; const dirOrFilenames = readdirSync(sourceDir); dirOrFilenames.forEach((dirOrFilename) => { const secondDirPath = resolve(sourceDir, dirOrFilename); if (!isSome(ignoreListAll, dirOrFilename) && statSync(secondDirPath).isDirectory()) { dirPaths.push(secondDirPath); } }); return dirPaths; }; const scannerMdFile = (root, option, prefix = "") => { const { path: srcDir = "", ignoreList = [] } = option; const ignoreListAll = [...DEFAULT_IGNORE_DIR, ...ignoreList]; const dirOrFilenames = readdirSync(root); dirOrFilenames.forEach((dirOrFilename) => { if (isSome(ignoreListAll, dirOrFilename)) return []; const filePath = resolve(root, dirOrFilename); if (statSync(filePath).isDirectory()) { scannerMdFile(filePath, option, `${prefix}/${dirOrFilename}`); } else { if (!isMdFile(dirOrFilename)) return; const content = readFileSync(filePath, "utf-8"); const { data: { catalogue, path = "" } = {} } = matter(content, {}); if (catalogue && path) { const filename = basename(dirOrFilename, extname(dirOrFilename)); catalogueInfo.push({ filePath: `${prefix}/${filename}`, path, catalogues: createCatalogueList(join(srcDir, path), option, `/${path}/`) }); } } }); }; const createCatalogueList = (root, option, prefix = "/") => { if (!existsSync(root)) { console.warn(`'${root}' \u8DEF\u5F84\u4E0D\u5B58\u5728\uFF0C\u5C06\u5FFD\u7565\u8BE5\u76EE\u5F55\u9875\u7684\u751F\u6210`); return []; } const { ignoreIndexMd = false, titleFormMd = false } = option; const catalogueItemList = []; const catalogueItemListNoIndex = []; const dirOrFilenames = readdirSync(root); dirOrFilenames.forEach((dirOrFilename) => { const filePath = resolve(root, dirOrFilename); const { index: indexStr, title, name } = resolveFileName(dirOrFilename, filePath); const index = parseInt(indexStr, 10); if (statSync(filePath).isDirectory()) { const mdTitle = titleFormMd ? tryGetMdTitle(root, dirOrFilename) : ""; const catalogueItem = { title: mdTitle || title, children: createCatalogueList(filePath, option, `${prefix}${dirOrFilename}/`) }; if (isIllegalIndex(index)) catalogueItemListNoIndex.push(catalogueItem); else catalogueItemList[index] = catalogueItem; } else { if (!isMdFile(dirOrFilename)) return []; if (ignoreIndexMd && ["index.md", "index.MD"].includes(dirOrFilename)) return []; const content = readFileSync(filePath, "utf-8"); const { data: frontmatter = {}, content: mdContent } = matter(content, {}); const { title: frontmatterTitle, catalogue, inCatalogue = true } = frontmatter; if (catalogue || !inCatalogue) return []; const mdTitle = titleFormMd ? getTitleFromMd(mdContent) : ""; const finalTitle = frontmatterTitle || mdTitle || title; const catalogueItem = { title: finalTitle, link: prefix + name, frontmatter }; if (isIllegalIndex(index)) catalogueItemListNoIndex.push(catalogueItem); else catalogueItemList[index] = catalogueItem; } }); return [...catalogueItemList, ...catalogueItemListNoIndex].filter(Boolean); }; const resolveFileName = (filename, filePath) => { const stat = statSync(filePath); let index = ""; let title = ""; let type = ""; let name = ""; const fileNameArr = filename.split("."); if (fileNameArr.length === 2) { index = fileNameArr[0] === "index" ? "0" : fileNameArr[0]; title = stat.isDirectory() ? fileNameArr[1] : fileNameArr[0]; type = fileNameArr[1]; name = fileNameArr[0]; } else { const firstDotIndex = filename.indexOf("."); const lastDotIndex = filename.lastIndexOf("."); index = filename.substring(0, firstDotIndex); type = filename.substring(lastDotIndex + 1); name = filename.substring(0, lastDotIndex); if (stat.isDirectory()) title = filename.substring(firstDotIndex + 1); else title = filename.substring(firstDotIndex + 1, lastDotIndex); } return { index, title, type, name }; }; const tryGetMdTitle = (root, dirOrFilename) => { const filePaths = [ join(root, dirOrFilename, "index.md"), join(root, dirOrFilename, "index.MD"), join(root, dirOrFilename, dirOrFilename + ".md") ]; for (const filePath of filePaths) { if (!existsSync(filePath)) continue; const content = readFileSync(filePath, "utf-8"); const { content: mdContent } = matter(content, {}); const t = getTitleFromMd(mdContent); if (t) return t; } return ""; }; const version = "1.0.15"; const logger = createLogger("info", { prefix: `[vitepress-plugin-catalogue v${version}]` }); const info = (message, level = "green", option = { timestamp: true }) => { logger.info(picocolors[level](message), option); }; const warn = (message, level = "yellow", option = { timestamp: true }) => { logger.warn(picocolors[level](message), option); }; const warnOnce = (message, level = "yellow", option = { timestamp: true }) => { logger.info(picocolors[level](message), option); }; const error = (message, level = "red", option = { timestamp: true }) => { logger.error(picocolors[level](message), option); }; const logger$1 = { info, warn, warnOnce, error }; function VitePluginVitePressCatalogue(option = {}) { let isExecute = false; return { name: "vite-plugin-vitepress-catalogue", config(config) { if (isExecute) return; isExecute = true; const { site: { themeConfig }, srcDir } = config.vitepress; const baseDir = option.path ? join(srcDir, option.path) : srcDir; const catalogues = createCatalogues({ ...option, path: baseDir }); const finalCatalogues = { arr: catalogues, map: {}, inv: {} }; catalogues.forEach((item) => { const { filePath, path, catalogues: catalogues2 = [] } = item; finalCatalogues.map[filePath] = { path, catalogues: catalogues2 }; finalCatalogues.inv[path] = { filePath, catalogues: catalogues2 }; }); themeConfig.catalogues = finalCatalogues; logger$1.info("Injected Catalogues Data Successfully. \u6CE8\u5165\u76EE\u5F55\u9875\u6570\u636E\u6210\u529F!"); } }; } export { VitePluginVitePressCatalogue as default };