mjz update
This commit is contained in:
185
vdoing/node_utils/getSidebarData.js
Normal file
185
vdoing/node_utils/getSidebarData.js
Normal file
@@ -0,0 +1,185 @@
|
||||
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是每个目录的绝对路径
|
||||
|
||||
if (toc.substr(-6) === '_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 = '', titleTag = '' } = data || {}
|
||||
if (data.title) {
|
||||
title = data.title
|
||||
}
|
||||
const item = [filename, title, permalink]
|
||||
if (titleTag) {
|
||||
item.push(titleTag)
|
||||
}
|
||||
postSidebar.push(item); // [<路径>, <标题>, <永久链接>, <?标题标签>]
|
||||
})
|
||||
|
||||
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); // 文件信息
|
||||
if (filename === '.DS_Store') { // 过滤.DS_Store文件
|
||||
return
|
||||
}
|
||||
// let [order, title, type] = filename.split('.');
|
||||
|
||||
const fileNameArr = filename.split('.')
|
||||
const isDir = stat.isDirectory()
|
||||
let order = '', title = '', type = '';
|
||||
if (fileNameArr.length === 2) {
|
||||
order = fileNameArr[0];
|
||||
title = fileNameArr[1];
|
||||
} else {
|
||||
const firstDotIndex = filename.indexOf('.');
|
||||
const lastDotIndex = filename.lastIndexOf('.');
|
||||
order = filename.substring(0, firstDotIndex);
|
||||
type = filename.substring(lastDotIndex + 1);
|
||||
if (isDir) {
|
||||
title = filename.substring(firstDotIndex + 1);
|
||||
} else {
|
||||
title = filename.substring(firstDotIndex + 1, lastDotIndex);
|
||||
}
|
||||
}
|
||||
|
||||
order = parseInt(order, 10);
|
||||
if (isNaN(order) || order < 0) {
|
||||
log(chalk.yellow(`warning: 该文件 "${file}" 序号出错,请填写正确的序号`))
|
||||
return;
|
||||
}
|
||||
if (sidebar[order]) { // 判断序号是否已经存在
|
||||
log(chalk.yellow(`warning: 该文件 "${file}" 的序号在同一级别中重复出现,将会被覆盖`))
|
||||
}
|
||||
if (isDir) { // 是文件夹目录
|
||||
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 = '', titleTag = '' } = data || {}
|
||||
|
||||
// 目录页对应的永久链接,用于给面包屑提供链接
|
||||
const { pageComponent } = data
|
||||
if (pageComponent && pageComponent.name === "Catalogue") {
|
||||
catalogueData[title] = permalink
|
||||
}
|
||||
|
||||
if (data.title) {
|
||||
title = data.title
|
||||
}
|
||||
const item = [prefix + filename, title, permalink]
|
||||
if (titleTag) item.push(titleTag)
|
||||
sidebar[order] = item; // [<路径>, <标题>, <永久链接>, <?标题标签>]
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
sidebar = sidebar.filter(item => item !== null && item !== undefined);
|
||||
return {
|
||||
sidebar,
|
||||
catalogueData
|
||||
};
|
||||
}
|
||||
82
vdoing/node_utils/handlePage.js
Normal file
82
vdoing/node_utils/handlePage.js
Normal file
@@ -0,0 +1,82 @@
|
||||
// 生成或删除页面(分类页、标签页、归档页...)
|
||||
|
||||
const fs = require('fs'); // 文件模块
|
||||
const path = require('path'); // 路径模块
|
||||
const chalk = require('chalk') // 命令行打印美化
|
||||
const { type } = require('./modules/fn');
|
||||
const log = console.log
|
||||
|
||||
function createPage (sourceDir, page) {
|
||||
const dirPath = path.join(sourceDir, '@pages') // 生成的文件夹路径
|
||||
|
||||
// 文件夹不存在时
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath) // 创建文件夹
|
||||
}
|
||||
|
||||
const pagePath = path.join(dirPath, `${page}.md`) // 生成的文件路径
|
||||
|
||||
// 文件已经存在时跳出
|
||||
if (fs.existsSync(pagePath)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 注意:反引号字符串的格式会映射到文件
|
||||
let content = ''
|
||||
if (page.indexOf('categories') > -1) {
|
||||
content = `---
|
||||
categoriesPage: true
|
||||
title: 分类
|
||||
permalink: /categories/
|
||||
article: false
|
||||
---`
|
||||
} else if (page.indexOf('tags') > -1) {
|
||||
content = `---
|
||||
tagsPage: true
|
||||
title: 标签
|
||||
permalink: /tags/
|
||||
article: false
|
||||
---`
|
||||
} else if (page.indexOf('archives') > -1) {
|
||||
content = `---
|
||||
archivesPage: true
|
||||
title: 归档
|
||||
permalink: /archives/
|
||||
article: false
|
||||
---`
|
||||
}
|
||||
|
||||
if (content) {
|
||||
fs.writeFileSync(pagePath, content)
|
||||
log(chalk.blue('tip ') + chalk.green(`create page(生成页面): ${pagePath}`))
|
||||
}
|
||||
}
|
||||
|
||||
// 删除页面文件
|
||||
function deletePage (sourceDir, page) {
|
||||
const dirPath = path.join(sourceDir, '@pages') // 文件夹路径
|
||||
const pagePath = path.join(dirPath, `${page}.md`) // 文件路径
|
||||
|
||||
// 文件是否存在
|
||||
if (fs.existsSync(pagePath)) {
|
||||
fs.unlinkSync(pagePath)
|
||||
log(chalk.blue('tip ') + chalk.green(`delete page(删除页面): ${pagePath}`))
|
||||
}
|
||||
deleteDir(dirPath)
|
||||
}
|
||||
|
||||
// 删除文件夹
|
||||
function deleteDir (dirPath) {
|
||||
if (fs.existsSync(dirPath)) {
|
||||
const files = fs.readdirSync(dirPath)
|
||||
if (type(files) === 'array' && files.length === 0) {
|
||||
fs.rmdirSync(dirPath)
|
||||
log(chalk.blue('tip ') + chalk.green(`delete dir(删除目录): ${dirPath}`))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createPage,
|
||||
deletePage
|
||||
}
|
||||
21
vdoing/node_utils/modules/fn.js
Normal file
21
vdoing/node_utils/modules/fn.js
Normal file
@@ -0,0 +1,21 @@
|
||||
// 类型判断
|
||||
exports.type = function (o) {
|
||||
var s = Object.prototype.toString.call(o)
|
||||
return s.match(/\[object (.*?)\]/)[1].toLowerCase()
|
||||
}
|
||||
|
||||
// 修复date时区格式的问题
|
||||
exports.repairDate = function (date) {
|
||||
date = new Date(date);
|
||||
return `${date.getUTCFullYear()}-${zero(date.getUTCMonth() + 1)}-${zero(date.getUTCDate())} ${zero(date.getUTCHours())}:${zero(date.getUTCMinutes())}:${zero(date.getUTCSeconds())}`;
|
||||
}
|
||||
|
||||
// 日期的格式
|
||||
exports.dateFormat = function (date) {
|
||||
return `${date.getFullYear()}-${zero(date.getMonth() + 1)}-${zero(date.getDate())} ${zero(date.getHours())}:${zero(date.getMinutes())}:${zero(date.getSeconds())}`
|
||||
}
|
||||
|
||||
// 小于10补0
|
||||
function zero (d) {
|
||||
return d.toString().padStart(2, '0')
|
||||
}
|
||||
45
vdoing/node_utils/modules/readFileList.js
Normal file
45
vdoing/node_utils/modules/readFileList.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 读取所有md文件数据
|
||||
*/
|
||||
const fs = require('fs'); // 文件模块
|
||||
const path = require('path'); // 路径模块
|
||||
const chalk = require('chalk') // 命令行打印美化
|
||||
const log = console.log
|
||||
|
||||
function readFileList(dir, filesList = []) {
|
||||
const files = fs.readdirSync(dir);
|
||||
files.forEach((item, index) => {
|
||||
let filePath = path.join(dir, item);
|
||||
const stat = fs.statSync(filePath);
|
||||
if (stat.isDirectory() && item !== '.vuepress' && item !== '@pages') {
|
||||
readFileList(path.join(dir, item), filesList); //递归读取文件
|
||||
} else {
|
||||
if (path.basename(dir) !== 'docs') { // 过滤docs目录级下的文件
|
||||
|
||||
const filename = path.basename(filePath)
|
||||
const fileNameArr = filename.split('.')
|
||||
const firstDotIndex = filename.indexOf('.');
|
||||
const lastDotIndex = filename.lastIndexOf('.');
|
||||
|
||||
let name = null, type = null;
|
||||
if (fileNameArr.length === 2) { // 没有序号的文件
|
||||
name = fileNameArr[0]
|
||||
type = fileNameArr[1]
|
||||
} else if (fileNameArr.length >= 3) { // 有序号的文件(或文件名中间有'.')
|
||||
name = filename.substring(firstDotIndex + 1, lastDotIndex)
|
||||
type = filename.substring(lastDotIndex + 1)
|
||||
}
|
||||
|
||||
if (type === 'md') { // 过滤非md文件
|
||||
filesList.push({
|
||||
name,
|
||||
filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return filesList;
|
||||
}
|
||||
|
||||
module.exports = readFileList;
|
||||
172
vdoing/node_utils/setFrontmatter.js
Normal file
172
vdoing/node_utils/setFrontmatter.js
Normal file
@@ -0,0 +1,172 @@
|
||||
const fs = require('fs'); // 文件模块
|
||||
const matter = require('gray-matter'); // FrontMatter解析器 https://github.com/jonschlinkert/gray-matter
|
||||
const jsonToYaml = require('json2yaml')
|
||||
const chalk = require('chalk') // 命令行打印美化
|
||||
// const arg = process.argv.splice(2)[0]; // 获取命令行传入的参数
|
||||
const readFileList = require('./modules/readFileList');
|
||||
const { type, repairDate, dateFormat } = require('./modules/fn');
|
||||
const log = console.log
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const PREFIX = '/pages/'
|
||||
|
||||
/**
|
||||
* 给.md文件设置frontmatter(标题、日期、永久链接等数据)
|
||||
*/
|
||||
function setFrontmatter(sourceDir, themeConfig) {
|
||||
const { category: isCategory, tag: isTag, categoryText = '随笔', extendFrontmatter } = themeConfig
|
||||
const files = readFileList(sourceDir) // 读取所有md文件数据
|
||||
// 扩展自定义生成frontmatter
|
||||
const extendFrontmatterStr = extendFrontmatter ?
|
||||
jsonToYaml.stringify(extendFrontmatter)
|
||||
.replace(/\n\s{2}/g, "\n")
|
||||
.replace(/"|---\n/g, "")
|
||||
: '';
|
||||
|
||||
files.forEach(file => {
|
||||
let dataStr = fs.readFileSync(file.filePath, 'utf8');// 读取每个md文件内容
|
||||
|
||||
// fileMatterObj => {content:'剔除frontmatter后的文件内容字符串', data:{<frontmatter对象>}, ...}
|
||||
const fileMatterObj = matter(dataStr, {});
|
||||
|
||||
if (Object.keys(fileMatterObj.data).length === 0) { // 未定义FrontMatter数据
|
||||
const stat = fs.statSync(file.filePath);
|
||||
const dateStr = dateFormat(
|
||||
getBirthtime(stat)
|
||||
); // 文件的创建时间
|
||||
const categories = getCategories(
|
||||
file,
|
||||
categoryText
|
||||
);
|
||||
|
||||
let cateLabelStr = '';
|
||||
categories.forEach(item => {
|
||||
cateLabelStr += os.EOL + ' - ' + item
|
||||
});
|
||||
|
||||
let cateStr = '';
|
||||
if (!(isCategory === false)) {
|
||||
cateStr = os.EOL + 'categories:' + cateLabelStr
|
||||
};
|
||||
|
||||
// 注意下面这些反引号字符串的格式会映射到文件
|
||||
const tagsStr = isTag === false ? '' : `
|
||||
tags:
|
||||
- `;
|
||||
|
||||
const fmData = `---
|
||||
title: ${file.name}
|
||||
date: ${dateStr}
|
||||
permalink: ${getPermalink()}${file.filePath.indexOf('_posts') > -1 ? os.EOL + 'sidebar: auto' : ''}${cateStr}${tagsStr}
|
||||
${extendFrontmatterStr}---`;
|
||||
|
||||
fs.writeFileSync(file.filePath, `${fmData}${os.EOL}${fileMatterObj.content}`); // 写入
|
||||
log(chalk.blue('tip ') + chalk.green(`write frontmatter(写入frontmatter):${file.filePath} `))
|
||||
|
||||
} else { // 已有FrontMatter
|
||||
let matterData = fileMatterObj.data;
|
||||
let hasChange = false;
|
||||
|
||||
// 已有FrontMatter,但是没有title、date、permalink、categories、tags数据的
|
||||
if (!matterData.hasOwnProperty('title')) { // 标题
|
||||
matterData.title = file.name;
|
||||
hasChange = true;
|
||||
}
|
||||
|
||||
if (!matterData.hasOwnProperty('date')) { // 日期
|
||||
const stat = fs.statSync(file.filePath);
|
||||
matterData.date = dateFormat(getBirthtime(stat));
|
||||
hasChange = true;
|
||||
}
|
||||
|
||||
if (!matterData.hasOwnProperty('permalink')) { // 永久链接
|
||||
matterData.permalink = getPermalink();
|
||||
hasChange = true;
|
||||
}
|
||||
|
||||
if (file.filePath.indexOf('_posts') > -1 && !matterData.hasOwnProperty('sidebar')) { // auto侧边栏,_posts文件夹特有
|
||||
matterData.sidebar = "auto";
|
||||
hasChange = true;
|
||||
}
|
||||
|
||||
if (!matterData.hasOwnProperty('pageComponent') && matterData.article !== false) { // 是文章页才添加分类和标签
|
||||
if (isCategory !== false && !matterData.hasOwnProperty('categories')) { // 分类
|
||||
matterData.categories = getCategories(file, categoryText)
|
||||
hasChange = true;
|
||||
}
|
||||
if (isTag !== false && !matterData.hasOwnProperty('tags')) { // 标签
|
||||
matterData.tags = [''];
|
||||
hasChange = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 扩展自动生成frontmatter的字段
|
||||
if (type(extendFrontmatter) === 'object') {
|
||||
Object.keys(extendFrontmatter).forEach(keyName => {
|
||||
if (!matterData.hasOwnProperty(keyName)) {
|
||||
matterData[keyName] = extendFrontmatter[keyName]
|
||||
hasChange = true;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (hasChange) {
|
||||
if (matterData.date && type(matterData.date) === 'date') {
|
||||
matterData.date = repairDate(matterData.date) // 修复时间格式
|
||||
}
|
||||
const newData = jsonToYaml.stringify(matterData).replace(/\n\s{2}/g, "\n").replace(/"/g, "") + '---' + os.EOL + fileMatterObj.content;
|
||||
fs.writeFileSync(file.filePath, newData); // 写入
|
||||
log(chalk.blue('tip ') + chalk.green(`write frontmatter(写入frontmatter):${file.filePath} `))
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取分类数据
|
||||
function getCategories(file, categoryText) {
|
||||
let categories = []
|
||||
|
||||
if (file.filePath.indexOf('_posts') === -1) {
|
||||
// 不在_posts文件夹
|
||||
let filePathArr = file.filePath.split(path.sep) // path.sep用于兼容不同系统下的路径斜杠
|
||||
filePathArr.pop()
|
||||
|
||||
let ind = filePathArr.indexOf('docs')
|
||||
if (ind !== -1) {
|
||||
while (filePathArr[++ind] !== undefined) {
|
||||
const item = filePathArr[ind]
|
||||
const firstDotIndex = item.indexOf('.');
|
||||
categories.push(item.substring(firstDotIndex + 1) || '') // 获取分类
|
||||
// categories.push(filePathArr[ind].split('.').pop()) // 获取分类
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 碎片化文章的分类生成
|
||||
const matchResult = file.filePath.match(/_posts\/(\S*)\//);
|
||||
const resultStr = matchResult ? matchResult[1] : ''
|
||||
const resultArr = resultStr.split('/').filter(Boolean)
|
||||
|
||||
if (resultArr.length) {
|
||||
categories.push(...resultArr)
|
||||
} else {
|
||||
categories.push(categoryText)
|
||||
}
|
||||
}
|
||||
return categories
|
||||
}
|
||||
|
||||
// 获取文件创建时间
|
||||
function getBirthtime(stat) {
|
||||
// 在一些系统下无法获取birthtime属性的正确时间,使用atime代替
|
||||
return stat.birthtime.getFullYear() != 1970 ? stat.birthtime : stat.atime
|
||||
}
|
||||
|
||||
// 定义永久链接数据
|
||||
function getPermalink() {
|
||||
return `${PREFIX + (Math.random() + Math.random()).toString(16).slice(2, 8)}/`
|
||||
}
|
||||
|
||||
|
||||
module.exports = setFrontmatter;
|
||||
Reference in New Issue
Block a user