commit
718d9a091a
@ -116,7 +116,7 @@ export const zhNotes = definePlumeNotesConfig({
|
||||
{
|
||||
text: 'plugin-netlify-functions',
|
||||
dir: 'netlify-functions',
|
||||
link: '/plugins/plugin-netlify-functions/',
|
||||
link: 'plugin-netlify-functions/',
|
||||
items: [
|
||||
'介绍',
|
||||
'使用',
|
||||
|
||||
@ -60,7 +60,7 @@ let el: HTMLDivElement | null = null
|
||||
|
||||
watch(() => onlyOnce.value, value => nextTick(() => {
|
||||
if (typeof document !== 'undefined') {
|
||||
el ??= document.querySelector('#VPContent')
|
||||
el ??= document.querySelector('.vp-layout')
|
||||
el?.classList.toggle('footer-no-border', value)
|
||||
}
|
||||
}), { immediate: true })
|
||||
|
||||
@ -9,23 +9,20 @@ import VPDocMeta from '@theme/VPDocMeta.vue'
|
||||
import { useEncrypt } from '../composables/encrypt.js'
|
||||
import { useSidebar } from '../composables/sidebar.js'
|
||||
import { useData } from '../composables/data.js'
|
||||
import { useHeaders } from '../composables/outline.js'
|
||||
|
||||
const { page, theme, frontmatter, isDark } = useData()
|
||||
const route = useRoute()
|
||||
|
||||
const { hasSidebar, hasAside, leftAside } = useSidebar()
|
||||
const headers = useHeaders()
|
||||
const { isPageDecrypted } = useEncrypt()
|
||||
|
||||
const hasComments = computed(() => {
|
||||
return page.value.frontmatter.comments !== false && isPageDecrypted.value
|
||||
})
|
||||
|
||||
const enableAside = computed(() => {
|
||||
if (page.value.isBlogPost)
|
||||
return hasAside.value && isPageDecrypted.value && page.value.headers.length
|
||||
|
||||
return hasAside.value && isPageDecrypted.value
|
||||
})
|
||||
const enableAside = computed(() => hasAside.value && headers.value.length)
|
||||
|
||||
const pageName = computed(() =>
|
||||
route.path.replace(/[./]+/g, '_').replace(/_html$/, ''),
|
||||
@ -68,7 +65,7 @@ watch(
|
||||
<div
|
||||
:key="page.path" class="vp-doc-container" :class="{
|
||||
'has-sidebar': hasSidebar,
|
||||
'has-aside': hasAside,
|
||||
'has-aside': enableAside,
|
||||
'is-blog': page.isBlogPost,
|
||||
'with-encrypt': !isPageDecrypted,
|
||||
}"
|
||||
@ -207,7 +204,7 @@ watch(
|
||||
.aside-container {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
min-height: calc(100vh - var(--vp-footer-height, 0px));
|
||||
min-height: calc(100vh - var(--vp-nav-height, 0px) - var(--vp-footer-height, 0px));
|
||||
max-height: 100vh;
|
||||
padding-top: calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 32px);
|
||||
margin-top: calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1 - 32px);
|
||||
@ -241,7 +238,7 @@ watch(
|
||||
.aside-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));
|
||||
min-height: calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-footer-height, 0px) + 48px));
|
||||
padding-bottom: 32px;
|
||||
}
|
||||
|
||||
|
||||
@ -1,19 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
|
||||
import VPDocOutlineItem from '@theme/VPDocOutlineItem.vue'
|
||||
import { type MenuItem, getHeaders, useActiveAnchor } from '../composables/outline.js'
|
||||
import { useActiveAnchor, useHeaders } from '../composables/outline.js'
|
||||
import { useData } from '../composables/data.js'
|
||||
|
||||
const { theme, frontmatter } = useData()
|
||||
const { theme } = useData()
|
||||
|
||||
const headers = ref<MenuItem[]>([])
|
||||
const headers = useHeaders()
|
||||
const hasOutline = computed(() => headers.value.length > 0)
|
||||
|
||||
onContentUpdated(() => {
|
||||
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
|
||||
})
|
||||
|
||||
const container = ref()
|
||||
const marker = ref()
|
||||
|
||||
|
||||
@ -50,6 +50,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.footer-no-border .vp-footer {
|
||||
background-color: transparent;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { useWindowScroll } from '@vueuse/core'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
|
||||
import VPLocalNavOutlineDropdown from '@theme/VPLocalNavOutlineDropdown.vue'
|
||||
import { useSidebar } from '../composables/sidebar.js'
|
||||
import { type MenuItem, getHeaders } from '../composables/outline.js'
|
||||
import { useHeaders } from '../composables/outline.js'
|
||||
import { useData } from '../composables/data.js'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -14,23 +13,19 @@ const props = defineProps<{
|
||||
|
||||
defineEmits<(e: 'openMenu') => void>()
|
||||
|
||||
const { page, theme, frontmatter } = useData()
|
||||
const { page, theme } = useData()
|
||||
|
||||
const { hasSidebar } = useSidebar()
|
||||
const { y } = useWindowScroll()
|
||||
|
||||
const navHeight = ref(0)
|
||||
|
||||
const headers = ref<MenuItem[]>([])
|
||||
const headers = useHeaders()
|
||||
|
||||
const empty = computed(() => {
|
||||
return headers.value.length === 0 && !hasSidebar.value
|
||||
})
|
||||
|
||||
onContentUpdated(() => {
|
||||
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
navHeight.value = Number.parseInt(
|
||||
getComputedStyle(document.documentElement).getPropertyValue(
|
||||
|
||||
@ -10,7 +10,7 @@ export function useArchives() {
|
||||
const archives: { label: string, list: ShortPostItem[] }[] = []
|
||||
|
||||
list.value.forEach((item) => {
|
||||
const createTime = item.createTime.split(' ')[0]
|
||||
const createTime = item.createTime?.split(' ')[0] || ''
|
||||
const year = createTime.split('/')[0]
|
||||
let current = archives.find(archive => archive.label === year)
|
||||
if (!current) {
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { onMounted, onUnmounted, onUpdated } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { inject, onMounted, onUnmounted, onUpdated, provide, ref } from 'vue'
|
||||
import type { InjectionKey, Ref } from 'vue'
|
||||
import { onContentUpdated } from '@vuepress-plume/plugin-content-update/client'
|
||||
import type { ThemeOutline } from '../../shared/index.js'
|
||||
import { throttleAndDebounce } from '../utils/index.js'
|
||||
import { useAside } from './aside.js'
|
||||
import { useData } from './data.js'
|
||||
|
||||
export interface Header {
|
||||
/**
|
||||
@ -41,6 +43,31 @@ export type MenuItem = Omit<Header, 'slug' | 'children'> & {
|
||||
children?: MenuItem[]
|
||||
}
|
||||
|
||||
export const headersSymbol: InjectionKey<Ref<MenuItem[]>> = Symbol(
|
||||
__VUEPRESS_DEV__ ? 'headers' : '',
|
||||
)
|
||||
|
||||
export function setupHeaders(): Ref<MenuItem[]> {
|
||||
const { frontmatter, theme } = useData()
|
||||
const headers = ref<MenuItem[]>([])
|
||||
|
||||
onContentUpdated(() => {
|
||||
headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline)
|
||||
})
|
||||
|
||||
provide(headersSymbol, headers)
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
export function useHeaders(): Ref<MenuItem[]> {
|
||||
const headers = inject(headersSymbol)
|
||||
if (!headers) {
|
||||
throw new Error('useHeaders() is called without provider.')
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export function getHeaders(range?: ThemeOutline): MenuItem[] {
|
||||
const headers = Array.from(
|
||||
document.querySelectorAll('.vp-doc :where(h1,h2,h3,h4,h5,h6)'),
|
||||
|
||||
@ -23,6 +23,7 @@ import { isActive, normalizeLink, normalizePrefix, resolveNavLink } from '../uti
|
||||
import type { Sidebar, SidebarItem } from '../../shared/index.js'
|
||||
import type { ResolvedSidebarItem } from '../../shared/resolved/sidebar.js'
|
||||
import { useData } from './data.js'
|
||||
import { useEncrypt } from './encrypt.js'
|
||||
|
||||
export type SidebarData = Record<string, Sidebar>
|
||||
|
||||
@ -229,6 +230,7 @@ export function useSidebar(): UseSidebarReturn {
|
||||
const { theme, frontmatter, page } = useData()
|
||||
const routeLocal = useRouteLocale()
|
||||
const is960 = useMediaQuery('(min-width: 960px)')
|
||||
const { isPageDecrypted } = useEncrypt()
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
@ -257,6 +259,13 @@ export function useSidebar(): UseSidebarReturn {
|
||||
const hasAside = computed(() => {
|
||||
if (frontmatter.value.pageLayout === 'home' || frontmatter.value.home)
|
||||
return false
|
||||
|
||||
if (frontmatter.value.pageLayout === 'friends' || frontmatter.value.friends)
|
||||
return false
|
||||
|
||||
if (!isPageDecrypted.value)
|
||||
return false
|
||||
|
||||
if (frontmatter.value.aside != null)
|
||||
return !!frontmatter.value.aside
|
||||
return theme.value.aside !== false
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
enhanceScrollBehavior,
|
||||
setupDarkMode,
|
||||
setupEncrypt,
|
||||
setupHeaders,
|
||||
setupSidebar,
|
||||
setupThemeData,
|
||||
setupWatermark,
|
||||
@ -23,6 +24,7 @@ export default defineClientConfig({
|
||||
},
|
||||
setup() {
|
||||
setupSidebar()
|
||||
setupHeaders()
|
||||
setupEncrypt()
|
||||
setupWatermark()
|
||||
},
|
||||
|
||||
@ -69,7 +69,7 @@ export async function generateAFrontmatter(app: App) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function watchAutoFrontmatter(app: App, watchers: any[]) {
|
||||
export async function watchAutoFrontmatter(app: App, watchers: any[], enable?: () => boolean) {
|
||||
if (!generate)
|
||||
return
|
||||
|
||||
@ -80,7 +80,8 @@ export async function watchAutoFrontmatter(app: App, watchers: any[]) {
|
||||
})
|
||||
|
||||
watcher.on('add', async (relativePath) => {
|
||||
if (!generate!.globFilter(relativePath))
|
||||
const enabled = enable ? enable() : true
|
||||
if (!generate!.globFilter(relativePath) || !enabled)
|
||||
return
|
||||
const file = await readMarkdown(app.dir.source(), relativePath)
|
||||
await generator(file)
|
||||
|
||||
@ -101,7 +101,7 @@ export function resolveOptions(
|
||||
const note = findNote(relativePath)
|
||||
if (note?.text)
|
||||
return note.text
|
||||
return getCurrentDirname(note?.dir, relativePath) || ''
|
||||
return getCurrentDirname('', relativePath) || ''
|
||||
},
|
||||
...baseFrontmatter,
|
||||
permalink(permalink: string, { relativePath }, data: any) {
|
||||
|
||||
@ -41,13 +41,11 @@ export async function initConfigLoader(
|
||||
defaultConfig: ThemeConfig,
|
||||
{ configFile, onChange }: InitConfigLoaderOptions = {},
|
||||
) {
|
||||
configFile = await findConfigPath(app, configFile)
|
||||
|
||||
const { encrypt, autoFrontmatter, ...localeOptions } = defaultConfig
|
||||
loader = {
|
||||
configFile,
|
||||
dependencies: [],
|
||||
load: () => compiler(configFile),
|
||||
load: () => compiler(loader!.configFile),
|
||||
loaded: false,
|
||||
watcher: null,
|
||||
changeEvents: [],
|
||||
@ -60,6 +58,8 @@ export async function initConfigLoader(
|
||||
},
|
||||
}
|
||||
|
||||
loader.configFile = await findConfigPath(app, configFile)
|
||||
|
||||
onChange && loader.changeEvents.push(onChange)
|
||||
|
||||
const { config, dependencies = [] } = await loader.load()
|
||||
|
||||
@ -57,8 +57,6 @@ export async function preparedBlogData(
|
||||
) as Page<PlumeThemePageData, PlumeThemePostFrontmatter>[]
|
||||
|
||||
const blogData: PlumeThemeBlogPostData = pages.map((page) => {
|
||||
page.data.isBlogPost = true
|
||||
|
||||
const tags = page.frontmatter.tags
|
||||
const data: PlumeThemeBlogPostItem = {
|
||||
path: page.path,
|
||||
@ -66,7 +64,7 @@ export async function preparedBlogData(
|
||||
categoryList: page.data.categoryList,
|
||||
tags,
|
||||
sticky: page.frontmatter.sticky,
|
||||
createTime: page.data.frontmatter.createTime!,
|
||||
createTime: page.data.frontmatter.createTime! || page.date?.replaceAll('-', '/') || '',
|
||||
lang: page.lang,
|
||||
excerpt: '',
|
||||
}
|
||||
|
||||
@ -1,6 +1,17 @@
|
||||
import type { App, Page } from 'vuepress'
|
||||
import { entries, isArray, isPlainObject, removeLeadingSlash } from '@vuepress/helper'
|
||||
import type { PlumeThemeLocaleOptions, PlumeThemePageData, Sidebar, SidebarItem, ThemeIcon } from '../../shared/index.js'
|
||||
import {
|
||||
entries,
|
||||
isArray,
|
||||
isPlainObject,
|
||||
removeLeadingSlash,
|
||||
} from '@vuepress/helper'
|
||||
import type {
|
||||
PlumeThemeLocaleOptions,
|
||||
PlumeThemePageData,
|
||||
Sidebar,
|
||||
SidebarItem,
|
||||
ThemeIcon,
|
||||
} from '../../shared/index.js'
|
||||
import { normalizeLink, resolveContent, writeTemp } from '../utils/index.js'
|
||||
import type { ResolvedSidebarItem } from '../../shared/resolved/sidebar.js'
|
||||
|
||||
@ -53,6 +64,14 @@ function getSidebarData(
|
||||
return resolved
|
||||
}
|
||||
|
||||
const MD_RE = /\.md$/
|
||||
const NUMBER_RE = /^\d+\./
|
||||
function resolveTitle(dirname: string) {
|
||||
return dirname
|
||||
.replace(MD_RE, '')
|
||||
.replace(NUMBER_RE, '')
|
||||
}
|
||||
|
||||
function getAutoDirSidebar(
|
||||
app: App,
|
||||
localePath: string,
|
||||
@ -80,6 +99,7 @@ function getAutoDirSidebar(
|
||||
}
|
||||
|
||||
const RE_INDEX = ['index.md', 'README.md', 'readme.md']
|
||||
|
||||
const result: ResolvedSidebarItem[] = []
|
||||
for (const page of pages) {
|
||||
const { data, title, path, frontmatter } = page
|
||||
@ -90,25 +110,37 @@ function getAutoDirSidebar(
|
||||
let index = 0
|
||||
let dir: string
|
||||
let items = result
|
||||
let parent: ResolvedSidebarItem | undefined
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while ((dir = paths[index])) {
|
||||
const text = dir.replace(/\.md$/, '').replace(/^\d+\./, '')
|
||||
const text = resolveTitle(dir)
|
||||
const isHome = RE_INDEX.includes(dir)
|
||||
let current = items.find(item => item.text === text)
|
||||
if (!current) {
|
||||
current = { text, link: undefined, items: [] } as ResolvedSidebarItem
|
||||
!RE_INDEX.includes(dir) ? items.push(current) : items.unshift(current)
|
||||
if (!isHome) {
|
||||
items.push(current)
|
||||
}
|
||||
else {
|
||||
!parent && items.unshift(current)
|
||||
}
|
||||
}
|
||||
if (dir.endsWith('.md')) {
|
||||
current.link = path
|
||||
current.text = title
|
||||
if (isHome && parent) {
|
||||
parent.link = path
|
||||
}
|
||||
else {
|
||||
current.link = path
|
||||
current.text = title
|
||||
}
|
||||
}
|
||||
if (frontmatter.icon)
|
||||
if (frontmatter.icon) {
|
||||
current.icon = frontmatter.icon as ThemeIcon
|
||||
|
||||
if (index > 0) {
|
||||
current.collapsed = false
|
||||
}
|
||||
|
||||
if (parent?.items?.length) {
|
||||
parent.collapsed = true
|
||||
}
|
||||
parent = current
|
||||
items = current.items as ResolvedSidebarItem[]
|
||||
index++
|
||||
}
|
||||
|
||||
@ -2,17 +2,19 @@ import {
|
||||
ensureLeadingSlash,
|
||||
getRootLang,
|
||||
getRootLangPath,
|
||||
removeLeadingSlash,
|
||||
} from '@vuepress/helper'
|
||||
import type { App, Page } from 'vuepress/core'
|
||||
import { createPage } from 'vuepress/core'
|
||||
import { createFilter } from 'create-filter'
|
||||
import type {
|
||||
PageCategoryData,
|
||||
PlumeThemeLocaleOptions,
|
||||
PlumeThemePageData,
|
||||
} from '../shared/index.js'
|
||||
import { withBase } from './utils/index.js'
|
||||
import { normalizePath, withBase } from './utils/index.js'
|
||||
import { PRESET_LOCALES } from './locales/index.js'
|
||||
import { resolveNotesLinkList } from './config/index.js'
|
||||
import { resolveNotesLinkList, resolveNotesOptions } from './config/index.js'
|
||||
|
||||
export async function setupPage(
|
||||
app: App,
|
||||
@ -56,6 +58,35 @@ export async function setupPage(
|
||||
app.pages.push(...await Promise.all(pageList))
|
||||
}
|
||||
|
||||
const weakFilter = new WeakMap<PlumeThemeLocaleOptions, (id: string | undefined) => boolean>()
|
||||
|
||||
function createBlogFilter(localeOptions: PlumeThemeLocaleOptions) {
|
||||
if (weakFilter.has(localeOptions))
|
||||
return weakFilter.get(localeOptions)!
|
||||
|
||||
const blog = localeOptions.blog || {}
|
||||
const notesList = resolveNotesOptions(localeOptions)
|
||||
const notesDirList = notesList
|
||||
.map(notes => removeLeadingSlash(normalizePath(`${notes.dir}/**`)))
|
||||
.filter(Boolean)
|
||||
|
||||
const filter = createFilter(
|
||||
blog.include ?? ['**/*.md'],
|
||||
[
|
||||
'**/{README,readme,index}.md',
|
||||
'.vuepress/',
|
||||
'node_modules/',
|
||||
...(blog.exclude ?? []),
|
||||
...notesDirList,
|
||||
].filter(Boolean),
|
||||
{ resolve: false },
|
||||
)
|
||||
|
||||
weakFilter.set(localeOptions, filter)
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
export function extendsPageData(
|
||||
page: Page<PlumeThemePageData>,
|
||||
localeOptions: PlumeThemeLocaleOptions,
|
||||
@ -63,6 +94,10 @@ export function extendsPageData(
|
||||
page.data.filePathRelative = page.filePathRelative
|
||||
page.routeMeta.title = page.frontmatter.title || page.title
|
||||
|
||||
if (createBlogFilter(localeOptions)(page.filePathRelative || '')) {
|
||||
page.data.isBlogPost = true
|
||||
}
|
||||
|
||||
if (page.frontmatter.icon) {
|
||||
page.routeMeta.icon = page.frontmatter.icon
|
||||
}
|
||||
|
||||
@ -79,10 +79,10 @@ export function plumeTheme(options: PlumeThemeOptions = {}): Theme {
|
||||
onWatched: (app, watchers) => {
|
||||
watchConfigFile(app, watchers)
|
||||
watchPrepare(app, watchers)
|
||||
const autoFrontmatter = getResolvedThemeConfig().autoFrontmatter ?? pluginOptions.frontmatter
|
||||
if (autoFrontmatter !== false) {
|
||||
watchAutoFrontmatter(app, watchers)
|
||||
}
|
||||
watchAutoFrontmatter(app, watchers, () => {
|
||||
const autoFrontmatter = getResolvedThemeConfig().autoFrontmatter ?? pluginOptions.frontmatter
|
||||
return autoFrontmatter !== false
|
||||
})
|
||||
},
|
||||
|
||||
extendsPage: async (page) => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user