diff --git a/docs/.vuepress/config.ts b/docs/.vuepress/config.ts
index fc75ef62..1286668a 100644
--- a/docs/.vuepress/config.ts
+++ b/docs/.vuepress/config.ts
@@ -19,31 +19,32 @@ export default defineUserConfig({
theme: themePlume({
logo: 'https://pengzhanbo.cn/g.gif',
hostname: 'https://pengzhanbo.cn',
+ appearance: true,
avatar: {
url: '/images/blogger.jpg',
name: 'Plume Theme',
description: 'The Theme for Vuepress 2.0',
},
- social: {
- email: 'volodymyr@foxmail.com',
- github: 'pengzhanbo',
- QQ: '942450674',
- weiBo: 'https://weibo.com',
- zhiHu: 'https://zhihu.com',
- facebook: 'https://baidu.com',
- twitter: 'https://baidu.com',
- linkedin: 'https://baidu.com',
- },
+ social: [{ icon: 'github', link: 'https://github.com/pengzhanbo' }],
+ // {
+ // email: 'volodymyr@foxmail.com',
+ // github: 'pengzhanbo',
+ // QQ: '942450674',
+ // weiBo: 'https://weibo.com',
+ // zhiHu: 'https://zhihu.com',
+ // facebook: 'https://baidu.com',
+ // twitter: 'https://baidu.com',
+ // linkedin: 'https://baidu.com',
+ // },
notes,
- darkMode: true,
navbar: [
{
text: 'VuePress',
- children: [
+ items: [
{ text: 'theme-plume', link: '/note/vuepress-theme-plume/' },
{
text: 'Plugin',
- children: [
+ items: [
{ text: 'caniuse', link: '/note/vuepress-plugin/caniuse/' },
{
text: 'netlify-functions',
diff --git a/packages/plugin-notes-data/src/node/prepareNotesData.ts b/packages/plugin-notes-data/src/node/prepareNotesData.ts
index dd3e5b71..6795c729 100644
--- a/packages/plugin-notes-data/src/node/prepareNotesData.ts
+++ b/packages/plugin-notes-data/src/node/prepareNotesData.ts
@@ -101,7 +101,7 @@ function initSidebar(note: NotesItem, pages: NotePage[]): NotesSidebarItem[] {
}
function initSidebarByConfig(
- { text, link, dir, sidebar }: NotesItem,
+ { text, dir, sidebar }: NotesItem,
pages: NotePage[]
): NotesSidebarItem[] {
return (sidebar as NotesSidebar).map((item) => {
@@ -114,11 +114,11 @@ function initSidebarByConfig(
items: [],
}
} else {
- // link = path.join(link || '', item.link || '')
const current = findNotePage(item.link || '', dir, pages)
return {
text: item.text || item.dir || current?.title,
link: current?.link,
+ collapsed: item.collapsed,
items: initSidebarByConfig(
{
link: item.link || '',
diff --git a/packages/plugin-notes-data/src/shared/index.ts b/packages/plugin-notes-data/src/shared/index.ts
index 67d5d9d0..ea4a16fc 100644
--- a/packages/plugin-notes-data/src/shared/index.ts
+++ b/packages/plugin-notes-data/src/shared/index.ts
@@ -19,6 +19,7 @@ export type NotesSidebarItem = {
text?: string
link?: string
dir?: string
+ collapsed?: boolean
items?: NotesSidebar
}
diff --git a/packages/theme/src/client/components/AutoLink.vue b/packages/theme/src/client/components/AutoLink.vue
new file mode 100644
index 00000000..ee8c11bc
--- /dev/null
+++ b/packages/theme/src/client/components/AutoLink.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Flyout/MenuGroup.vue b/packages/theme/src/client/components/Flyout/MenuGroup.vue
new file mode 100644
index 00000000..dc12f21e
--- /dev/null
+++ b/packages/theme/src/client/components/Flyout/MenuGroup.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Flyout/MenuLink.vue b/packages/theme/src/client/components/Flyout/MenuLink.vue
new file mode 100644
index 00000000..aa43cf43
--- /dev/null
+++ b/packages/theme/src/client/components/Flyout/MenuLink.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Flyout/VMenu.vue b/packages/theme/src/client/components/Flyout/VMenu.vue
new file mode 100644
index 00000000..91c3f491
--- /dev/null
+++ b/packages/theme/src/client/components/Flyout/VMenu.vue
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Flyout/index.vue b/packages/theme/src/client/components/Flyout/index.vue
new file mode 100644
index 00000000..f4456911
--- /dev/null
+++ b/packages/theme/src/client/components/Flyout/index.vue
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Nav/NavBar.vue b/packages/theme/src/client/components/Nav/NavBar.vue
index 9b6aed79..4b26fb1c 100644
--- a/packages/theme/src/client/components/Nav/NavBar.vue
+++ b/packages/theme/src/client/components/Nav/NavBar.vue
@@ -2,6 +2,11 @@
import { useWindowScroll } from '@vueuse/core'
import { computed } from 'vue'
import { useSidebar } from '../../composables/sidebar.js'
+import NavBarAppearance from './NavBarAppearance.vue'
+import NavBarExtra from './NavBarExtra.vue'
+import NavBarHamburger from './NavBarHamburger.vue'
+import NavBarMenu from './NavBarMenu.vue'
+import NavBarSocialLinks from './NavBarSocialLinks.vue'
import NavBarTitle from './NavBarTitle.vue'
defineProps<{
@@ -31,6 +36,15 @@ const classes = computed(() => ({
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Nav/NavBarAppearance.vue b/packages/theme/src/client/components/Nav/NavBarAppearance.vue
new file mode 100644
index 00000000..b04ed1fd
--- /dev/null
+++ b/packages/theme/src/client/components/Nav/NavBarAppearance.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Nav/NavBarExtra.vue b/packages/theme/src/client/components/Nav/NavBarExtra.vue
new file mode 100644
index 00000000..fc8f22a7
--- /dev/null
+++ b/packages/theme/src/client/components/Nav/NavBarExtra.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Nav/NavBarHamburger.vue b/packages/theme/src/client/components/Nav/NavBarHamburger.vue
new file mode 100644
index 00000000..f4129e2a
--- /dev/null
+++ b/packages/theme/src/client/components/Nav/NavBarHamburger.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Nav/NavBarMenu.vue b/packages/theme/src/client/components/Nav/NavBarMenu.vue
new file mode 100644
index 00000000..10c45b53
--- /dev/null
+++ b/packages/theme/src/client/components/Nav/NavBarMenu.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Nav/NavBarMenuGroup.vue b/packages/theme/src/client/components/Nav/NavBarMenuGroup.vue
new file mode 100644
index 00000000..deb023ee
--- /dev/null
+++ b/packages/theme/src/client/components/Nav/NavBarMenuGroup.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Nav/NavBarMenuLink.vue b/packages/theme/src/client/components/Nav/NavBarMenuLink.vue
new file mode 100644
index 00000000..0b5bd0a5
--- /dev/null
+++ b/packages/theme/src/client/components/Nav/NavBarMenuLink.vue
@@ -0,0 +1,50 @@
+
+
+
+
+ {{ item.text }}
+
+
+
+
diff --git a/packages/theme/src/client/components/Nav/NavBarSocialLinks.vue b/packages/theme/src/client/components/Nav/NavBarSocialLinks.vue
new file mode 100644
index 00000000..9f1f494c
--- /dev/null
+++ b/packages/theme/src/client/components/Nav/NavBarSocialLinks.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/SocialLink.vue b/packages/theme/src/client/components/SocialLink.vue
new file mode 100644
index 00000000..687ac3fe
--- /dev/null
+++ b/packages/theme/src/client/components/SocialLink.vue
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/SocialLinks.vue b/packages/theme/src/client/components/SocialLinks.vue
new file mode 100644
index 00000000..dcbee7a0
--- /dev/null
+++ b/packages/theme/src/client/components/SocialLinks.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/components/Switch.vue b/packages/theme/src/client/components/Switch.vue
new file mode 100644
index 00000000..174313fc
--- /dev/null
+++ b/packages/theme/src/client/components/Switch.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
diff --git a/packages/theme/src/client/components/SwitchAppearance.vue b/packages/theme/src/client/components/SwitchAppearance.vue
new file mode 100644
index 00000000..d496fb24
--- /dev/null
+++ b/packages/theme/src/client/components/SwitchAppearance.vue
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/theme/src/client/composables/darkMode.ts b/packages/theme/src/client/composables/darkMode.ts
index 2e76b835..4a190911 100644
--- a/packages/theme/src/client/composables/darkMode.ts
+++ b/packages/theme/src/client/composables/darkMode.ts
@@ -1,7 +1,5 @@
-import { usePreferredDark, useStorage } from '@vueuse/core'
-import { computed, inject, onMounted, onUnmounted, provide, watch } from 'vue'
+import { inject, provide, ref } from 'vue'
import type { InjectionKey, WritableComputedRef } from 'vue'
-import { useThemeLocaleData } from './themeData.js'
export type DarkModeRef = WritableComputedRef
@@ -13,60 +11,17 @@ export const darkModeSymbol: InjectionKey = Symbol(
* Inject dark mode global computed
*/
export const useDarkMode = (): DarkModeRef => {
- const isDarkMode = inject(darkModeSymbol)
- if (!isDarkMode) {
+ const isDark = inject(darkModeSymbol)
+ if (isDark === undefined) {
throw new Error('useDarkMode() is called without provider.')
}
- return isDarkMode
+ return isDark
}
/**
* Create dark mode ref and provide as global computed in setup
*/
export const setupDarkMode = (): void => {
- const themeLocale = useThemeLocaleData()
- const isDarkPreferred = usePreferredDark()
- const darkStorage = useStorage(
- 'vuepress-color-scheme',
- themeLocale.value.colorMode
- )
-
- const isDarkMode = computed({
- get() {
- // disable color mode switching
- if (!themeLocale.value.colorModeSwitch) {
- return themeLocale.value.colorMode === 'dark'
- }
- // auto detected from prefers-color-scheme
- if (darkStorage.value === 'auto') {
- return isDarkPreferred.value
- }
- // storage value
- return darkStorage.value === 'dark'
- },
- set(val) {
- if (val === isDarkPreferred.value) {
- darkStorage.value = 'auto'
- } else {
- darkStorage.value = val ? 'dark' : 'light'
- }
- },
- })
- provide(darkModeSymbol, isDarkMode)
-
- updateHtmlDarkClass(isDarkMode)
-}
-
-export const updateHtmlDarkClass = (isDarkMode: DarkModeRef): void => {
- const update = (value = isDarkMode.value): void => {
- // set `class="dark"` on `` element
- const htmlEl = window?.document.querySelector('html')
- htmlEl?.classList.toggle('dark', value)
- }
-
- onMounted(() => {
- watch(isDarkMode, update, { immediate: true })
- })
-
- onUnmounted(() => update())
+ const isDark = ref(false)
+ provide(darkModeSymbol, isDark)
}
diff --git a/packages/theme/src/client/composables/flyout.ts b/packages/theme/src/client/composables/flyout.ts
new file mode 100644
index 00000000..cecc2520
--- /dev/null
+++ b/packages/theme/src/client/composables/flyout.ts
@@ -0,0 +1,59 @@
+import { onUnmounted, readonly, ref, type Ref, watch } from 'vue'
+import { inBrowser } from '../utils/index.js'
+
+interface UseFlyoutOptions {
+ el: Ref
+ onFocus?(): void
+ onBlur?(): void
+}
+
+export const focusedElement = ref()
+
+let active = false
+let listeners = 0
+
+export function useFlyout(options: UseFlyoutOptions) {
+ const focus = ref(false)
+
+ if (inBrowser) {
+ !active && activateFocusTracking()
+
+ listeners++
+
+ const unwatch = watch(focusedElement, (el) => {
+ if (el === options.el.value || options.el.value?.contains(el!)) {
+ focus.value = true
+ options.onFocus?.()
+ } else {
+ focus.value = false
+ options.onBlur?.()
+ }
+ })
+
+ onUnmounted(() => {
+ unwatch()
+
+ listeners--
+
+ if (!listeners) {
+ deactivateFocusTracking()
+ }
+ })
+ }
+
+ return readonly(focus)
+}
+
+function activateFocusTracking() {
+ document.addEventListener('focusin', handleFocusIn)
+ active = true
+ focusedElement.value = document.activeElement as HTMLElement
+}
+
+function deactivateFocusTracking() {
+ document.removeEventListener('focusin', handleFocusIn)
+}
+
+function handleFocusIn() {
+ focusedElement.value = document.activeElement as HTMLElement
+}
diff --git a/packages/theme/src/client/composables/sidebar.ts b/packages/theme/src/client/composables/sidebar.ts
index ac3e0a3b..85738c51 100644
--- a/packages/theme/src/client/composables/sidebar.ts
+++ b/packages/theme/src/client/composables/sidebar.ts
@@ -1,11 +1,169 @@
-import { computed } from 'vue'
+import type {
+ NotesData,
+ NotesSidebarItem,
+} from '@vuepress-plume/vuepress-plugin-notes-data'
+import { useNotesData } from '@vuepress-plume/vuepress-plugin-notes-data/client'
+import type { PageData } from '@vuepress/client'
+import { usePageData, usePageFrontmatter, withBase } from '@vuepress/client'
+import { useMediaQuery } from '@vueuse/core'
+import type { ComputedRef, Ref } from 'vue'
+import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'
+import { useRoute } from 'vue-router'
+import { isActive } from '../utils/index.js'
+import { useThemeLocaleData } from './themeData.js'
+
+export function getSidebarList(path: string, notesData: NotesData) {
+ const link = Object.keys(notesData).find((link) =>
+ path.startsWith(withBase(link))
+ )
+ return link ? notesData[link] : []
+}
export function useSidebar() {
+ const route = useRoute()
+ const notesData = useNotesData()
+ const theme = useThemeLocaleData()
+ const frontmatter = usePageFrontmatter()
+
+ const is960 = useMediaQuery('(min-width: 960px)')
+
+ const isOpen = ref(false)
+
+ const sidebar = computed(() => {
+ return theme.value.notes ? getSidebarList(route.path, notesData.value) : []
+ })
const hasSidebar = computed(() => {
- return false
+ return !frontmatter.value.home && sidebar.value.length > 0
})
+ const hasAside = computed(() => {
+ return !frontmatter.value.home && frontmatter.value.aside !== false
+ })
+
+ const isSidebarEnabled = computed(() => hasSidebar.value && is960.value)
+
+ function open() {
+ isOpen.value = true
+ }
+
+ function close() {
+ isOpen.value = false
+ }
+
+ function toggle() {
+ isOpen.value ? close() : open()
+ }
+
return {
+ isOpen,
+ sidebar,
hasSidebar,
+ hasAside,
+ isSidebarEnabled,
+ open,
+ close,
+ toggle,
}
}
+
+export function useCloseSidebarOnEscape(
+ isOpen: Ref,
+ close: () => void
+) {
+ let triggerElement: HTMLButtonElement | undefined
+
+ watchEffect(() => {
+ triggerElement = isOpen.value
+ ? (document.activeElement as HTMLButtonElement)
+ : undefined
+ })
+
+ onMounted(() => {
+ window.addEventListener('keyup', onEscape)
+ })
+
+ onUnmounted(() => {
+ window.removeEventListener('keyup', onEscape)
+ })
+
+ function onEscape(e: KeyboardEvent) {
+ if (e.key === 'Escape' && isOpen.value) {
+ close()
+ triggerElement?.focus()
+ }
+ }
+}
+
+export function useSidebarControl(item: ComputedRef) {
+ const page = usePageData()
+
+ const collapsed = ref(false)
+
+ const collapsible = computed(() => {
+ return item.value.collapsed != null
+ })
+
+ const isLink = computed(() => {
+ return !!item.value.link
+ })
+
+ const isActiveLink = computed(() => {
+ return isActive(page.value.path, item.value.link)
+ })
+
+ const hasActiveLink = computed(() => {
+ if (isActiveLink.value) {
+ return true
+ }
+
+ return item.value.items
+ ? containsActiveLink(
+ page.value.path,
+ item.value.items as NotesSidebarItem[]
+ )
+ : false
+ })
+
+ const hasChildren = computed(() => {
+ return !!(item.value.items && item.value.items.length)
+ })
+
+ watchEffect(() => {
+ collapsed.value = !!(collapsible.value && item.value.collapsed)
+ })
+
+ watchEffect(() => {
+ ;(isActiveLink.value || hasActiveLink.value) && (collapsed.value = false)
+ })
+
+ function toggle() {
+ if (collapsible.value) {
+ collapsed.value = !collapsed.value
+ }
+ }
+
+ return {
+ collapsed,
+ collapsible,
+ isLink,
+ isActiveLink,
+ hasActiveLink,
+ hasChildren,
+ toggle,
+ }
+}
+
+export function containsActiveLink(
+ path: string,
+ items: NotesSidebarItem | NotesSidebarItem[]
+): boolean {
+ if (Array.isArray(items)) {
+ return items.some((item) => containsActiveLink(path, item))
+ }
+
+ return isActive(path, items.link)
+ ? true
+ : items.items
+ ? containsActiveLink(path, items.items as NotesSidebarItem[])
+ : false
+}
diff --git a/packages/theme/src/client/styles/index.scss b/packages/theme/src/client/styles/index.scss
index dec863da..14cd491e 100644
--- a/packages/theme/src/client/styles/index.scss
+++ b/packages/theme/src/client/styles/index.scss
@@ -2,3 +2,4 @@
@use 'fonts';
@use 'normalize';
@use 'nprogress';
+@use 'utils';
diff --git a/packages/theme/src/client/styles/utils.scss b/packages/theme/src/client/styles/utils.scss
new file mode 100644
index 00000000..65c7e55e
--- /dev/null
+++ b/packages/theme/src/client/styles/utils.scss
@@ -0,0 +1,9 @@
+.visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ white-space: nowrap;
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ overflow: hidden;
+}
diff --git a/packages/theme/src/client/utils/index.ts b/packages/theme/src/client/utils/index.ts
new file mode 100644
index 00000000..326882af
--- /dev/null
+++ b/packages/theme/src/client/utils/index.ts
@@ -0,0 +1,3 @@
+export * from './shared.js'
+export * from './normalizeLink.js'
+export * from './socialIcons.js'
diff --git a/packages/theme/src/client/utils/normalizeLink.ts b/packages/theme/src/client/utils/normalizeLink.ts
new file mode 100644
index 00000000..cddb372e
--- /dev/null
+++ b/packages/theme/src/client/utils/normalizeLink.ts
@@ -0,0 +1,20 @@
+import { withBase } from '@vuepress/client'
+import { isExternal, PATHNAME_PROTOCOL_RE } from './shared.js'
+
+export function normalizeLink(url: string): string {
+ if (isExternal(url)) {
+ return url.replace(PATHNAME_PROTOCOL_RE, '')
+ }
+
+ const { pathname, search, hash } = new URL(url, 'http://example.com')
+
+ const normalizedPath =
+ pathname.endsWith('/') || pathname.endsWith('.html')
+ ? url
+ : url.replace(
+ /(?:(^\.+)\/)?.*$/,
+ `$1${pathname.replace(/(\.md)?$/, '.html')}${search}${hash}`
+ )
+
+ return withBase(normalizedPath)
+}
diff --git a/packages/theme/src/client/utils/shared.ts b/packages/theme/src/client/utils/shared.ts
new file mode 100644
index 00000000..e0a49ff9
--- /dev/null
+++ b/packages/theme/src/client/utils/shared.ts
@@ -0,0 +1,43 @@
+export const EXTERNAL_URL_RE = /^[a-z]+:/i
+export const PATHNAME_PROTOCOL_RE = /^pathname:\/\//
+export const APPEARANCE_KEY = 'vuepress-theme-appearance'
+export const HASH_RE = /#.*$/
+export const EXT_RE = /(index)?\.(md|html)$/
+
+export const inBrowser = typeof document !== 'undefined'
+
+export function isActive(
+ currentPath: string,
+ matchPath?: string,
+ asRegex = false
+): boolean {
+ if (matchPath === undefined) {
+ return false
+ }
+
+ currentPath = normalize(`/${currentPath}`)
+
+ if (asRegex) {
+ return new RegExp(matchPath).test(currentPath)
+ }
+
+ if (normalize(matchPath) !== currentPath) {
+ return false
+ }
+
+ const hashMatch = matchPath.match(HASH_RE)
+
+ if (hashMatch) {
+ return (inBrowser ? location.hash : '') === hashMatch[0]
+ }
+
+ return true
+}
+
+export function normalize(path: string): string {
+ return decodeURI(path).replace(HASH_RE, '').replace(EXT_RE, '')
+}
+
+export function isExternal(path: string): boolean {
+ return EXTERNAL_URL_RE.test(path)
+}
diff --git a/packages/theme/src/client/utils/socialIcons.ts b/packages/theme/src/client/utils/socialIcons.ts
new file mode 100644
index 00000000..54a91e6e
--- /dev/null
+++ b/packages/theme/src/client/utils/socialIcons.ts
@@ -0,0 +1,22 @@
+// Used under CC0 1.0 from https://simpleicons.org/
+
+export const icons = {
+ discord:
+ '',
+ facebook:
+ '',
+ github:
+ '',
+ instagram:
+ '',
+ linkedin:
+ '',
+ mastodon:
+ '',
+ slack:
+ '',
+ twitter:
+ '',
+ youtube:
+ '',
+} as const
diff --git a/packages/theme/src/node/plugins.ts b/packages/theme/src/node/plugins.ts
index c2f17666..dff0f715 100644
--- a/packages/theme/src/node/plugins.ts
+++ b/packages/theme/src/node/plugins.ts
@@ -34,7 +34,14 @@ export const setupPlugins = (
return [
palettePlugin({ preset: 'sass' }),
- themeDataPlugin({ themeData: localeOptions }),
+ themeDataPlugin({
+ themeData: {
+ ...localeOptions,
+ notes: localeOptions.notes
+ ? { dir: localeOptions.notes.dir, link: localeOptions.notes.link }
+ : undefined,
+ } as any,
+ }),
autoFrontmatterPlugin(autoFrontmatter(app, localeOptions)),
blogDataPlugin({
include: ['**/*.md'],
diff --git a/packages/theme/src/shared/options/index.ts b/packages/theme/src/shared/options/index.ts
index 3363b0e1..4bba3d83 100644
--- a/packages/theme/src/shared/options/index.ts
+++ b/packages/theme/src/shared/options/index.ts
@@ -47,3 +47,4 @@ export type PlumeThemeData = ThemeData
export * from './locale.js'
export * from './plugins.js'
+export * from './navbar.js'
diff --git a/packages/theme/src/shared/options/locale.ts b/packages/theme/src/shared/options/locale.ts
index 3183df1a..ef93cf02 100644
--- a/packages/theme/src/shared/options/locale.ts
+++ b/packages/theme/src/shared/options/locale.ts
@@ -1,12 +1,9 @@
import type { NotesDataOptions } from '@vuepress-plume/vuepress-plugin-notes-data'
import type { LocaleData } from '@vuepress/core'
+import type { NavItem, NavItemWithLink } from './navbar.js'
// import type { NavbarConfig, NavLink } from '../layout/index.js'
// import type { PlumeThemeNotesOptions } from './notes.js'
-// todo type
-type NavbarConfig = any
-type NavLink = any
-
export interface PlumeThemeAvatar {
/**
* 头像链接
@@ -22,46 +19,28 @@ export interface PlumeThemeAvatar {
description?: string
}
-export interface PlumeThemeSocialOption {
- /**
- * 邮箱
- */
- email?: string
- /**
- * github链接 支持仅填写 organization / Repositories
- */
- github?: string
- /**
- * 微博
- */
- weiBo?: string
- /**
- * 知乎
- */
- zhiHu?: string
- /**
- * QQ
- */
- QQ?: string
- /**
- * facebook
- */
- facebook?: string
- /**
- * twitter
- */
- twitter?: string
- /**
- * linkedin 英领
- */
- linkedin?: string
+export interface SocialLink {
+ icon: SocialLinkIcon
+ link: string
}
+export type SocialLinkIcon =
+ | 'discord'
+ | 'facebook'
+ | 'github'
+ | 'instagram'
+ | 'linkedin'
+ | 'mastodon'
+ | 'slack'
+ | 'twitter'
+ | 'youtube'
+ | { svg: string }
+
export interface PlumeThemeLocaleData extends LocaleData {
/**
* 网站站点首页
*/
- home?: false | NavLink
+ home?: false | NavItemWithLink
/**
* 网站站点logo
*/
@@ -73,13 +52,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
/**
* 是否启用深色模式切换按钮
*/
- colorModeSwitch?: boolean
-
- colorMode?: 'auto' | 'light' | 'dark'
-
- toggleDarkMode?: string
-
- toggleSidebar?: string
+ appearance?: boolean | 'dark'
/**
* 部署站点域名。
@@ -98,7 +71,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
/**
* 社交账号配置
*/
- social?: PlumeThemeSocialOption
+ social?: SocialLink[]
/**
* 文章链接前缀
@@ -112,14 +85,14 @@ export interface PlumeThemeLocaleData extends LocaleData {
*
* @def:{ text: '标签', link: '/tag/' }
*/
- tag?: false | NavLink
+ tag?: false | NavItemWithLink
/**
* 文章分类 与 navbar配置
*
* @default: { text: '分类', link: '/category/ }
*/
- category?: false | NavLink
+ category?: false | NavItemWithLink
/**
* 归档页 链接与 navbar 配置
@@ -128,7 +101,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
*
* @default: { text: '归档', link: '/timeline/' }
*/
- archive?: false | NavLink
+ archive?: false | NavItemWithLink
/**
* 笔记配置, 笔记中的文章默认不会出现在首页文章列表
@@ -165,7 +138,7 @@ export interface PlumeThemeLocaleData extends LocaleData {
*
* Set to `false` to disable navbar in current locale
*/
- navbar?: false | NavbarConfig
+ navbar?: false | NavItem[]
/**
* 外部链接打开方式
*/
diff --git a/packages/theme/src/shared/options/navbar.ts b/packages/theme/src/shared/options/navbar.ts
new file mode 100644
index 00000000..b70ed4a8
--- /dev/null
+++ b/packages/theme/src/shared/options/navbar.ts
@@ -0,0 +1,28 @@
+export type NavItem = NavItemWithLink | NavItemWithChildren
+
+export type NavItemWithLink = {
+ text: string
+ link: string
+
+ /**
+ * `activeMatch` is expected to be a regex string. We can't use actual
+ * RegExp object here because it isn't serializable
+ */
+ activeMatch?: string
+}
+
+export type NavItemChildren = {
+ text?: string
+ items: NavItemWithLink[]
+}
+
+export interface NavItemWithChildren {
+ text?: string
+ items: (NavItemChildren | NavItemWithLink)[]
+
+ /**
+ * `activeMatch` is expected to be a regex string. We can't use actual
+ * RegExp object here because it isn't serializable
+ */
+ activeMatch?: string
+}