diff --git a/scripts/README.md b/scripts/README.md index 28d22d0d..d6e4bb60 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,11 +1,3 @@ -## scripts/autoInstall - -检查各个 workspace package 中的 vuepress 相关依赖,并更新到最新版本。 - -``` sh -pnpm autoUpdate -``` - ## scripts/create 在 `packages/` 目录下生成一个新的 插件包 diff --git a/theme/src/client/config.ts b/theme/src/client/config.ts index e08aa266..ae380bbc 100644 --- a/theme/src/client/config.ts +++ b/theme/src/client/config.ts @@ -11,7 +11,6 @@ import NotFound from './layouts/NotFound.vue' export default defineClientConfig({ enhance({ app, router }) { // global component - app.component('Badge', Badge) app.component('ExternalLinkIcon', ExternalLinkIcon) diff --git a/theme/src/node/autoFrontmatter.ts b/theme/src/node/autoFrontmatter.ts index a7411f69..b042c525 100644 --- a/theme/src/node/autoFrontmatter.ts +++ b/theme/src/node/autoFrontmatter.ts @@ -1,6 +1,4 @@ -import fs from 'node:fs' import path from 'node:path' -import process from 'node:process' import type { App } from '@vuepress/core' import { resolveLocalePath } from '@vuepress/shared' import type { @@ -8,28 +6,13 @@ import type { FrontmatterArray, FrontmatterObject, } from '@vuepress-plume/plugin-auto-frontmatter' -import type { NotesItem } from '@vuepress-plume/plugin-notes-data' import { format } from 'date-fns' -import { customAlphabet } from 'nanoid' +import { uniq } from '@pengzhanbo/utils' import type { PlumeThemeLocaleOptions, PlumeThemePluginOptions, } from '../shared/index.js' - -const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8) -function getPackage() { - let pkg = {} as any - try { - const content = fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8') - pkg = JSON.parse(content) - } - catch {} - return pkg -} - -function normalizePath(dir: string) { - return dir.replace(/\\+/g, '/') -} +import { getCurrentDirname, getPackage, nanoid, pathJoin } from './utils.js' export default function autoFrontmatter( app: App, @@ -38,17 +21,17 @@ export default function autoFrontmatter( ): AutoFrontmatterOptions { const sourceDir = app.dir.source() const pkg = getPackage() - const articlePrefix = localeOption.article || '/article/' + const { locales = {}, avatar, article: articlePrefix = '/article/' } = localeOption + const { frontmatter } = options - const locales = (app.siteData.locales || {}) as PlumeThemeLocaleOptions - const localesNotesDirs = Object.keys(locales) + const localesNotesDirs = Object.keys(app.siteData.locales || {}) .map((locale) => { // fixed: #15 - const notes = localeOption.locales?.[locale]?.notes + const notes = locales[locale]?.notes if (!notes) return '' - const dir = notes.dir - return dir ? normalizePath(path.join(locale, dir)).replace(/^\//, '') : '' + + return notes.dir ? pathJoin(locale, notes.dir).replace(/^\//, '') : '' }) .filter(Boolean) @@ -58,7 +41,7 @@ export default function autoFrontmatter( return author if (data.friends) return - return localeOption.avatar?.name || pkg.author || '' + return avatar?.name || pkg.author || '' }, createTime(formatTime: string, { createTime }, data: any) { if (formatTime) @@ -70,20 +53,20 @@ export default function autoFrontmatter( } const resolveLocale = (filepath: string) => { - const file = normalizePath( - path.join('/', path.relative(sourceDir, filepath)), - ) + const file = pathJoin('/', path.relative(sourceDir, filepath)) + return resolveLocalePath(localeOption.locales!, file) } const notesByLocale = (locale: string) => { - const notes = localeOption.locales![locale]?.notes || localeOption.notes + const notes = locales[locale]?.notes || localeOption.notes if (notes === false) return undefined return notes } + const findNote = (filepath: string) => { - const file = path.join('/', path.relative(sourceDir, filepath)) - const locale = resolveLocalePath(localeOption.locales!, normalizePath(file)) + const file = pathJoin('/', path.relative(sourceDir, filepath)) + const locale = resolveLocalePath(locales, file) const notes = notesByLocale(locale) if (!notes) return undefined @@ -94,22 +77,15 @@ export default function autoFrontmatter( ) } - const getCurrentDirname = (note: NotesItem | undefined, filepath: string) => { - const dirList = normalizePath(note?.dir || path.dirname(filepath)) - .replace(/^\/|\/$/g, '') - .split('/') - return dirList.length > 0 ? dirList[dirList.length - 1] : '' - } return { - include: options.frontmatter?.include ?? ['**/*.md'], - exclude: options.frontmatter?.exclude ?? ['.vuepress/**/*', 'node_modules'], - frontmatter: options.frontmatter?.frontmatter ?? [ + include: frontmatter?.include ?? ['**/*.md'], + exclude: uniq(['.vuepress/**/*', 'node_modules', ...(frontmatter?.exclude ?? [])]), + + frontmatter: [ localesNotesDirs.length ? { // note 首页链接 - include: localesNotesDirs.map(dir => - normalizePath(path.join(dir, '**/{readme,README,index}.md')), - ), + include: localesNotesDirs.map(dir => pathJoin(dir, '**/{readme,README,index}.md')), frontmatter: { title(title: string, { filepath }) { if (title) @@ -117,7 +93,7 @@ export default function autoFrontmatter( const note = findNote(filepath) if (note?.text) return note.text - return getCurrentDirname(note, filepath) || '' + return getCurrentDirname(note?.dir, filepath) || '' }, ...baseFrontmatter, permalink(permalink: string, { filepath }, data: any) { @@ -128,13 +104,11 @@ export default function autoFrontmatter( const locale = resolveLocale(filepath) const notes = notesByLocale(locale) const note = findNote(filepath) - return normalizePath( - path.join( - locale, - notes?.link || '', - note?.link || getCurrentDirname(note, filepath), - '/', - ), + return pathJoin( + locale, + notes?.link || '', + note?.link || getCurrentDirname(note?.dir, filepath), + '/', ) }, }, @@ -142,9 +116,7 @@ export default function autoFrontmatter( : '', localesNotesDirs.length ? { - include: localesNotesDirs.map(dir => - normalizePath(path.join(dir, '**/**.md')), - ), + include: localesNotesDirs.map(dir => pathJoin(dir, '**/**.md')), frontmatter: { title(title: string, { filepath }) { if (title) @@ -161,14 +133,12 @@ export default function autoFrontmatter( const locale = resolveLocale(filepath) const note = findNote(filepath) const notes = notesByLocale(locale) - return normalizePath( - path.join( - locale, - notes?.link || '', - note?.link || getCurrentDirname(note, filepath), - nanoid(), - '/', - ), + return pathJoin( + locale, + notes?.link || '', + note?.link || getCurrentDirname(note?.dir, filepath), + nanoid(), + '/', ) }, }, @@ -192,9 +162,7 @@ export default function autoFrontmatter( if (permalink) return permalink const locale = resolveLocale(filepath) - return normalizePath( - path.join(locale, articlePrefix, nanoid(), '/'), - ) + return pathJoin(locale, articlePrefix, nanoid(), '/') }, }, }, diff --git a/theme/src/node/plugins.ts b/theme/src/node/plugins.ts index 99dc5aa1..b7d70a41 100644 --- a/theme/src/node/plugins.ts +++ b/theme/src/node/plugins.ts @@ -18,8 +18,8 @@ import { iconifyPlugin } from '@vuepress-plume/plugin-iconify' import { notesDataPlugin } from '@vuepress-plume/plugin-notes-data' import { shikijiPlugin } from '@vuepress-plume/plugin-shikiji' import { commentPlugin } from 'vuepress-plugin-comment2' -import { mdEnhancePlugin } from 'vuepress-plugin-md-enhance' -import { useReadingTimePlugin } from 'vuepress-plugin-reading-time2' +import { type MarkdownEnhanceOptions, mdEnhancePlugin } from 'vuepress-plugin-md-enhance' +import { readingTimePlugin } from 'vuepress-plugin-reading-time2' import { seoPlugin } from 'vuepress-plugin-seo2' import { sitemapPlugin } from 'vuepress-plugin-sitemap2' import type { @@ -41,10 +41,7 @@ export function setupPlugins(app: App, options: PlumeThemePluginOptions, localeO }) .filter(Boolean) - if (options.readingTime !== false) - useReadingTimePlugin(app, options.readingTime || {}, true) - - return [ + const plugins: PluginConfig = [ palettePlugin({ preset: 'sass' }), themeDataPlugin({ @@ -55,6 +52,7 @@ export function setupPlugins(app: App, options: PlumeThemePluginOptions, localeO : undefined, } as any, }), + autoFrontmatterPlugin(autoFrontmatter(app, options, localeOptions)), blogDataPlugin({ @@ -85,8 +83,6 @@ export function setupPlugins(app: App, options: PlumeThemePluginOptions, localeO }, }), - localeOptions.notes ? notesDataPlugin(localeOptions.notes) : [], - iconifyPlugin(), activeHeaderLinksPlugin({ @@ -95,103 +91,124 @@ export function setupPlugins(app: App, options: PlumeThemePluginOptions, localeO delay: 200, offset: 20, }), - - options.nprogress !== false ? nprogressPlugin() : [], - - options.git !== false - ? gitPlugin({ - createdTime: false, - updatedTime: localeOptions.lastUpdated !== false, - contributors: localeOptions.contributors !== false, - }) - : [], - - options.mediumZoom !== false - ? mediumZoomPlugin({ - selector: '.plume-content > img, .plume-content :not(a) > img', - zoomOptions: { - background: 'var(--vp-c-bg)', - }, - delay: 300, - }) - : [], - - options.caniuse !== false - ? caniusePlugin( - options.caniuse || { - mode: 'embed', - }, - ) - : [], - - options.externalLinkIcon !== false - ? externalLinkIconPlugin({ - locales: Object.entries(localeOptions.locales || {}).reduce( - (result: Record, [key, value]) => { - result[key] = { - openInNewWindow: - value.openInNewWindow ?? localeOptions.openInNewWindow, - } - return result - }, - {}, - ), - }) - : [], - - options.search !== false ? searchPlugin(options.search) : [], - options.docsearch !== false && !options.search - ? docsearchPlugin(options.docsearch!) - : [], - - options.shikiji !== false - ? shikijiPlugin({ - theme: { light: 'vitesse-light', dark: 'vitesse-dark' }, - ...(options.shikiji ?? {}), - }) - : [], - - options.copyCode !== false - ? copyCodePlugin({ - selector: '.plume-content div[class*="language-"] pre', - ...options.copyCode, - }) - : [], - - options.markdownEnhance !== false - ? mdEnhancePlugin( - Object.assign( - { - hint: true, // info note tip warning danger details d - codetabs: true, - tabs: true, - align: true, - mark: true, - tasklist: true, - demo: true, - attrs: true, - }, - options.markdownEnhance || {}, - ), - ) - : [], - - options.comment !== false ? commentPlugin(options.comment || {}) : [], - - options.baiduTongji !== false && options.baiduTongji?.key - ? baiduTongjiPlugin(options.baiduTongji) - : [], - - options.sitemap !== false && localeOptions.hostname && isProd - ? sitemapPlugin({ - hostname: localeOptions.hostname, - }) - : [], - options.seo !== false && localeOptions.hostname && isProd - ? seoPlugin({ - hostname: localeOptions.hostname || '', - author: localeOptions.avatar?.name, - }) - : [], ] + + if (options.readingTime !== false) + plugins.push(readingTimePlugin(options.readingTime || {})) + + if (localeOptions.notes) + plugins.push(notesDataPlugin(localeOptions.notes)) + + if (options.nprogress !== false) + plugins.push(nprogressPlugin()) + + if (options.git !== false) { + plugins.push(gitPlugin({ + createdTime: false, + updatedTime: localeOptions.lastUpdated !== false, + contributors: localeOptions.contributors !== false, + })) + } + + if (options.mediumZoom !== false) { + plugins.push(mediumZoomPlugin({ + selector: '.plume-content > img, .plume-content :not(a) > img', + zoomOptions: { + background: 'var(--vp-c-bg)', + }, + delay: 300, + })) + } + + if (options.caniuse !== false) { + plugins.push(caniusePlugin( + options.caniuse || { + mode: 'embed', + }, + )) + } + + if (options.externalLinkIcon !== false) { + plugins.push(externalLinkIconPlugin({ + locales: Object.entries(localeOptions.locales || {}).reduce( + (result: Record, [key, value]) => { + result[key] = { + openInNewWindow: + value.openInNewWindow ?? localeOptions.openInNewWindow, + } + return result + }, + {}, + ), + })) + } + + if (options.search !== false) + plugins.push(searchPlugin(options.search)) + + if (options.docsearch !== false && !options.search) { + if (options.docsearch?.appId && options.docsearch?.apiKey) { + plugins.push(docsearchPlugin(options.docsearch)) + } + else { + console.error( + 'docsearch plugin: appId and apiKey are both required', + ) + } + } + + if (options.shikiji !== false) { + plugins.push(shikijiPlugin({ + theme: { light: 'vitesse-light', dark: 'vitesse-dark' }, + ...(options.shikiji ?? {}), + })) + } + + if (options.copyCode !== false) { + plugins.push(copyCodePlugin({ + selector: '.plume-content div[class*="language-"] pre', + ...options.copyCode, + })) + } + + if (options.markdownEnhance !== false) { + plugins.push(mdEnhancePlugin( + Object.assign( + { + hint: true, // info note tip warning danger details + codetabs: true, + tabs: true, + align: true, + mark: true, + tasklist: true, + demo: true, + attrs: true, + sup: true, + sub: true, + } as MarkdownEnhanceOptions, + options.markdownEnhance || {}, + ), + )) + } + + if (options.comment !== false) + plugins.push(commentPlugin(options.comment || {})) + + if (options.baiduTongji !== false && options.baiduTongji?.key) + plugins.push(baiduTongjiPlugin(options.baiduTongji)) + + if (options.sitemap !== false && localeOptions.hostname && isProd) { + plugins.push(sitemapPlugin({ + hostname: localeOptions.hostname, + })) + } + + if (options.seo !== false && localeOptions.hostname && isProd) { + plugins.push(seoPlugin({ + hostname: localeOptions.hostname || '', + author: localeOptions.avatar?.name, + })) + } + + return plugins } diff --git a/theme/src/node/setupPages.ts b/theme/src/node/setupPages.ts index fd4d2437..566cbb9a 100644 --- a/theme/src/node/setupPages.ts +++ b/theme/src/node/setupPages.ts @@ -6,10 +6,7 @@ import type { PlumeThemeLocaleOptions, PlumeThemePageData, } from '../shared/index.js' - -function normalizePath(dir: string) { - return dir.replace(/\\+/g, '/') -} +import { normalizePath } from './utils.js' export async function setupPage( app: App, diff --git a/theme/src/node/utils.ts b/theme/src/node/utils.ts new file mode 100644 index 00000000..dd9abbf9 --- /dev/null +++ b/theme/src/node/utils.ts @@ -0,0 +1,33 @@ +import fs from 'node:fs' +import path from 'node:path' +import process from 'node:process' +import { customAlphabet } from 'nanoid' + +export const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8) + +export function getPackage() { + let pkg = {} as any + try { + const content = fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8') + pkg = JSON.parse(content) + } + catch { } + return pkg +} + +const RE_SLASH = /\\+/g +export function normalizePath(dir: string) { + return dir.replace(RE_SLASH, '/') +} + +export function pathJoin(...args: string[]) { + return normalizePath(path.join(...args)) +} + +const RE_START_END_SLASH = /^\/|\/$/g +export function getCurrentDirname(basePath: string | undefined, filepath: string) { + const dirList = normalizePath(basePath || path.dirname(filepath)) + .replace(RE_START_END_SLASH, '') + .split('/') + return dirList.length > 0 ? dirList[dirList.length - 1] : '' +} diff --git a/theme/src/shared/options/plugins.ts b/theme/src/shared/options/plugins.ts index cdd53aee..85d97f5d 100644 --- a/theme/src/shared/options/plugins.ts +++ b/theme/src/shared/options/plugins.ts @@ -50,7 +50,7 @@ export interface PlumeThemePluginOptions { baiduTongji?: false | BaiduTongjiOptions - frontmatter?: AutoFrontmatterOptions + frontmatter?: Omit readingTime?: false | ReadingTimeOptions }