feat(theme): add breadcrumb nav
This commit is contained in:
parent
9579152326
commit
d44ac5fcd2
@ -6,6 +6,7 @@ import VPDocAside from '@theme/VPDocAside.vue'
|
||||
import VPDocFooter from '@theme/VPDocFooter.vue'
|
||||
import VPEncryptPage from '@theme/VPEncryptPage.vue'
|
||||
import VPDocMeta from '@theme/VPDocMeta.vue'
|
||||
import VPDocBreadcrumbs from '@theme/VPDocBreadcrumbs.vue'
|
||||
import {
|
||||
useBlogPageData,
|
||||
useData,
|
||||
@ -116,6 +117,7 @@ watch(
|
||||
<div class="content-container">
|
||||
<slot name="doc-before" />
|
||||
<main class="main">
|
||||
<VPDocBreadcrumbs />
|
||||
<VPDocMeta />
|
||||
<VPEncryptPage v-if="!isPageDecrypted" />
|
||||
<Content
|
||||
|
||||
139
theme/src/client/components/VPDocBreadcrumbs.vue
Normal file
139
theme/src/client/components/VPDocBreadcrumbs.vue
Normal file
@ -0,0 +1,139 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouteLocale } from 'vuepress/client'
|
||||
import VPLink from '@theme/VPLink.vue'
|
||||
import {
|
||||
useBlogExtract,
|
||||
useBlogNavTitle,
|
||||
useBlogPageData,
|
||||
useData,
|
||||
useSidebarData,
|
||||
} from '../composables/index.js'
|
||||
import type { ResolvedSidebarItem } from '../../shared/index.js'
|
||||
|
||||
interface Breadcrumb {
|
||||
text: string
|
||||
link?: string
|
||||
current?: boolean
|
||||
}
|
||||
|
||||
const { page } = useData<'post'>()
|
||||
const routeLocale = useRouteLocale()
|
||||
const { isBlogPost } = useBlogPageData()
|
||||
const { categoriesLink, blogLink } = useBlogExtract()
|
||||
const sidebar = useSidebarData()
|
||||
|
||||
const hasBreadcrumb = computed(() => {
|
||||
if (isBlogPost.value && page.value.categoryList)
|
||||
return page.value.categoryList.length > 0
|
||||
return sidebar.value.length > 0
|
||||
})
|
||||
const homeTitle = useBlogNavTitle('home')
|
||||
const blogTile = useBlogNavTitle('blog')
|
||||
|
||||
const breadcrumbList = computed<Breadcrumb[]>(() => {
|
||||
if (!hasBreadcrumb.value)
|
||||
return []
|
||||
const list: Breadcrumb[] = [{ text: homeTitle.value, link: routeLocale.value }]
|
||||
|
||||
if (isBlogPost.value) {
|
||||
list.push({ text: blogTile.value, link: blogLink.value })
|
||||
const categoryList = page.value.categoryList ?? []
|
||||
for (const category of categoryList) {
|
||||
list.push({
|
||||
text: category.name,
|
||||
link: `${categoriesLink.value}?id=${category.id}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
else if (sidebar.value.length > 0) {
|
||||
list.push(...(resolveSidebar(sidebar.value) || []))
|
||||
}
|
||||
list.push({ text: page.value.title, link: page.value.path, current: true })
|
||||
return list
|
||||
})
|
||||
|
||||
function resolveSidebar(
|
||||
sidebar: ResolvedSidebarItem[],
|
||||
result: Breadcrumb[] = [],
|
||||
): Breadcrumb[] | null {
|
||||
for (const item of sidebar) {
|
||||
if (item.link === page.value.path) {
|
||||
return result
|
||||
}
|
||||
else if (item.items) {
|
||||
const res = resolveSidebar(
|
||||
item.items,
|
||||
[...result, { text: item.text!, link: item.link }],
|
||||
)
|
||||
if (res)
|
||||
return res
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav
|
||||
v-if="hasBreadcrumb"
|
||||
class="vp-breadcrumb"
|
||||
>
|
||||
<ol vocab="https://schema.org/" typeof="BreadcrumbList">
|
||||
<li
|
||||
v-for="({ text, link, current }, index) in breadcrumbList"
|
||||
:key="link"
|
||||
property="itemListElement"
|
||||
typeof="ListItem"
|
||||
>
|
||||
<VPLink :href="link" class="breadcrumb" :class="{ current }" property="item" typeof="WebPage">
|
||||
{{ text }}
|
||||
</VPLink>
|
||||
<span v-if="index !== breadcrumbList.length - 1" class="vpi-chevron-right" />
|
||||
<meta property="position" :content="index + 1">
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-breadcrumb {
|
||||
padding-left: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
border-left: solid 4px var(--vp-c-brand-1);
|
||||
transition: border-left var(--t-color);
|
||||
}
|
||||
|
||||
.vp-breadcrumb ol {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.vp-breadcrumb ol li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vp-breadcrumb .breadcrumb {
|
||||
color: var(--vp-c-brand-2);
|
||||
transition: color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-breadcrumb .breadcrumb:hover {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.vp-breadcrumb .breadcrumb.current {
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.vp-breadcrumb .vpi-chevron-right {
|
||||
margin-left: 8px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
</style>
|
||||
@ -1,19 +1,11 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue'
|
||||
import { useReadingTimeLocale } from '@vuepress/plugin-reading-time/client'
|
||||
import VPLink from '@theme/VPLink.vue'
|
||||
import {
|
||||
useBlogExtract,
|
||||
useBlogPageData,
|
||||
useData,
|
||||
useTagColors,
|
||||
} from '../composables/index.js'
|
||||
import { useData, useTagColors } from '../composables/index.js'
|
||||
|
||||
const { page, frontmatter: matter } = useData<'post'>()
|
||||
const { isBlogPost } = useBlogPageData()
|
||||
const colors = useTagColors()
|
||||
const readingTime = useReadingTimeLocale()
|
||||
const { categories } = useBlogExtract()
|
||||
|
||||
const createTime = computed(() => {
|
||||
if (matter.value.createTime)
|
||||
@ -22,10 +14,6 @@ const createTime = computed(() => {
|
||||
return ''
|
||||
})
|
||||
|
||||
const categoryList = computed(() => {
|
||||
return page.value.categoryList ?? []
|
||||
})
|
||||
|
||||
const tags = computed(() => {
|
||||
if (matter.value.tags) {
|
||||
return matter.value.tags.slice(0, 4).map(tag => ({
|
||||
@ -41,28 +29,10 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-if="isBlogPost && categoryList.length"
|
||||
class="vp-doc-category"
|
||||
>
|
||||
<template
|
||||
v-for="({ id, name }, index) in categoryList"
|
||||
:key="id"
|
||||
>
|
||||
<VPLink :href="`${categories.link}?id=${id}`" class="category">
|
||||
{{ name }}
|
||||
</VPLink>
|
||||
<span v-if="index !== categoryList.length - 1" class="dot">›</span>
|
||||
</template>
|
||||
</div>
|
||||
<h1 class="vp-doc-title page-title" :class="{ padding: !hasMeta }">
|
||||
{{ page.title }}
|
||||
</h1>
|
||||
<div v-if="hasMeta" class="vp-doc-meta">
|
||||
<!-- <p v-if="matter.author" class="author">
|
||||
<span class="icon vpi-user" />
|
||||
<span>{{ matter.author }}</span>
|
||||
</p> -->
|
||||
<p v-if="readingTime.time && matter.readingTime !== false" class="reading-time">
|
||||
<span class="vpi-books icon" />
|
||||
<span>{{ readingTime.words }}</span>
|
||||
@ -86,29 +56,6 @@ const hasMeta = computed(() => readingTime.value.time || tags.value.length || cr
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vp-doc-category {
|
||||
padding-left: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
border-left: solid 4px var(--vp-c-brand-1);
|
||||
transition: border-left var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc-category .category {
|
||||
color: var(--vp-c-text-2);
|
||||
transition: color var(--t-color);
|
||||
}
|
||||
|
||||
.vp-doc-category .category:hover {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.vp-doc-category .dot {
|
||||
margin: 0 0.2rem;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.vp-doc-title {
|
||||
margin-bottom: 0.7rem;
|
||||
font-size: 28px;
|
||||
|
||||
@ -30,7 +30,7 @@ export function useBlogExtract() {
|
||||
|| blog.value.tags !== false
|
||||
|| blog.value.categories !== false,
|
||||
)
|
||||
|
||||
const blogLink = useLocaleLink(blog.value.link || 'blog/')
|
||||
const tagsLink = useLocaleLink(blog.value.tagsLink || 'blog/tags/')
|
||||
const archiveLink = useLocaleLink(blog.value.archivesLink || 'blog/archives/')
|
||||
const categoriesLink = useLocaleLink(blog.value.categoriesLink || 'blog/categories/')
|
||||
@ -55,6 +55,10 @@ export function useBlogExtract() {
|
||||
|
||||
return {
|
||||
hasBlogExtract,
|
||||
blogLink,
|
||||
tagsLink,
|
||||
archiveLink,
|
||||
categoriesLink,
|
||||
tags,
|
||||
archives,
|
||||
categories,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user