From 48c6113f77851ded18105f4e43faa44f0b4a2f26 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Sun, 26 May 2024 13:11:21 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E8=B0=83=E6=95=B4=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/netlify-functions/测试文件.md | 6 + plugins/plugin-notes-data/src/shared/index.ts | 2 +- theme/src/client/composables/darkMode.ts | 54 ++-- theme/src/client/composables/data.ts | 39 +++ theme/src/client/composables/encrypt.ts | 6 +- theme/src/client/composables/index.ts | 1 + theme/src/client/composables/sidebar.ts | 4 +- theme/src/client/config.ts | 7 +- theme/src/node/autoFrontmatter.ts | 190 -------------- theme/src/node/config/index.ts | 1 + theme/src/node/config/resolveLocaleOptions.ts | 6 +- theme/src/node/config/resolveNotesOptions.ts | 37 +++ theme/src/node/config/resolveThemeData.ts | 46 ++-- theme/src/node/locales/zh.ts | 2 +- theme/src/node/{ => plugins}/blogTags.ts | 0 .../containerPlugins.ts} | 2 +- .../{plugins.ts => plugins/getPlugins.ts} | 131 ++++------ theme/src/node/plugins/index.ts | 1 + .../plugins/resolveAutoFrontmatterOptions.ts | 240 ++++++++++++++++++ .../node/plugins/resolveBlogDataOptions.ts | 56 ++++ theme/src/node/resolveLocaleOptions.ts | 36 --- theme/src/node/resolveNotesList.ts | 63 ----- theme/src/node/setupPages.ts | 97 +++---- theme/src/node/theme.ts | 21 +- theme/src/node/utils.ts | 16 +- theme/src/shared/blog.ts | 14 + 26 files changed, 580 insertions(+), 498 deletions(-) create mode 100644 docs/notes/plugins/netlify-functions/测试文件.md create mode 100644 theme/src/client/composables/data.ts delete mode 100644 theme/src/node/autoFrontmatter.ts create mode 100644 theme/src/node/config/resolveNotesOptions.ts rename theme/src/node/{ => plugins}/blogTags.ts (100%) rename theme/src/node/{container.ts => plugins/containerPlugins.ts} (97%) rename theme/src/node/{plugins.ts => plugins/getPlugins.ts} (52%) create mode 100644 theme/src/node/plugins/index.ts create mode 100644 theme/src/node/plugins/resolveAutoFrontmatterOptions.ts create mode 100644 theme/src/node/plugins/resolveBlogDataOptions.ts delete mode 100644 theme/src/node/resolveLocaleOptions.ts delete mode 100644 theme/src/node/resolveNotesList.ts diff --git a/docs/notes/plugins/netlify-functions/测试文件.md b/docs/notes/plugins/netlify-functions/测试文件.md new file mode 100644 index 00000000..f19648dd --- /dev/null +++ b/docs/notes/plugins/netlify-functions/测试文件.md @@ -0,0 +1,6 @@ +--- +title: 测试文件 +author: Plume Theme +createTime: 2024/05/26 02:11:27 +permalink: /plugins/9kekfblc/ +--- diff --git a/plugins/plugin-notes-data/src/shared/index.ts b/plugins/plugin-notes-data/src/shared/index.ts index 2b7a3773..0253d665 100644 --- a/plugins/plugin-notes-data/src/shared/index.ts +++ b/plugins/plugin-notes-data/src/shared/index.ts @@ -1,7 +1,7 @@ export interface NotesDataOptions { /** * 保存所有笔记的目录 - * @default '/notes' + * @default '/notes/' */ dir: string /** diff --git a/theme/src/client/composables/darkMode.ts b/theme/src/client/composables/darkMode.ts index fd3e37f9..a6fcda8c 100644 --- a/theme/src/client/composables/darkMode.ts +++ b/theme/src/client/composables/darkMode.ts @@ -1,39 +1,45 @@ -import { inject, onMounted, ref } from 'vue' +import { useDark } from '@vueuse/core' +import { inject, ref } from 'vue' import type { App, InjectionKey, Ref } from 'vue' +import { useThemeData } from './themeData.js' -export type DarkModeRef = Ref +type DarkModeRef = Ref export const darkModeSymbol: InjectionKey = Symbol( __VUEPRESS_DEV__ ? 'darkMode' : '', ) -/** - * Inject dark mode global computed - */ -export function useDarkMode(): DarkModeRef { - const isDark = inject(darkModeSymbol) - if (isDark === undefined) - throw new Error('useDarkMode() is called without provider.') +export function setupDarkMode(app: App): void { + const themeLocale = useThemeData() - return isDark -} + const appearance = themeLocale.value.appearance + const isDark + = appearance === 'force-dark' + ? ref(true) + : appearance + ? useDark({ + storageKey: 'vuepress-theme-appearance', + disableTransition: false, + initialValue: () => + typeof appearance === 'string' ? appearance : 'auto', + ...(typeof appearance === 'object' ? appearance : {}), + }) + : ref(false) -/** - * Create dark mode ref and provide as global computed in setup - */ -export function setupDarkMode(): void { - const isDark = useDarkMode() - onMounted(() => { - if (document.documentElement.classList.contains('dark')) - isDark.value = true - }) -} - -export function injectDarkMode(app: App): void { - const isDark = ref(false) app.provide(darkModeSymbol, isDark) Object.defineProperty(app.config.globalProperties, '$isDark', { get: () => isDark, }) } + +/** + * Inject dark mode global computed + */ +export function useDarkMode(): DarkModeRef { + const isDarkMode = inject(darkModeSymbol) + if (!isDarkMode) + throw new Error('useDarkMode() is called without provider.') + + return isDarkMode +} diff --git a/theme/src/client/composables/data.ts b/theme/src/client/composables/data.ts new file mode 100644 index 00000000..11f416f5 --- /dev/null +++ b/theme/src/client/composables/data.ts @@ -0,0 +1,39 @@ +import type { Ref } from 'vue' +import type { ThemeLocaleDataRef } from '@vuepress/plugin-theme-data/client' +import { + usePageData, + usePageFrontmatter, + useSiteLocaleData, +} from 'vuepress/client' +import type { + PageDataRef, + PageFrontmatterRef, + SiteLocaleDataRef, +} from 'vuepress/client' +import type { + PlumeThemeLocaleData, + PlumeThemePageData, + PlumeThemePageFrontmatter, +} from '../../shared/index.js' +import { useThemeLocaleData } from './themeData.js' +import { hashRef } from './hash.js' +import { useDarkMode } from './darkMode.js' + +export interface Data { + theme: ThemeLocaleDataRef + page: PageDataRef + frontmatter: PageFrontmatterRef + hash: Ref + site: SiteLocaleDataRef + isDark: Ref +} + +export function useData(): Data { + const theme = useThemeLocaleData() + const page = usePageData() + const frontmatter = usePageFrontmatter() + const site = useSiteLocaleData() + const isDark = useDarkMode() + + return { theme, page, frontmatter, hash: hashRef, site, isDark } +} diff --git a/theme/src/client/composables/encrypt.ts b/theme/src/client/composables/encrypt.ts index e52cfb28..b4333371 100644 --- a/theme/src/client/composables/encrypt.ts +++ b/theme/src/client/composables/encrypt.ts @@ -1,8 +1,8 @@ import { compareSync, genSaltSync } from 'bcrypt-ts/browser' import { type Ref, computed } from 'vue' import { hasOwn, useSessionStorage } from '@vueuse/core' -import { usePageData, useRoute } from 'vuepress/client' -import type { PlumeThemePageData } from '../../shared/index.js' +import { useRoute } from 'vuepress/client' +import { useData } from './data.js' declare const __PLUME_ENCRYPT_GLOBAL__: boolean declare const __PLUME_ENCRYPT_SEPARATOR__: string @@ -88,7 +88,7 @@ export function useGlobalEncrypt(): { } export function usePageEncrypt() { - const page = usePageData() + const { page } = useData() const route = useRoute() const hasPageEncrypt = computed(() => ruleList.length ? matches.some(toMatch) : false) diff --git a/theme/src/client/composables/index.ts b/theme/src/client/composables/index.ts index e21a3eec..21de7149 100644 --- a/theme/src/client/composables/index.ts +++ b/theme/src/client/composables/index.ts @@ -9,3 +9,4 @@ export * from './blog.js' export * from './locale.js' export * from './useRouteQuery.js' export * from './watermark.js' +export * from './data.js' diff --git a/theme/src/client/composables/sidebar.ts b/theme/src/client/composables/sidebar.ts index 52e3dc1f..40f4cac0 100644 --- a/theme/src/client/composables/sidebar.ts +++ b/theme/src/client/composables/sidebar.ts @@ -10,7 +10,6 @@ import type { ComputedRef, Ref } from 'vue' import { computed, onMounted, onUnmounted, ref, watch, watchEffect } from 'vue' import type { PlumeThemePageData } from '../../shared/index.js' import { isActive } from '../utils/index.js' -import { useThemeLocaleData } from './themeData.js' import { hashRef } from './hash.js' export { useNotesData } @@ -58,7 +57,6 @@ export function getSidebarFirstLink(sidebar: NotesSidebarItem[]) { export function useSidebar() { const route = useRoute() const notesData = useNotesData() - const theme = useThemeLocaleData() const frontmatter = usePageFrontmatter() const page = usePageData() @@ -74,7 +72,7 @@ export function useSidebar() { }) const sidebar = computed(() => { - return theme.value.notes ? getSidebarList(route.path, notesData.value) : [] + return getSidebarList(route.path, notesData.value) }) const hasSidebar = computed(() => { return ( diff --git a/theme/src/client/config.ts b/theme/src/client/config.ts index c8c65714..4637c6bf 100644 --- a/theme/src/client/config.ts +++ b/theme/src/client/config.ts @@ -4,16 +4,14 @@ import { defineClientConfig } from 'vuepress/client' import type { ClientConfig } from 'vuepress/client' import { h } from 'vue' import Badge from './components/global/Badge.vue' -import ExternalLinkIcon from './components/global/ExternalLinkIcon.vue' -import { injectDarkMode, setupDarkMode, setupWatermark, useScrollPromise } from './composables/index.js' +import { setupDarkMode, setupWatermark, useScrollPromise } from './composables/index.js' import Layout from './layouts/Layout.vue' import NotFound from './layouts/NotFound.vue' import HomeBox from './components/Home/HomeBox.vue' export default defineClientConfig({ enhance({ app, router }) { - injectDarkMode(app) - + setupDarkMode(app) // global component app.component('Badge', Badge) @@ -52,7 +50,6 @@ export default defineClientConfig({ } }, setup() { - setupDarkMode() setupWatermark() }, layouts: { diff --git a/theme/src/node/autoFrontmatter.ts b/theme/src/node/autoFrontmatter.ts deleted file mode 100644 index b808f9b1..00000000 --- a/theme/src/node/autoFrontmatter.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { path } from 'vuepress/utils' -import type { App } from 'vuepress/core' -import { resolveLocalePath } from 'vuepress/shared' -import type { - AutoFrontmatterOptions, - FrontmatterArray, - FrontmatterObject, -} from '@vuepress-plume/plugin-auto-frontmatter' -import { format } from 'date-fns' -import { uniq } from '@pengzhanbo/utils' -import type { - PlumeThemeLocaleOptions, - PlumeThemePluginOptions, -} from '../shared/index.js' -import { getCurrentDirname, getPackage, nanoid, pathJoin } from './utils.js' -import { resolveLinkBySidebar, resolveNotesList } from './resolveNotesList.js' -import { resolveLocaleOptions } from './resolveLocaleOptions.js' - -export default function autoFrontmatter( - app: App, - options: PlumeThemePluginOptions, - localeOptions: PlumeThemeLocaleOptions, -): AutoFrontmatterOptions { - const sourceDir = app.dir.source() - const pkg = getPackage() - const { locales = {}, article: articlePrefix = '/article/' } = localeOptions - const { frontmatter } = options - const avatar = resolveLocaleOptions(localeOptions, 'avatar') - const notesList = resolveNotesList(localeOptions) - const localesNotesDirs = notesList - .map(({ notes, dir }) => { - const _dir = dir?.replace(/^\//, '') - return notes.map(note => pathJoin(_dir, note.dir || '')) - }) - .flat() - .filter(Boolean) - - const baseFrontmatter: FrontmatterObject = { - author(author: string, _, data: any) { - if (author) - return author - if (data.friends) - return - return avatar?.name || pkg.author || '' - }, - createTime(formatTime: string, { createTime }, data: any) { - if (formatTime) - return formatTime - if (data.friends) - return - return format(new Date(createTime), 'yyyy/MM/dd HH:mm:ss') - }, - } - - const resolveLocale = (filepath: string) => { - const file = pathJoin('/', path.relative(sourceDir, filepath)) - - return resolveLocalePath(localeOptions.locales!, file) - } - const notesByLocale = (locale: string) => { - const notes = resolveLocaleOptions(localeOptions, 'notes', locale) - if (notes === false) - return undefined - return notes - } - - const findNote = (filepath: string) => { - const file = pathJoin('/', path.relative(sourceDir, filepath)) - const locale = resolveLocalePath(locales, file) - const notes = notesByLocale(locale) - if (!notes) - return undefined - const notesList = notes?.notes || [] - const notesDir = notes?.dir || '' - return notesList.find(note => - file.startsWith(path.join(locale, notesDir, note.dir)), - ) - } - - return { - include: frontmatter?.include ?? ['**/*.md'], - exclude: uniq(['.vuepress/**/*', 'node_modules', ...(frontmatter?.exclude ?? [])]), - - frontmatter: [ - localesNotesDirs.length - ? { - // note 首页链接 - include: localesNotesDirs.map(dir => pathJoin(dir, '/{readme,README,index}.md')), - frontmatter: { - title(title: string, { filepath }) { - if (title) - return title - const note = findNote(filepath) - if (note?.text) - return note.text - return getCurrentDirname(note?.dir, filepath) || '' - }, - ...baseFrontmatter, - permalink(permalink: string, { filepath }, data: any) { - if (permalink) - return permalink - if (data.friends) - return - const locale = resolveLocale(filepath) - const notes = notesByLocale(locale) - const note = findNote(filepath) - return pathJoin( - locale, - notes?.link || '', - note?.link || getCurrentDirname(note?.dir, filepath), - '/', - ) - }, - }, - } - : '', - localesNotesDirs.length - ? { - include: localesNotesDirs.map(dir => pathJoin(dir, '**/**.md')), - frontmatter: { - title(title: string, { filepath }) { - if (title) - return title - - const note = findNote(filepath) - - let basename = path.basename(filepath, '.md') - if (note?.sidebar === 'auto') - basename = basename.replace(/^\d+\./, '') - - return basename - }, - ...baseFrontmatter, - permalink(permalink: string, { filepath }, data: any) { - if (permalink) - return permalink - if (data.friends) - return - const locale = resolveLocale(filepath) - const notes = notesByLocale(locale) - const note = findNote(filepath) - const args: string[] = [ - locale, - notes?.link || '', - note?.link || getCurrentDirname(note?.dir, filepath), - ] - const sidebar = note?.sidebar - - if (sidebar && sidebar !== 'auto') { - const res = resolveLinkBySidebar(sidebar, pathJoin(notes?.dir || '', note?.dir || '')) - const file = pathJoin('/', path.relative(sourceDir, filepath)) - res[file] && args.push(res[file]) - } - - return pathJoin(...args, nanoid(), '/') - }, - }, - } - : '', - { - include: '**/{readme,README,index}.md', - frontmatter: {}, - }, - { - include: '*', - frontmatter: { - title(title: string, { filepath }) { - if (title) - return title - const basename = path.basename(filepath, '.md') - return basename - }, - ...baseFrontmatter, - permalink(permalink: string, { filepath }) { - if (permalink) - return permalink - const locale = resolveLocale(filepath) - const prefix = resolveLocaleOptions(localeOptions, 'article', locale, false) - const args: string[] = [] - prefix - ? args.push(prefix) - : args.push(locale, articlePrefix) - - return pathJoin(...args, nanoid(), '/') - }, - }, - }, - ].filter(Boolean) as FrontmatterArray, - } -} diff --git a/theme/src/node/config/index.ts b/theme/src/node/config/index.ts index 64d5e38b..99c2e188 100644 --- a/theme/src/node/config/index.ts +++ b/theme/src/node/config/index.ts @@ -3,3 +3,4 @@ export * from './resolveThemeData.js' export * from './resolveSearchOptions.js' export * from './resolvePageHead.js' export * from './resolveEncrypt.js' +export * from './resolveNotesOptions.js' diff --git a/theme/src/node/config/resolveLocaleOptions.ts b/theme/src/node/config/resolveLocaleOptions.ts index 1d48c544..56e09fe6 100644 --- a/theme/src/node/config/resolveLocaleOptions.ts +++ b/theme/src/node/config/resolveLocaleOptions.ts @@ -2,12 +2,14 @@ import { entries, fromEntries, getLocaleConfig } from '@vuepress/helper' import type { App } from 'vuepress' import { LOCALE_OPTIONS } from '../locales/index.js' import type { PlumeThemeLocaleData, PlumeThemeLocaleOptions } from '../../shared/index.js' +import { THEME_NAME } from '../utils.js' const FALLBACK_OPTIONS: PlumeThemeLocaleData = { appearance: true, + blog: { link: '/blog/', pagination: { perPage: 20 }, tags: true, archives: true, tagsLink: '/blog/tags/', archivesLink: '/blog/archives/' }, article: '/article/', - notes: { link: '/', dir: 'notes', notes: [] }, + notes: { link: '/', dir: '/notes/', notes: [] }, navbarSocialInclude: ['github', 'twitter', 'discord', 'facebook'], // page meta @@ -22,7 +24,7 @@ export function resolveLocaleOptions(app: App, { locales, ...options }: PlumeThe ...options, locales: getLocaleConfig({ app, - name: 'vuepress-theme-plume', + name: THEME_NAME, default: LOCALE_OPTIONS, config: fromEntries( entries({ diff --git a/theme/src/node/config/resolveNotesOptions.ts b/theme/src/node/config/resolveNotesOptions.ts new file mode 100644 index 00000000..0ba6f979 --- /dev/null +++ b/theme/src/node/config/resolveNotesOptions.ts @@ -0,0 +1,37 @@ +import type { NotesDataOptions, NotesSidebar } from '@vuepress-plume/plugin-notes-data' +import { entries } from '@vuepress/helper' +import { uniq } from '@pengzhanbo/utils' +import type { PlumeThemeLocaleOptions } from '../..//shared/index.js' +import { withBase } from '../utils.js' + +export function resolveNotesLinkList(localeOptions: PlumeThemeLocaleOptions) { + const locales = localeOptions.locales || {} + const notesLinks: string[] = [] + for (const [locale, opt] of entries(locales)) { + const config = locale === '/' ? (opt.notes || localeOptions.notes) : opt.notes + if (config && config.notes?.length) { + const prefix = config.link || '' + notesLinks.push( + ...config.notes.map( + note => withBase(`${prefix}/${note.link || ''}`, locale), + ), + ) + } + } + + return uniq(notesLinks) +} + +export function resolveNotesOptions(localeOptions: PlumeThemeLocaleOptions): NotesDataOptions[] { + const locales = localeOptions.locales || {} + const notesOptionsList: NotesDataOptions[] = [] + for (const [locale, opt] of entries(locales)) { + const options = locale === '/' ? (opt.notes || localeOptions.notes) : opt.notes + if (options) { + options.dir = withBase(options.dir, locale) + notesOptionsList.push(options) + } + } + + return notesOptionsList +} diff --git a/theme/src/node/config/resolveThemeData.ts b/theme/src/node/config/resolveThemeData.ts index 4efe847a..69a87186 100644 --- a/theme/src/node/config/resolveThemeData.ts +++ b/theme/src/node/config/resolveThemeData.ts @@ -1,10 +1,12 @@ -import { ensureEndingSlash, ensureLeadingSlash, entries, getRootLangPath } from '@vuepress/helper' +import { entries, getRootLangPath } from '@vuepress/helper' import type { App } from 'vuepress' import type { NavItem, PlumeThemeLocaleOptions } from '../../shared/index.js' import { PRESET_LOCALES } from '../locales/index.js' -import { normalizePath } from '../utils.js' +import { withBase } from '../utils.js' const EXCLUDE_LIST = ['locales', 'sidebar', 'navbar', 'notes', 'article'] +// 过滤不需要出现在多语言配置中的字段 +const EXCLUDE_LOCALE_LIST = [...EXCLUDE_LIST, 'blog', 'appearance'] export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): PlumeThemeLocaleOptions { const themeData: PlumeThemeLocaleOptions = { locales: {} } @@ -18,13 +20,15 @@ export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): Pl entries(options.locales || {}).forEach(([locale, opt]) => { themeData.locales![locale] = {} entries(opt).forEach(([key, value]) => { - if (!EXCLUDE_LIST.includes(key)) + if (!EXCLUDE_LOCALE_LIST.includes(key)) themeData.locales![locale][key] = value }) }) + const blog = options.blog || {} + const blogLink = blog.link || '/blog/' entries(options.locales || {}).forEach(([locale, opt]) => { - // 注入预设 导航栏。 + // 注入预设 导航栏 // home | blog | tags | archives if (opt.navbar !== false && opt.navbar?.length === 0) { // fallback navbar option @@ -33,20 +37,19 @@ export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): Pl text: PRESET_LOCALES[localePath].home, link: locale, }] - if (opt.blog) { - navbar.push({ - text: PRESET_LOCALES[localePath].blog, - link: withBase(opt.blog.link ?? '/blog/', locale), - }) - opt.blog.tags && navbar.push({ - text: PRESET_LOCALES[locale].tag, - link: withBase('/tags/', locale), - }) - opt.blog.archives && navbar.push({ - text: PRESET_LOCALES[locale].archive, - link: withBase('/archives/', locale), - }) - } + navbar.push({ + text: PRESET_LOCALES[localePath].blog, + link: withBase(blogLink, locale), + }) + blog.tags !== false && navbar.push({ + text: PRESET_LOCALES[locale].tag, + link: withBase(blog.tagsLink || `${blogLink}/tags/`, locale), + }) + blog.archives !== false && navbar.push({ + text: PRESET_LOCALES[locale].archive, + link: withBase(blog.archivesLink || `${blogLink}/archives/`, locale), + }) + themeData.locales![locale].navbar = navbar } else { @@ -56,10 +59,3 @@ export function resolveThemeData(app: App, options: PlumeThemeLocaleOptions): Pl return themeData } - -function withBase(path: string, base = '/'): string { - path = ensureEndingSlash(ensureLeadingSlash(path)) - if (path.startsWith(base)) - return path - return normalizePath(`${base}${path}`) -} diff --git a/theme/src/node/locales/zh.ts b/theme/src/node/locales/zh.ts index eb1054b1..1728c84f 100644 --- a/theme/src/node/locales/zh.ts +++ b/theme/src/node/locales/zh.ts @@ -37,7 +37,7 @@ export const zhLocale: PlumeThemeLocaleData = { footer: { message: - 'VuePress & vuepress-theme-plume 提供支持', + 'Power by VuePress & vuepress-theme-plume', }, } diff --git a/theme/src/node/blogTags.ts b/theme/src/node/plugins/blogTags.ts similarity index 100% rename from theme/src/node/blogTags.ts rename to theme/src/node/plugins/blogTags.ts diff --git a/theme/src/node/container.ts b/theme/src/node/plugins/containerPlugins.ts similarity index 97% rename from theme/src/node/container.ts rename to theme/src/node/plugins/containerPlugins.ts index ca027a92..49743fb0 100644 --- a/theme/src/node/container.ts +++ b/theme/src/node/plugins/containerPlugins.ts @@ -1,7 +1,7 @@ import { markdownContainerPlugin as containerPlugin } from '@vuepress/plugin-markdown-container' import type { Plugin } from 'vuepress/core' -export const customContainers: Plugin[] = [ +export const customContainerPlugins: Plugin[] = [ /** * :::demo-wrapper img no-padding title="xxx" height="100px" * ::: diff --git a/theme/src/node/plugins.ts b/theme/src/node/plugins/getPlugins.ts similarity index 52% rename from theme/src/node/plugins.ts rename to theme/src/node/plugins/getPlugins.ts index eb3c8bad..6da1dfa8 100644 --- a/theme/src/node/plugins.ts +++ b/theme/src/node/plugins/getPlugins.ts @@ -24,80 +24,43 @@ import type { PlumeThemeEncrypt, PlumeThemeLocaleOptions, PlumeThemePluginOptions, -} from '../shared/index.js' -import autoFrontmatter from './autoFrontmatter.js' -import { resolveLocaleOptions } from './resolveLocaleOptions.js' -import { pathJoin } from './utils.js' -import { resolveNotesList } from './resolveNotesList.js' -import { customContainers } from './container.js' -import { BLOG_TAGS_COLORS_PRESET, generateBlogTagsColors } from './blogTags.js' -import { isEncryptPage } from './config/resolveEncrypt.js' -import { resolveDocsearchOptions, resolveSearchOptions, resolveThemeData } from './config/index.js' +} from '../../shared/index.js' +import { + resolveDocsearchOptions, + resolveNotesOptions, + resolveSearchOptions, + resolveThemeData, +} from '../config/index.js' +import { resolveAutoFrontmatterOptions } from './resolveAutoFrontmatterOptions.js' +import { resolveBlogDataOptions } from './resolveBlogDataOptions.js' +import { customContainerPlugins } from './containerPlugins.js' export interface SetupPluginOptions { app: App - options: PlumeThemePluginOptions + pluginOptions: PlumeThemePluginOptions localeOptions: PlumeThemeLocaleOptions encrypt?: PlumeThemeEncrypt hostname?: string } -export function setupPlugins({ +export function getPlugins({ app, - options, + pluginOptions, localeOptions, encrypt, hostname, }: SetupPluginOptions): PluginConfig { const isProd = !app.env.isDev - const notesList = resolveNotesList(localeOptions) - const notesDirList = notesList - .map(notes => notes.dir && pathJoin(notes.dir, '**').replace(/^\//, '')) - .filter(Boolean) - - const blog = resolveLocaleOptions(localeOptions, 'blog') - const plugins: PluginConfig = [ themeDataPlugin({ themeData: resolveThemeData(app, localeOptions) }), - autoFrontmatterPlugin(autoFrontmatter(app, options, localeOptions)), + autoFrontmatterPlugin(resolveAutoFrontmatterOptions(pluginOptions, localeOptions)), - blogDataPlugin({ - include: blog?.include ?? ['**/*.md'], - exclude: [ - '**/{README,readme,index}.md', - '.vuepress/', - 'node_modules/', - ...(blog?.exclude ?? []), - ...notesDirList, - ].filter(Boolean), - sortBy: 'createTime', - excerpt: true, - pageFilter: (page: any) => page.frontmatter.article !== undefined - ? !!page.frontmatter.article - : true, - extraBlogData(extra) { - extra.tagsColorsPreset = BLOG_TAGS_COLORS_PRESET - extra.tagsColors = {} - }, - extendBlogData: (page: any, extra) => { - const tags = page.frontmatter.tags - generateBlogTagsColors(extra.tagsColors, tags) - const data: Record = { - categoryList: page.data.categoryList, - tags, - sticky: page.frontmatter.sticky, - createTime: page.data.frontmatter.createTime, - lang: page.lang, - } - isEncryptPage(page, encrypt) && (data.encrypt = true) - return data - }, - }), + blogDataPlugin(resolveBlogDataOptions(localeOptions, encrypt)), - notesDataPlugin(notesList), + notesDataPlugin(resolveNotesOptions(localeOptions)), iconifyPlugin(), @@ -110,10 +73,10 @@ export function setupPlugins({ offset: 20, }), - ...customContainers, + ...customContainerPlugins, ] - if (options.readingTime !== false) { + if (pluginOptions.readingTime !== false) { plugins.push(readingTimePlugin({ locales: { '/zh/': { @@ -122,22 +85,22 @@ export function setupPlugins({ time: '约$time分钟', }, }, - ...options.readingTime, + ...pluginOptions.readingTime, })) } - if (options.nprogress !== false) + if (pluginOptions.nprogress !== false) plugins.push(nprogressPlugin()) - if (options.git !== false) { + if (pluginOptions.git ?? isProd) { plugins.push(gitPlugin({ createdTime: false, - updatedTime: resolveLocaleOptions(localeOptions, 'lastUpdated') !== false, - contributors: resolveLocaleOptions(localeOptions, 'contributors') !== false, + updatedTime: true, + contributors: true, })) } - if (options.mediumZoom !== false) { + if (pluginOptions.mediumZoom !== false) { plugins.push(mediumZoomPlugin({ selector: '.plume-content > img, .plume-content :not(a) > img', zoomOptions: { background: 'var(--vp-c-bg)' }, @@ -145,18 +108,18 @@ export function setupPlugins({ })) } - if (options.docsearch) { - if (options.docsearch.appId && options.docsearch.apiKey) - plugins.push(docsearchPlugin(resolveDocsearchOptions(app, options.docsearch))) + if (pluginOptions.docsearch) { + if (pluginOptions.docsearch.appId && pluginOptions.docsearch.apiKey) + plugins.push(docsearchPlugin(resolveDocsearchOptions(app, pluginOptions.docsearch))) else console.error('docsearch plugin: appId and apiKey are both required') } - else if (options.search !== false) { - plugins.push(searchPlugin(resolveSearchOptions(app, options.search))) + else if (pluginOptions.search !== false) { + plugins.push(searchPlugin(resolveSearchOptions(app, pluginOptions.search))) } - const shikiOption = options.shiki + const shikiOption = pluginOptions.shiki let shikiTheme: any = { light: 'vitesse-light', dark: 'vitesse-dark' } if (shikiOption !== false) { shikiTheme = shikiOption?.theme ?? shikiTheme @@ -166,7 +129,7 @@ export function setupPlugins({ })) } - if (options.markdownEnhance !== false) { + if (pluginOptions.markdownEnhance !== false) { plugins.push(mdEnhancePlugin( Object.assign( { @@ -183,42 +146,42 @@ export function setupPlugins({ footnote: true, katex: true, } as MarkdownEnhancePluginOptions, - options.markdownEnhance || {}, + pluginOptions.markdownEnhance || {}, ), )) } - if (options.markdownPower !== false) { + if (pluginOptions.markdownPower !== false) { plugins.push(markdownPowerPlugin({ - caniuse: options.caniuse, - ...options.markdownPower || {}, - repl: options.markdownPower?.repl - ? { theme: shikiTheme, ...options.markdownPower?.repl } - : options.markdownPower?.repl, + caniuse: pluginOptions.caniuse, + ...pluginOptions.markdownPower || {}, + repl: pluginOptions.markdownPower?.repl + ? { theme: shikiTheme, ...pluginOptions.markdownPower?.repl } + : pluginOptions.markdownPower?.repl, })) } - if (options.watermark) { + if (pluginOptions.watermark) { plugins.push(watermarkPlugin({ delay: 300, enabled: true, - ...typeof options.watermark === 'object' ? options.watermark : {}, + ...typeof pluginOptions.watermark === 'object' ? pluginOptions.watermark : {}, })) } - if (options.comment) - plugins.push(commentPlugin(options.comment)) + if (pluginOptions.comment) + plugins.push(commentPlugin(pluginOptions.comment)) - if (options.baiduTongji !== false && options.baiduTongji?.key && isProd) - plugins.push(baiduTongjiPlugin(options.baiduTongji)) + if (pluginOptions.baiduTongji !== false && pluginOptions.baiduTongji?.key && isProd) + plugins.push(baiduTongjiPlugin(pluginOptions.baiduTongji)) - if (options.sitemap !== false && hostname && isProd) + if (pluginOptions.sitemap !== false && hostname && isProd) plugins.push(sitemapPlugin({ hostname })) - if (options.seo !== false && hostname && isProd) { + if (pluginOptions.seo !== false && hostname && isProd) { plugins.push(seoPlugin({ hostname, - author: resolveLocaleOptions(localeOptions, 'avatar')?.name, + author: localeOptions.locales?.['/'].avatar?.name || localeOptions.avatar?.name, })) } diff --git a/theme/src/node/plugins/index.ts b/theme/src/node/plugins/index.ts new file mode 100644 index 00000000..62d0b452 --- /dev/null +++ b/theme/src/node/plugins/index.ts @@ -0,0 +1 @@ +export * from './getPlugins.js' diff --git a/theme/src/node/plugins/resolveAutoFrontmatterOptions.ts b/theme/src/node/plugins/resolveAutoFrontmatterOptions.ts new file mode 100644 index 00000000..caf98f2c --- /dev/null +++ b/theme/src/node/plugins/resolveAutoFrontmatterOptions.ts @@ -0,0 +1,240 @@ +import { path } from 'vuepress/utils' +import { removeLeadingSlash, resolveLocalePath } from 'vuepress/shared' +import { ensureLeadingSlash } from '@vuepress/helper' +import type { + AutoFrontmatterOptions, + FrontmatterArray, + FrontmatterObject, +} from '@vuepress-plume/plugin-auto-frontmatter' +import { format } from 'date-fns' +import { uniq } from '@pengzhanbo/utils' +import type { NotesSidebar } from '@vuepress-plume/plugin-notes-data' +import type { + PlumeThemeLocaleOptions, + PlumeThemePluginOptions, +} from '../../shared/index.js' +import { + getCurrentDirname, + getPackage, + nanoid, + normalizePath, + pathJoin, + withBase, +} from '../utils.js' +import { resolveNotesOptions } from '../config/index.js' + +export function resolveAutoFrontmatterOptions( + pluginOptions: PlumeThemePluginOptions, + localeOptions: PlumeThemeLocaleOptions, +): AutoFrontmatterOptions { + const pkg = getPackage() + const { locales = {}, article: articlePrefix = '/article/' } = localeOptions + const { frontmatter } = pluginOptions + + const resolveLocale = (relativeFilepath: string) => { + const file = ensureLeadingSlash(relativeFilepath) + + return resolveLocalePath(localeOptions.locales!, file) + } + + const resolveOptions = (relativeFilepath: string) => { + const locale = resolveLocale(relativeFilepath) + return locales[locale] || localeOptions + } + + const notesList = resolveNotesOptions(localeOptions) + const localesNotesDirs = notesList + .flatMap(({ notes, dir }) => { + dir = removeLeadingSlash(dir || '') + return notes.map(note => normalizePath(`${dir}/${note.dir || ''}/`)) + }) + .filter(Boolean) + + const baseFrontmatter: FrontmatterObject = { + author(author: string, { relativePath }, data: any) { + if (author) + return author + if (data.friends) + return + const avatar = resolveOptions(relativePath).avatar + + return avatar?.name || pkg.author || '' + }, + createTime(formatTime: string, { createTime }, data: any) { + if (formatTime) + return formatTime + if (data.friends) + return + return format(new Date(createTime), 'yyyy/MM/dd HH:mm:ss') + }, + } + + const notesByLocale = (locale: string) => { + const notes = localeOptions.locales?.[locale]?.notes + if (notes === false) + return undefined + return notes + } + + const findNote = (relativeFilepath: string) => { + const locale = resolveLocale(relativeFilepath) + const filepath = ensureLeadingSlash(relativeFilepath) + const notes = notesByLocale(locale) + if (!notes) + return undefined + const notesList = notes?.notes || [] + const notesDir = notes?.dir || '' + return notesList.find(note => + filepath.startsWith(normalizePath(`${notesDir}/${note.dir}`)), + ) + } + + return { + include: frontmatter?.include ?? ['**/*.md'], + exclude: uniq(['.vuepress/**/*', 'node_modules', ...(frontmatter?.exclude ?? [])]), + + frontmatter: [ + localesNotesDirs.length + ? { + // note 首页链接 + include: localesNotesDirs.map(dir => pathJoin(dir, '/{readme,README,index}.md')), + frontmatter: { + title(title: string, { relativePath }) { + if (title) + return title + const note = findNote(relativePath) + if (note?.text) + return note.text + return getCurrentDirname(note?.dir, relativePath) || '' + }, + ...baseFrontmatter, + permalink(permalink: string, { relativePath }, data: any) { + if (permalink) + return permalink + if (data.friends) + return + const locale = resolveLocale(relativePath) + + const notes = notesByLocale(locale) + const note = findNote(relativePath) + return pathJoin( + locale, + notes?.link || '', + note?.link || getCurrentDirname(note?.dir, relativePath), + '/', + ) + }, + }, + } + : '', + localesNotesDirs.length + ? { + include: localesNotesDirs.map(dir => `${dir}**/**.md`), + frontmatter: { + title(title: string, { relativePath }) { + if (title) + return title + + const note = findNote(relativePath) + let basename = path.basename(relativePath, '.md') + if (note?.sidebar === 'auto') + basename = basename.replace(/^\d+\./, '') + + return basename + }, + ...baseFrontmatter, + permalink(permalink: string, { relativePath }, data: any) { + if (permalink) + return permalink + if (data.friends) + return + const locale = resolveLocale(relativePath) + const notes = notesByLocale(locale) + const note = findNote(relativePath) + const prefix = notes?.link || '' + const args: string[] = [ + locale, + prefix, + note?.link || '', + ] + const sidebar = note?.sidebar + + if (note && sidebar && sidebar !== 'auto') { + const res = resolveLinkBySidebar(sidebar, pathJoin(prefix, note.dir || '')) + const file = ensureLeadingSlash(relativePath) + res[file] && args.push(res[file]) + } + + return pathJoin(...args, nanoid(), '/') + }, + }, + } + : '', + { + include: '**/{readme,README,index}.md', + frontmatter: {}, + }, + { + include: '*', + frontmatter: { + title(title: string, { relativePath }) { + if (title) + return title + const basename = path.basename(relativePath || '', '.md') + return basename + }, + ...baseFrontmatter, + permalink(permalink: string, { relativePath }) { + if (permalink) + return permalink + const locale = resolveLocale(relativePath) + const prefix = withBase(articlePrefix, locale) + + return normalizePath(`${prefix}/${nanoid()}/`) + }, + }, + }, + ].filter(Boolean) as FrontmatterArray, + } +} + +function resolveLinkBySidebar( + sidebar: NotesSidebar, + prefix: string, +) { + const res: Record = {} + + for (const item of sidebar) { + if (typeof item !== 'string') { + const { dir = '', link = '/', items, text = '' } = item + SidebarLink(items, link, text, pathJoin(prefix, dir), res) + } + } + return res +} + +function SidebarLink(items: NotesSidebar | undefined, link: string, text: string, dir = '', res: Record = {}) { + if (!items) { + res[pathJoin(dir, `${text}.md`)] = link + return + } + + for (const item of items) { + if (typeof item === 'string') { + if (!link) + continue + if (item) { + res[pathJoin(dir, `${item}.md`)] = link + } + else { + res[pathJoin(dir, 'README.md')] = link + res[pathJoin(dir, 'index.md')] = link + res[pathJoin(dir, 'readme.md')] = link + } + } + else { + const { dir: subDir = '', link: subLink = '/', items: subItems, text: subText = '' } = item + SidebarLink(subItems, pathJoin(link, subLink), subText, pathJoin(dir, subDir), res) + } + } +} diff --git a/theme/src/node/plugins/resolveBlogDataOptions.ts b/theme/src/node/plugins/resolveBlogDataOptions.ts new file mode 100644 index 00000000..6dfbeb78 --- /dev/null +++ b/theme/src/node/plugins/resolveBlogDataOptions.ts @@ -0,0 +1,56 @@ +import type { BlogDataPluginOptions } from '@vuepress-plume/plugin-blog-data' +import { removeLeadingSlash } from '@vuepress/helper' +import { + isEncryptPage, + resolveNotesOptions, +} from '../config/index.js' +import { normalizePath } from '../utils.js' +import type { PlumeThemeEncrypt, PlumeThemeLocaleOptions } from '../..//shared/index.js' +import { + BLOG_TAGS_COLORS_PRESET, + generateBlogTagsColors, +} from './blogTags.js' + +export function resolveBlogDataOptions( + localeOptions: PlumeThemeLocaleOptions, + encrypt?: PlumeThemeEncrypt, +): BlogDataPluginOptions { + const blog = localeOptions.blog || {} + const notesList = resolveNotesOptions(localeOptions) + const notesDirList = notesList + .map(notes => removeLeadingSlash(normalizePath(`${notes.dir}/**`))) + .filter(Boolean) + + return { + include: blog?.include ?? ['**/*.md'], + exclude: [ + '**/{README,readme,index}.md', + '.vuepress/', + 'node_modules/', + ...(blog?.exclude ?? []), + ...notesDirList, + ].filter(Boolean), + sortBy: 'createTime', + excerpt: true, + pageFilter: (page: any) => page.frontmatter.article !== undefined + ? !!page.frontmatter.article + : true, + extraBlogData(extra) { + extra.tagsColorsPreset = BLOG_TAGS_COLORS_PRESET + extra.tagsColors = {} + }, + extendBlogData: (page: any, extra) => { + const tags = page.frontmatter.tags + generateBlogTagsColors(extra.tagsColors, tags) + const data: Record = { + categoryList: page.data.categoryList, + tags, + sticky: page.frontmatter.sticky, + createTime: page.data.frontmatter.createTime, + lang: page.lang, + } + isEncryptPage(page, encrypt) && (data.encrypt = true) + return data + }, + } +} diff --git a/theme/src/node/resolveLocaleOptions.ts b/theme/src/node/resolveLocaleOptions.ts deleted file mode 100644 index a9da228a..00000000 --- a/theme/src/node/resolveLocaleOptions.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { isEmptyObject } from '@pengzhanbo/utils' -import type { App } from 'vuepress/core' -import type { PlumeThemeLocaleOptions } from '../shared/index.js' -import { normalizePath } from './utils.js' - -export function resolveLocaleOptions< - T extends PlumeThemeLocaleOptions = PlumeThemeLocaleOptions, - K extends Exclude = Exclude, ->(options: T, key: K, locale = '', fallback = true): T[K] | undefined { - const locales = options.locales - - if (!locales) - return options[key] - - locale = !locale || locale === '/' ? '/' : normalizePath(`/${locale}/`) - - const localeOptions = locales[locale] - const fallbackLocaleOptions = locales['/'] - if (!localeOptions) - return fallback ? options[key] : undefined - - const _key = key as keyof typeof localeOptions - const fallbackData = (fallbackLocaleOptions[_key] ?? options[key]) as T[K] - - const value = localeOptions[_key] as T[K] - - return value ?? (fallback ? fallbackData : undefined) -} - -export function resolvedAppLocales(app: App): NonNullable { - if (app.siteData.locales && !isEmptyObject(app.siteData.locales)) - return app.siteData.locales - - const defaultLang = app.siteData.lang || 'en-US' - return { '/': { lang: defaultLang } } -} diff --git a/theme/src/node/resolveNotesList.ts b/theme/src/node/resolveNotesList.ts deleted file mode 100644 index e1be3d15..00000000 --- a/theme/src/node/resolveNotesList.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { NotesDataOptions, NotesSidebar } from '@vuepress-plume/plugin-notes-data' -import type { PlumeThemeLocaleOptions } from '../shared/index.js' -import { resolveLocaleOptions } from './resolveLocaleOptions.js' -import { normalizePath, pathJoin } from './utils.js' - -export function resolveNotesList(options: PlumeThemeLocaleOptions) { - const locales = options.locales || {} - const notesList: NotesDataOptions[] = [] - - for (const locale of Object.keys(locales)) { - const notes = resolveLocaleOptions(options, 'notes', locale, false) - if (notes) { - const dir = normalizePath(`/${notes.dir}`) - if (!dir.startsWith(locale)) - notes.dir = pathJoin(locale, notes.dir).replace(/^\//, '') - - notesList.push(notes) - } - } - - return notesList -} - -export function resolveLinkBySidebar( - sidebar: NotesSidebar, - prefix: string, -) { - const res: Record = {} - - for (const item of sidebar) { - if (typeof item !== 'string') { - const { dir = '', link = '/', items, text = '' } = item - SidebarLink(items, link, text, pathJoin(prefix, dir), res) - } - } - return res -} - -function SidebarLink(items: NotesSidebar | undefined, link: string, text: string, dir = '', res: Record = {}) { - if (!items) { - res[pathJoin(dir, `${text}.md`)] = link - return - } - - for (const item of items) { - if (typeof item === 'string') { - if (!link) - continue - if (item) { - res[pathJoin(dir, `${item}.md`)] = link - } - else { - res[pathJoin(dir, 'README.md')] = link - res[pathJoin(dir, 'index.md')] = link - res[pathJoin(dir, 'readme.md')] = link - } - } - else { - const { dir: subDir = '', link: subLink = '/', items: subItems, text: subText = '' } = item - SidebarLink(subItems, pathJoin(link, subLink), subText, pathJoin(dir, subDir), res) - } - } -} diff --git a/theme/src/node/setupPages.ts b/theme/src/node/setupPages.ts index 4382e66b..50fd09bb 100644 --- a/theme/src/node/setupPages.ts +++ b/theme/src/node/setupPages.ts @@ -1,4 +1,8 @@ -import { path } from 'vuepress/utils' +import { + ensureLeadingSlash, + getRootLang, + getRootLangPath, +} from '@vuepress/helper' import type { App, Page } from 'vuepress/core' import { createPage } from 'vuepress/core' import type { @@ -6,53 +10,62 @@ import type { PlumeThemeLocaleOptions, PlumeThemePageData, } from '../shared/index.js' -import { pathJoin } from './utils.js' -import { resolveLocaleOptions, resolvedAppLocales } from './resolveLocaleOptions.js' +import { withBase } from './utils.js' +import { PRESET_LOCALES } from './locales/index.js' +import { resolveNotesLinkList } from './config/index.js' export async function setupPage( app: App, localeOption: PlumeThemeLocaleOptions, ) { - const locales = resolvedAppLocales(app) - const defaultBlog = resolveLocaleOptions(localeOption, 'blog') - for (const [, locale] of Object.keys(locales).entries()) { - const blog = resolveLocaleOptions(localeOption, 'blog', locale, false) - const lang = locales[locale].lang || app.siteData.lang - const link = blog?.link - ? blog.link - : pathJoin('/', locale, defaultBlog?.link || '/blog/') - const blogPage = await createPage(app, { - path: link, - frontmatter: { lang, type: 'blog' }, - }) - app.pages.push(blogPage) + const pageList: Promise[] = [] + const locales = localeOption.locales || {} + const rootPath = getRootLangPath(app) + const rootLang = getRootLang(app) - if (blog?.tags !== false || defaultBlog?.tags !== false) { - const tagsPage = await createPage(app, { - path: pathJoin(link, 'tags/'), - frontmatter: { lang, type: 'blog-tags' }, - }) - app.pages.push(tagsPage) - } + const blog = localeOption.blog || {} + const link = blog.link || '/blog/' - if (blog?.archives !== false || defaultBlog?.archives !== false) { - const archivesPage = await createPage(app, { - path: pathJoin(link, 'archives/'), - frontmatter: { lang, type: 'blog-archives' }, - }) - app.pages.push(archivesPage) - } + const getTitle = (locale: string, key: string) => { + const opt = PRESET_LOCALES[locale] || PRESET_LOCALES[rootPath] || {} + return opt[key] || '' } + + for (const localePath of Object.keys(locales)) { + const lang = app.siteData.locales?.[localePath]?.lang || rootLang + const locale = localePath === '/' ? rootPath : localePath + + // 添加 博客页面 + pageList.push(createPage(app, { + path: withBase(link, localePath), + frontmatter: { lang, type: 'blog', title: getTitle(locale, 'blog') }, + })) + + // 添加 标签页 + blog.tags !== false && pageList.push(createPage(app, { + path: withBase(blog.tagsLink || `${link}/tags/`, localePath), + frontmatter: { lang, type: 'blog-tags', title: getTitle(locale, 'tag') }, + })) + + // 添加归档页 + blog.archives !== false && pageList.push(createPage(app, { + path: withBase(blog.archivesLink || `${link}/archives/`, localePath), + frontmatter: { lang, type: 'blog-archives', title: getTitle(locale, 'archive') }, + })) + } + app.pages.push(...await Promise.all(pageList)) } export function extendsPageData( - app: App, page: Page, localeOptions: PlumeThemeLocaleOptions, ) { page.data.filePathRelative = page.filePathRelative page.routeMeta.title = page.title + if (page.frontmatter.icon) + page.routeMeta.icon = page.frontmatter.icon + if (page.frontmatter.friends) { page.frontmatter.article = false page.frontmatter.type = 'friends' @@ -66,7 +79,7 @@ export function extendsPageData( page.data.type = page.frontmatter.type as any } - autoCategory(app, page, localeOptions) + autoCategory(page, localeOptions) pageContentRendered(page) } @@ -75,7 +88,6 @@ const cache: Record = {} const RE_CATEGORY = /^(\d+)?(?:\.?)([^]+)$/ export function autoCategory( - app: App, page: Page, options: PlumeThemeLocaleOptions, ) { @@ -83,24 +95,15 @@ export function autoCategory( if (page.frontmatter.type || !pagePath) return - const locales = Object.keys(resolvedAppLocales(app)) - const notesLinks: string[] = [] - for (const [, locale] of locales.entries()) { - const config = options.locales?.[locale]?.notes - if (config && config.notes) { - notesLinks.push( - ...config.notes.map( - note => path.join(locale, config.link || '', note.link).replace(/\\+/g, '/'), - ), - ) - } - } + const notesLinks = resolveNotesLinkList(options) + if (notesLinks.some(link => page.path.startsWith(link))) return + const RE_LOCALE = new RegExp( - `^(${locales.filter(l => l !== '/').join('|')})`, + `^(${Object.keys(options.locales || {}).filter(l => l !== '/').join('|')})`, ) - const categoryList: PageCategoryData[] = `/${pagePath}` + const categoryList: PageCategoryData[] = ensureLeadingSlash(pagePath) .replace(RE_LOCALE, '') .replace(/^\//, '') .split('/') diff --git a/theme/src/node/theme.ts b/theme/src/node/theme.ts index a4e42714..96c35364 100644 --- a/theme/src/node/theme.ts +++ b/theme/src/node/theme.ts @@ -1,14 +1,12 @@ import type { Page, Theme } from 'vuepress/core' -import { logger, templateRenderer } from 'vuepress/utils' +import { templateRenderer } from 'vuepress/utils' import { isPlainObject } from '@vuepress/helper' import type { PlumeThemeOptions, PlumeThemePageData } from '../shared/index.js' -import { setupPlugins } from './plugins.js' +import { getPlugins } from './plugins/index.js' import { extendsPageData, setupPage } from './setupPages.js' -import { getThemePackage, resolve, templates } from './utils.js' +import { THEME_NAME, getThemePackage, logger, resolve, templates } from './utils.js' import { resolveEncrypt, resolveLocaleOptions, resolvePageHead } from './config/index.js' -const THEME_NAME = 'vuepress-theme-plume' - export function plumeTheme({ themePlugins, plugins, @@ -16,10 +14,11 @@ export function plumeTheme({ hostname, ...localeOptions }: PlumeThemeOptions = {}): Theme { - const pluginsOptions = plugins ?? themePlugins ?? {} + const pluginOptions = plugins ?? themePlugins ?? {} const pkg = getThemePackage() - const watermarkFullPage = isPlainObject(pluginsOptions.watermark) - ? pluginsOptions.watermark.fullPage !== false + + const watermarkFullPage = isPlainObject(pluginOptions.watermark) + ? pluginOptions.watermark.fullPage !== false : true if (themePlugins) { @@ -42,12 +41,12 @@ export function plumeTheme({ clientConfigFile: resolve('client/config.js'), - plugins: setupPlugins({ app, options: pluginsOptions, localeOptions, encrypt, hostname }), + plugins: getPlugins({ app, pluginOptions, localeOptions, encrypt, hostname }), - onInitialized: app => setupPage(app, localeOptions), + onInitialized: async app => await setupPage(app, localeOptions), extendsPage: (page) => { - extendsPageData(app, page as Page, localeOptions) + extendsPageData(page as Page, localeOptions) resolvePageHead(page, localeOptions) }, diff --git a/theme/src/node/utils.ts b/theme/src/node/utils.ts index 24fd7ea5..cbd04915 100644 --- a/theme/src/node/utils.ts +++ b/theme/src/node/utils.ts @@ -1,6 +1,9 @@ import process from 'node:process' import { customAlphabet } from 'nanoid' import { fs, getDirname, path } from 'vuepress/utils' +import { Logger, ensureEndingSlash, ensureLeadingSlash } from '@vuepress/helper' + +export const THEME_NAME = 'vuepress-theme-plume' const __dirname = getDirname(import.meta.url) @@ -9,6 +12,8 @@ export const templates = (url: string) => resolve('../templates', url) export const nanoid = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 8) +export const logger = new Logger(THEME_NAME) + export function getPackage() { let pkg = {} as any try { @@ -30,8 +35,8 @@ export function getThemePackage() { } const RE_SLASH = /(\\|\/)+/g -export function normalizePath(dir: string) { - return dir.replace(RE_SLASH, '/') +export function normalizePath(path: string) { + return path.replace(RE_SLASH, '/') } export function pathJoin(...args: string[]) { @@ -45,3 +50,10 @@ export function getCurrentDirname(basePath: string | undefined, filepath: string .split('/') return dirList.length > 0 ? dirList[dirList.length - 1] : '' } + +export function withBase(path = '', base = '/'): string { + path = ensureEndingSlash(ensureLeadingSlash(path)) + if (path.startsWith(base)) + return normalizePath(path) + return normalizePath(`${base}${path}`) +} diff --git a/theme/src/shared/blog.ts b/theme/src/shared/blog.ts index 88b15b6c..8bd617e9 100644 --- a/theme/src/shared/blog.ts +++ b/theme/src/shared/blog.ts @@ -63,9 +63,23 @@ export interface PlumeThemeBlog { * @default true */ tags?: boolean + + /** + * 自定义标签页链接 + * + * @default '/blog/tags/' + */ + tagsLink?: string /** * 是否启用归档页 * @default true */ archives?: boolean + + /** + * 自定义归档页链接 + * + * @default '/blog/archives/' + */ + archivesLink?: string }