vuepress-theme-plume/packages/theme/src/node/generateFrontmatter.ts
pengzhanbo 253921e53d fix(theme): frontmatter
修复格式化md文件时异步导致的覆盖问题
2022-12-01 15:31:55 +08:00

148 lines
4.8 KiB
TypeScript

import { createRequire } from 'module'
import type { App } from '@vuepress/core'
import { fs, path } from '@vuepress/utils'
import chokidar from 'chokidar'
import { format } from 'date-fns'
import matter from 'gray-matter'
import jsonToYaml from 'json2yaml'
import { customAlphabet } from 'nanoid'
import type {
PlumeThemeLocaleOptions,
PlumeThemeNotesItem,
PlumeThemeNotesOptions,
} from '../shared/index.js'
import type { MarkdownFile } from './utils/index.js'
import { readFile, readFileList } from './utils/index.js'
const require = createRequire(import.meta.url)
export interface GenerateFrontmatter {
formatFrontmatter: () => Promise<void>
watchNewMarkDown: (app: App, watchers: unknown) => void
}
const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8)
const isReadme = function (filepath: string): boolean {
return /((readme)|(index))\.md$/i.test(filepath)
}
export const generateFrontmatter = (
sourceDir: string,
localeOption: PlumeThemeLocaleOptions
): GenerateFrontmatter => {
const { article, notes } = localeOption
const {
notes: noteList,
link: notesLink,
dir: notesDir,
} = notes as PlumeThemeNotesOptions
const matterTask: Record<string, any> = {
title: ({ filepath }: MarkdownFile, title: string): string => {
if (title) return title
const file = path.relative(sourceDir, filepath)
let currentNote: PlumeThemeNotesItem | undefined
if (
notesDir &&
file.startsWith(notesDir.replace(/^\//, '')) &&
isReadme(filepath) &&
(currentNote = noteList.find((note) =>
file.startsWith(path.join(notesDir.replace(/^\//, ''), note.dir))
))
) {
return currentNote.text || currentNote.dir
}
const basename = path.basename(filepath)
if (isReadme(basename)) {
return path.dirname(filepath).split('/').slice(-1)[0]
}
return basename.replace(/^\d+\./, '').replace(path.extname(filepath), '')
},
createTime: ({ createTime }: MarkdownFile, formatTime: string): string => {
if (formatTime) return formatTime
return format(new Date(createTime), 'yyyy/MM/dd hh:mm:ss')
},
author: (_: any, author: string): string => {
if (author) return author
const pkg = require(path.join(process.cwd(), 'package.json'))
return pkg.author
},
// 自动生成永久链接
permalink: ({ filepath }: MarkdownFile, permalink: string): string => {
const file = path.relative(sourceDir, filepath)
if (permalink) {
if (notes && notesDir && file.startsWith(notesDir.replace(/^\//, ''))) {
return permalink
} else {
if (
article &&
permalink.startsWith('/' + article.replace(/^\//, ''))
) {
return permalink
} else {
const per = permalink.split('/')
return path.join(article, per[per.length - 1])
}
}
}
let prefix = ''
if (notes) {
// 表示是以笔记开头的
if (notesDir && file.startsWith(notesDir.replace(/^\//, ''))) {
prefix = notesLink || ''
const currentNote = noteList.find((note) =>
file.startsWith(path.join(notesDir.replace(/^\//, ''), note.dir))
)
currentNote && (prefix = path.join(prefix, currentNote.link))
} else {
prefix = article as string
}
} else {
prefix = article as string
}
return path.join(prefix, isReadme(filepath) ? '' : nanoid(), '/')
},
}
const formatMarkdown = async (file: MarkdownFile): Promise<string> => {
const { data, content } = matter(file.content)
for (const key of Object.keys(matterTask)) {
const value = await matterTask[key](file, data[key])
data[key] = value ?? data[key]
}
const yaml = jsonToYaml
.stringify(data)
.replace(/\n\s{2}/g, '\n')
.replace(/"/g, '')
return `${yaml}---\n${content}`
}
const formatFrontmatter = async (): Promise<void> => {
const files = readFileList(sourceDir)
for (const file of files) {
const relativePath = path.relative(sourceDir, file.filepath)
if (isReadme(relativePath)) return
const content = await formatMarkdown(file)
await fs.writeFile(file.filepath, content, 'utf-8')
}
}
const watchNewMarkDown = (app: App, watchers: any): void => {
const watcher = chokidar.watch('**/*.md', {
ignored: /node_modules/,
cwd: app.options.source,
ignoreInitial: true,
})
watcher.on('add', async (file, stat) => {
const filepath = path.join(app.options.source, file)
stat = stat || fs.statSync(filepath)
const newFile = readFile(filepath, stat)
const content = await formatMarkdown(newFile)
fs.writeFileSync(filepath, content, 'utf-8')
})
watchers.push(watcher)
}
return { formatFrontmatter, watchNewMarkDown }
}