chore: tweak

This commit is contained in:
pengzhanbo 2024-06-16 15:41:27 +08:00
parent 89e99f10fa
commit fbe57418f9
10 changed files with 273 additions and 249 deletions

View File

@ -1,22 +1,34 @@
<script lang="ts" setup>
import { computed } from 'vue'
import VPLink from '@theme/VPLink.vue'
import {
useContributors,
useEditNavLink,
useLastUpdated,
usePageNav,
} from '../composables/page.js'
import { useContributors } from '../composables/contributors.js'
import { useEditLink } from '../composables/edit-link.js'
import { useLastUpdated } from '../composables/latest-updated.js'
import { usePrevNext } from '../composables/prev-next.js'
import { useData } from '../composables/data.js'
const { theme } = useData()
const editNavLink = useEditNavLink()
const { theme, frontmatter } = useData()
const editLink = useEditLink()
const { datetime: lastUpdated, isoDatetime, lastUpdatedText } = useLastUpdated()
const contributors = useContributors()
const { prev, next } = usePageNav()
const { prev, next } = usePrevNext()
const hasEditLink = computed(() =>
Boolean(theme.value.editLink && frontmatter.value.editLink !== false && editLink.value),
)
const hasLastUpdated = computed(() =>
Boolean(theme.value.lastUpdated && frontmatter.value.lastUpdated !== false && lastUpdated.value),
)
const hasContributors = computed(() =>
Boolean(theme.value.contributors && frontmatter.value.contributors !== false && contributors.value?.length),
)
const showFooter = computed(() => {
return editNavLink.value || lastUpdated.value || contributors.value || prev.value || next.value
return hasEditLink.value
|| hasLastUpdated.value
|| hasContributors.value
|| prev.value?.link
|| next.value?.link
})
</script>
@ -24,15 +36,15 @@ const showFooter = computed(() => {
<footer v-if="showFooter" class="vp-doc-footer">
<slot name="doc-footer-before" />
<div v-if="editNavLink || lastUpdated" class="edit-info">
<div v-if="editNavLink" class="edit-link">
<VPLink class="edit-link-button" :href="editNavLink.link" :no-icon="true">
<div v-if="hasEditLink || hasLastUpdated" class="edit-info">
<div v-if="hasEditLink && editLink" class="edit-link">
<VPLink class="edit-link-button" :href="editLink.link" :no-icon="true">
<span class="vpi-square-pen edit-link-icon" aria-label="edit icon" />
{{ editNavLink.text }}
{{ editLink.text }}
</VPLink>
</div>
<div v-if="lastUpdated" class="last-updated">
<div v-if="hasLastUpdated" class="last-updated">
<p class="last-updated-text">
{{ lastUpdatedText }}:
<time :datetime="isoDatetime" class="last-updated-time">
@ -42,7 +54,7 @@ const showFooter = computed(() => {
</div>
</div>
<div v-if="contributors && contributors.length" class="contributors">
<div v-if="hasContributors && contributors?.length" class="contributors">
<span class="contributors-label">
{{ theme.contributorsText || 'Contributors' }}:
</span>

View File

@ -3,10 +3,10 @@ import { computed } from 'vue'
import VPLink from '@theme/VPLink.vue'
import VPFriendsItem from '@theme/VPFriendsItem.vue'
import VPFriendsGroup from '@theme/VPFriendsGroup.vue'
import { useEditNavLink } from '../composables/page.js'
import { useEditLink } from '../composables/edit-link.js'
import { useData } from '../composables/data.js'
const editNavLink = useEditNavLink()
const editLink = useEditLink()
const { frontmatter: matter } = useData<'friends'>()
const list = computed(() => matter.value.list || [])
@ -31,14 +31,14 @@ const groups = computed(() => matter.value.groups || [])
<VPFriendsGroup v-for="(group, index) in groups" :key="index" :group="group" />
<div v-if="editNavLink" class="edit-link">
<div v-if="editLink" class="edit-link">
<VPLink
class="edit-link-button"
:href="editNavLink.link"
:href="editLink.link"
:no-icon="true"
>
<span class="vpi-square-pen edit-link-icon" aria-label="edit icon" />
{{ editNavLink.text }}
{{ editLink.text }}
</VPLink>
</div>
</div>

View File

@ -11,14 +11,12 @@ const props = defineProps<{
rel?: string
}>()
declare const __VUEPRESS_BASE__: string
const router = useRouter()
const route = useRoute()
const tag = computed(() => props.tag ?? (props.href ? 'a' : 'span'))
const isExternal = computed(
() => (props.href && isLinkExternal(props.href, __VUEPRESS_BASE__)) || props.target === '_blank',
() => (props.href && isLinkExternal(props.href)) || props.target === '_blank',
)
const link = computed(() => {
if (!props.href)

View File

@ -0,0 +1,20 @@
import { computed } from 'vue'
import type { ComputedRef } from 'vue'
import type { PlumeThemePageData } from '../../shared/index.js'
import { useData } from '../composables/data.js'
export function useContributors(): ComputedRef<
null | Required<PlumeThemePageData['git']>['contributors']
> {
const { theme, page, frontmatter } = useData()
return computed(() => {
const showContributors
= frontmatter.value.contributors ?? theme.value.contributors ?? true
if (!showContributors)
return null
return page.value.git?.contributors ?? null
})
}

View File

@ -0,0 +1,45 @@
import { computed } from 'vue'
import type { ComputedRef } from 'vue'
import type {
NavItemWithLink,
} from '../../shared/index.js'
import { useData } from '../composables/data.js'
import { resolveEditLink } from '../utils/index.js'
export function useEditLink(): ComputedRef<null | NavItemWithLink> {
const { theme, page, frontmatter } = useData()
return computed(() => {
const showEditLink
= frontmatter.value.editLink ?? theme.value.editLink ?? true
if (!showEditLink)
return null
const {
docsRepo,
docsBranch = 'main',
docsDir = '',
editLinkText,
} = theme.value
if (!docsRepo)
return null
const editLink = resolveEditLink({
docsRepo,
docsBranch,
docsDir,
filePathRelative: page.value.filePathRelative,
editLinkPattern:
frontmatter.value.editLinkPattern ?? theme.value.editLinkPattern,
})
if (!editLink)
return null
return {
text: editLinkText ?? 'Edit this page',
link: editLink,
}
})
}

View File

@ -1,13 +1,20 @@
export * from './dark-mode.js'
export * from './scroll-promise.js'
export * from './theme-data.js'
export * from './dark-mode.js'
export * from './data.js'
export * from './scroll-promise.js'
export * from './sidebar.js'
export * from './aside.js'
export * from './page.js'
export * from './outline.js'
export * from './prev-next.js'
export * from './edit-link.js'
export * from './latest-updated.js'
export * from './contributors.js'
export * from './blog.js'
export * from './tag-colors.js'
export * from './locale.js'
export * from './route-query.js'
export * from './watermark.js'
export * from './data.js'
export * from './outline.js'

View File

@ -0,0 +1,42 @@
import { usePageLang } from 'vuepress/client'
import { computed, onMounted, ref, watchEffect } from 'vue'
import { useData } from '../composables/data.js'
export function useLastUpdated() {
const { theme, page, frontmatter } = useData()
const lang = usePageLang()
const date = computed(() => page.value.git?.updatedTime ? new Date(page.value.git.updatedTime) : null)
const isoDatetime = computed(() => date.value?.toISOString())
const datetime = ref('')
const lastUpdatedText = computed(() => {
if (theme.value.lastUpdated === false)
return
return theme.value.lastUpdated?.text || theme.value.lastUpdatedText || 'Last updated'
})
onMounted(() => {
watchEffect(() => {
if (frontmatter.value.lastUpdated === false || theme.value.lastUpdated === false)
return
datetime.value = date.value
? new Intl.DateTimeFormat(
theme.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : undefined,
theme.value.lastUpdated?.formatOptions ?? {
dateStyle: 'short',
timeStyle: 'short',
},
).format(date.value)
: ''
})
})
return {
datetime,
isoDatetime,
lastUpdatedText,
}
}

View File

@ -1,217 +0,0 @@
import { resolveRouteFullPath, usePageData, usePageFrontmatter, usePageLang, useRoute } from 'vuepress/client'
import { isPlainObject, isString } from 'vuepress/shared'
import { useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
import type { NotesSidebarItem } from '@vuepress-plume/plugin-notes-data'
import { computed, onMounted, ref, watchEffect } from 'vue'
import type { ComputedRef, Ref } from 'vue'
import type {
NavItemWithLink,
PlumeThemeBlogPostItem,
PlumeThemePageData,
PlumeThemePageFrontmatter,
} from '../../shared/index.js'
import { useSidebar, useThemeLocaleData } from '../composables/index.js'
import { useData } from '../composables/data.js'
import { resolveEditLink, resolveNavLink } from '../utils/index.js'
export function useEditNavLink(): ComputedRef<null | NavItemWithLink> {
const { theme, page, frontmatter } = useData()
return computed(() => {
const showEditLink
= frontmatter.value.editLink ?? theme.value.editLink ?? true
if (!showEditLink)
return null
const {
docsRepo,
docsBranch = 'main',
docsDir = '',
editLinkText,
} = theme.value
if (!docsRepo)
return null
const editLink = resolveEditLink({
docsRepo,
docsBranch,
docsDir,
filePathRelative: page.value.filePathRelative,
editLinkPattern:
frontmatter.value.editLinkPattern ?? theme.value.editLinkPattern,
})
if (!editLink)
return null
return {
text: editLinkText ?? 'Edit this page',
link: editLink,
}
})
}
export function useLastUpdated() {
const { theme, page, frontmatter } = useData()
const lang = usePageLang()
const date = computed(() => page.value.git?.updatedTime ? new Date(page.value.git.updatedTime) : null)
const isoDatetime = computed(() => date.value?.toISOString())
const datetime = ref('')
const lastUpdatedText = computed(() => {
if (theme.value.lastUpdated === false)
return
return theme.value.lastUpdated?.text || theme.value.lastUpdatedText || 'Last updated'
})
onMounted(() => {
watchEffect(() => {
if (frontmatter.value.lastUpdated === false || theme.value.lastUpdated === false)
return
datetime.value = date.value
? new Intl.DateTimeFormat(
theme.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : undefined,
theme.value.lastUpdated?.formatOptions ?? {
dateStyle: 'short',
timeStyle: 'short',
},
).format(date.value)
: ''
})
})
return {
datetime,
isoDatetime,
lastUpdatedText,
}
}
export function useContributors(): ComputedRef<
null | Required<PlumeThemePageData['git']>['contributors']
> {
const { theme, page, frontmatter } = useData()
return computed(() => {
const showContributors
= frontmatter.value.contributors ?? theme.value.contributors ?? true
if (!showContributors)
return null
return page.value.git?.contributors ?? null
})
}
/**
* Resolve `prev` or `next` config from frontmatter
*/
function resolveFromFrontmatterConfig(conf: unknown): null | false | NavItemWithLink {
if (conf === false)
return null
if (isString(conf))
return resolveNavLink(conf)
if (isPlainObject<NavItemWithLink>(conf))
return conf
return false
}
function flatSidebar(sidebar: NotesSidebarItem[], res: NavItemWithLink[] = []): NavItemWithLink[] {
for (const item of sidebar) {
if (item.link)
res.push({ link: item.link, text: item.text || item.dir || '' })
if (Array.isArray(item.items) && item.items.length)
flatSidebar(item.items as NotesSidebarItem[], res)
}
return res
}
/**
* Resolve `prev` or `next` config from sidebar items
*/
function resolveFromSidebarItems(sidebarItems: NavItemWithLink[], currentPath: string, offset: number): null | NavItemWithLink {
const index = sidebarItems.findIndex(item => resolveRouteFullPath(item.link) === currentPath)
if (index !== -1) {
const targetItem = sidebarItems[index + offset]
if (targetItem?.link) {
return {
link: targetItem.link,
text: targetItem.text,
}
}
}
return null
}
function resolveFromBlogPostData(postList: PlumeThemeBlogPostItem[], currentPath: string, offset: number): null | NavItemWithLink {
const index = postList.findIndex(item => item.path === currentPath)
if (index !== -1) {
const targetItem = postList[index + offset]
if (!targetItem?.path)
return null
return {
link: targetItem.path,
text: targetItem.title,
}
}
return null
}
export function usePageNav() {
const route = useRoute()
const page = usePageData<PlumeThemePageData>()
const frontmatter = usePageFrontmatter<PlumeThemePageFrontmatter>()
const { sidebar } = useSidebar()
const postList = useBlogPostData() as unknown as Ref<PlumeThemeBlogPostItem[]>
const locale = usePageLang()
const prevNavList = computed(() => {
const prevConfig = resolveFromFrontmatterConfig(frontmatter.value.prev)
if (prevConfig !== false)
return prevConfig
if (page.value.isBlogPost) {
return resolveFromBlogPostData(
postList.value.filter(item => item.lang === locale.value),
route.path,
-1,
)
}
else {
return resolveFromSidebarItems(flatSidebar(sidebar.value), route.path, -1)
}
})
const nextNavList = computed(() => {
const nextConfig = resolveFromFrontmatterConfig(frontmatter.value.next)
if (nextConfig !== false)
return nextConfig
if (page.value.isBlogPost) {
return resolveFromBlogPostData(
postList.value.filter(item => item.lang === locale.value),
route.path,
1,
)
}
else {
return resolveFromSidebarItems(flatSidebar(sidebar.value), route.path, 1)
}
})
return {
prev: prevNavList,
next: nextNavList,
}
}

View File

@ -0,0 +1,118 @@
import { resolveRouteFullPath, usePageLang, useRoute } from 'vuepress/client'
import { isPlainObject, isString } from 'vuepress/shared'
import { useBlogPostData } from '@vuepress-plume/plugin-blog-data/client'
import type { NotesSidebarItem } from '@vuepress-plume/plugin-notes-data'
import { computed } from 'vue'
import type { Ref } from 'vue'
import type { NavItemWithLink, PlumeThemeBlogPostItem } from '../../shared/index.js'
import { resolveNavLink } from '../utils/index.js'
import { useSidebar } from './sidebar.js'
import { useData } from './data.js'
export function usePrevNext() {
const route = useRoute()
const { page, frontmatter } = useData()
const { sidebar } = useSidebar()
const postList = useBlogPostData() as unknown as Ref<PlumeThemeBlogPostItem[]>
const locale = usePageLang()
const prevNavList = computed(() => {
const prevConfig = resolveFromFrontmatterConfig(frontmatter.value.prev)
if (prevConfig !== false)
return prevConfig
if (page.value.isBlogPost) {
return resolveFromBlogPostData(
postList.value.filter(item => item.lang === locale.value),
route.path,
-1,
)
}
else {
return resolveFromSidebarItems(flatSidebar(sidebar.value), route.path, -1)
}
})
const nextNavList = computed(() => {
const nextConfig = resolveFromFrontmatterConfig(frontmatter.value.next)
if (nextConfig !== false)
return nextConfig
if (page.value.isBlogPost) {
return resolveFromBlogPostData(
postList.value.filter(item => item.lang === locale.value),
route.path,
1,
)
}
else {
return resolveFromSidebarItems(flatSidebar(sidebar.value), route.path, 1)
}
})
return {
prev: prevNavList,
next: nextNavList,
}
}
/**
* Resolve `prev` or `next` config from frontmatter
*/
function resolveFromFrontmatterConfig(conf: unknown): null | false | NavItemWithLink {
if (conf === false)
return null
if (isString(conf))
return resolveNavLink(conf)
if (isPlainObject<NavItemWithLink>(conf))
return conf
return false
}
function flatSidebar(sidebar: NotesSidebarItem[], res: NavItemWithLink[] = []): NavItemWithLink[] {
for (const item of sidebar) {
if (item.link)
res.push({ link: item.link, text: item.text || item.dir || '' })
if (Array.isArray(item.items) && item.items.length)
flatSidebar(item.items as NotesSidebarItem[], res)
}
return res
}
/**
* Resolve `prev` or `next` config from sidebar items
*/
function resolveFromSidebarItems(sidebarItems: NavItemWithLink[], currentPath: string, offset: number): null | NavItemWithLink {
const index = sidebarItems.findIndex(item => resolveRouteFullPath(item.link) === currentPath)
if (index !== -1) {
const targetItem = sidebarItems[index + offset]
if (targetItem?.link) {
return {
link: targetItem.link,
text: targetItem.text,
}
}
}
return null
}
function resolveFromBlogPostData(postList: PlumeThemeBlogPostItem[], currentPath: string, offset: number): null | NavItemWithLink {
const index = postList.findIndex(item => item.path === currentPath)
if (index !== -1) {
const targetItem = postList[index + offset]
if (!targetItem?.path)
return null
return {
link: targetItem.path,
text: targetItem.title,
}
}
return null
}

View File

@ -1,14 +1,13 @@
import { defineWatermarkConfig } from '@vuepress/plugin-watermark/client'
import { computed } from 'vue'
import { usePageFrontmatter } from 'vuepress/client'
import type { PlumeThemePageFrontmatter } from '../../shared/index.js'
import { useData } from './data.js'
declare const __PLUME_WM_FP__: boolean
const FP = __PLUME_WM_FP__
export function setupWatermark(): void {
const frontmatter = usePageFrontmatter<PlumeThemePageFrontmatter>()
const { frontmatter } = useData()
defineWatermarkConfig(computed(() => {
const disableFullPage = typeof frontmatter.value.watermark === 'object' && frontmatter.value.watermark.fullPage === false