chore: 调整优化结构
This commit is contained in:
parent
e358224217
commit
48c6113f77
6
docs/notes/plugins/netlify-functions/测试文件.md
Normal file
6
docs/notes/plugins/netlify-functions/测试文件.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
title: 测试文件
|
||||
author: Plume Theme
|
||||
createTime: 2024/05/26 02:11:27
|
||||
permalink: /plugins/9kekfblc/
|
||||
---
|
||||
@ -1,7 +1,7 @@
|
||||
export interface NotesDataOptions {
|
||||
/**
|
||||
* 保存所有笔记的目录
|
||||
* @default '/notes'
|
||||
* @default '/notes/'
|
||||
*/
|
||||
dir: string
|
||||
/**
|
||||
|
||||
@ -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<boolean>
|
||||
type DarkModeRef = Ref<boolean>
|
||||
|
||||
export const darkModeSymbol: InjectionKey<DarkModeRef> = 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<boolean>(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
|
||||
}
|
||||
|
||||
39
theme/src/client/composables/data.ts
Normal file
39
theme/src/client/composables/data.ts
Normal file
@ -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<PlumeThemeLocaleData>
|
||||
page: PageDataRef<PlumeThemePageData>
|
||||
frontmatter: PageFrontmatterRef<PlumeThemePageFrontmatter>
|
||||
hash: Ref<string>
|
||||
site: SiteLocaleDataRef
|
||||
isDark: Ref<boolean>
|
||||
}
|
||||
|
||||
export function useData(): Data {
|
||||
const theme = useThemeLocaleData()
|
||||
const page = usePageData<PlumeThemePageData>()
|
||||
const frontmatter = usePageFrontmatter<PlumeThemePageFrontmatter>()
|
||||
const site = useSiteLocaleData()
|
||||
const isDark = useDarkMode()
|
||||
|
||||
return { theme, page, frontmatter, hash: hashRef, site, isDark }
|
||||
}
|
||||
@ -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<PlumeThemePageData>()
|
||||
const { page } = useData()
|
||||
const route = useRoute()
|
||||
|
||||
const hasPageEncrypt = computed(() => ruleList.length ? matches.some(toMatch) : false)
|
||||
|
||||
@ -9,3 +9,4 @@ export * from './blog.js'
|
||||
export * from './locale.js'
|
||||
export * from './useRouteQuery.js'
|
||||
export * from './watermark.js'
|
||||
export * from './data.js'
|
||||
|
||||
@ -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<PlumeThemePageData>()
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
@ -3,3 +3,4 @@ export * from './resolveThemeData.js'
|
||||
export * from './resolveSearchOptions.js'
|
||||
export * from './resolvePageHead.js'
|
||||
export * from './resolveEncrypt.js'
|
||||
export * from './resolveNotesOptions.js'
|
||||
|
||||
@ -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<PlumeThemeLocaleOptions>({
|
||||
|
||||
37
theme/src/node/config/resolveNotesOptions.ts
Normal file
37
theme/src/node/config/resolveNotesOptions.ts
Normal file
@ -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
|
||||
}
|
||||
@ -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}`)
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ export const zhLocale: PlumeThemeLocaleData = {
|
||||
|
||||
footer: {
|
||||
message:
|
||||
'<a target="_blank" href="https://v2.vuepress.vuejs.org/">VuePress</a> & <a target="_blank" href="https://theme-plume.vuejs.press">vuepress-theme-plume</a> 提供支持',
|
||||
'Power by <a target="_blank" href="https://v2.vuepress.vuejs.org/">VuePress</a> & <a target="_blank" href="https://theme-plume.vuejs.press">vuepress-theme-plume</a>',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
* :::
|
||||
@ -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<string, any> = {
|
||||
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,
|
||||
}))
|
||||
}
|
||||
|
||||
1
theme/src/node/plugins/index.ts
Normal file
1
theme/src/node/plugins/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './getPlugins.js'
|
||||
240
theme/src/node/plugins/resolveAutoFrontmatterOptions.ts
Normal file
240
theme/src/node/plugins/resolveAutoFrontmatterOptions.ts
Normal file
@ -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<string, string> = {}
|
||||
|
||||
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<string, string> = {}) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
56
theme/src/node/plugins/resolveBlogDataOptions.ts
Normal file
56
theme/src/node/plugins/resolveBlogDataOptions.ts
Normal file
@ -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<string, any> = {
|
||||
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
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -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<keyof T, 'locales'> = Exclude<keyof T, 'locales'>,
|
||||
>(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<App['siteData']['locales']> {
|
||||
if (app.siteData.locales && !isEmptyObject(app.siteData.locales))
|
||||
return app.siteData.locales
|
||||
|
||||
const defaultLang = app.siteData.lang || 'en-US'
|
||||
return { '/': { lang: defaultLang } }
|
||||
}
|
||||
@ -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<string, string> = {}
|
||||
|
||||
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<string, string> = {}) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Page>[] = []
|
||||
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<PlumeThemePageData>,
|
||||
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<string, number> = {}
|
||||
const RE_CATEGORY = /^(\d+)?(?:\.?)([^]+)$/
|
||||
|
||||
export function autoCategory(
|
||||
app: App,
|
||||
page: Page<PlumeThemePageData>,
|
||||
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('/')
|
||||
|
||||
@ -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<PlumeThemePageData>, localeOptions)
|
||||
extendsPageData(page as Page<PlumeThemePageData>, localeOptions)
|
||||
resolvePageHead(page, localeOptions)
|
||||
},
|
||||
|
||||
|
||||
@ -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}`)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user