const fs = require('fs'); // 文件模块 const path = require('path'); // 路径模块 const chalk = require('chalk') // 命令行打印美化 const matter = require('gray-matter'); // front matter解析器 const log = console.log let catalogueData = {}; // 目录页数据 /** * 生成侧边栏数据 * @param {String} sourceDir .md文件所在源目录(一般是docs目录) * @param {Boolean} collapsable 是否可折叠 */ function createSidebarData (sourceDir, collapsable) { const sidebarData = {}; const tocs = readTocs(sourceDir); tocs.forEach(toc => { // toc是每个目录的绝对路径 const tocArr = toc.split('\\') if (tocArr[tocArr.length - 1] === '_posts') { // 碎片化文章 // 注释说明:碎片化文章不需要生成结构化侧边栏 2020.05.01 // const sidebarArr = mapTocToPostSidebar(toc); // sidebarData[`/${path.basename(toc)}/`] = sidebarArr } else { const sidebarObj = mapTocToSidebar(toc, collapsable); if (!sidebarObj.sidebar.length) { log(chalk.yellow(`warning: 该目录 "${toc}" 内部没有任何文件或文件序号出错,将忽略生成对应侧边栏`)) return; } sidebarData[`/${path.basename(toc)}/`] = sidebarObj.sidebar sidebarData.catalogue = sidebarObj.catalogueData } }) return sidebarData } module.exports = createSidebarData; /** * 读取指定目录下的文件绝对路径 * @param {String} root 指定的目录 */ function readTocs (root) { const result = []; const files = fs.readdirSync(root); // 读取目录,返回数组,成员是root底下所有的目录名 (包含文件夹和文件) files.forEach(name => { const file = path.resolve(root, name); // 将路径或路径片段的序列解析为绝对路径 if (fs.statSync(file).isDirectory() && name !== '.vuepress' && name !== '@pages') { // 是否为文件夹目录,并排除.vuepress文件夹 result.push(file); } }) return result; } /** * 将碎片化文章目录(_posts)映射为对应的侧边栏配置数据 * @param {String} root */ function mapTocToPostSidebar (root) { let postSidebar = [] // 碎片化文章数据 const files = fs.readdirSync(root); // 读取目录(文件和文件夹),返回数组 files.forEach(filename => { const file = path.resolve(root, filename); // 方法:将路径或路径片段的序列解析为绝对路径 const stat = fs.statSync(file); // 文件信息 const fileNameArr = filename.split('.'); if (fileNameArr.length > 2) { log(chalk.yellow(`warning: 该文件 "${file}" 在_posts文件夹中,不应有序号,且文件名中间不应有'.'`)) return } if (stat.isDirectory()) { // 是文件夹目录 // log(chalk.yellow(`warning: 该目录 "${file}" 内文件无法生成侧边栏,_posts文件夹里面不能有二级目录。`)) return } let [title, type] = filename.split('.'); if (type !== 'md') { log(chalk.yellow(`warning: 该文件 "${file}" 非.md格式文件,不支持该文件类型`)) return; } const contentStr = fs.readFileSync(file, 'utf8') // 读取md文件内容,返回字符串 const { data } = matter(contentStr) // 解析出front matter数据 const permalink = data.permalink || '' if (data.title) { title = data.title } postSidebar.push([filename, title, permalink]); // [<路径>, <标题>, <永久链接>] }) return postSidebar } /** * 将目录映射为对应的侧边栏配置数据 * @param {String} root * @param {Boolean} collapsable * @param {String} prefix */ function mapTocToSidebar (root, collapsable, prefix = '') { let sidebar = []; // 结构化文章侧边栏数据 const files = fs.readdirSync(root); // 读取目录(文件和文件夹),返回数组 files.forEach(filename => { const file = path.resolve(root, filename); // 方法:将路径或路径片段的序列解析为绝对路径 const stat = fs.statSync(file); // 文件信息 let [order, title, type] = filename.split('.'); order = parseInt(order, 10); if (isNaN(order) || order < 0) { log(chalk.yellow(`warning: 该文件 "${file}" 序号出错,请填写正确的序号`)) return; } if (sidebar[order]) { // 判断序号是否已经存在 log(chalk.yellow(`warning: 该文件 "${file}" 的序号在同一级别中重复出现,将会被覆盖`)) } if (stat.isDirectory()) { // 是文件夹目录 sidebar[order] = { title, collapsable, // 是否可折叠,默认true children: mapTocToSidebar(file, collapsable, prefix + filename + '/').sidebar // 子栏路径添加前缀 } } else { // 是文件 if (type !== 'md') { log(chalk.yellow(`warning: 该文件 "${file}" 非.md格式文件,不支持该文件类型`)) return; } const contentStr = fs.readFileSync(file, 'utf8') // 读取md文件内容,返回字符串 const { data } = matter(contentStr) // 解析出front matter数据 const permalink = data.permalink || '' // 目录页对应的永久链接,用于给面包屑提供链接 const { pageComponent } = data if (pageComponent && pageComponent.name === "Catalogue") { catalogueData[title] = permalink } if (data.title) { title = data.title } sidebar[order] = [prefix + filename, title, permalink]; // [<路径>, <标题>, <永久链接>] } }) sidebar = sidebar.filter(item => item !== null && item !== undefined); return { sidebar, catalogueData }; }