Skip to content

Commit

Permalink
feat(theme): add breadcrumb nav
Browse files Browse the repository at this point in the history
  • Loading branch information
pengzhanbo committed Aug 29, 2024
1 parent 9579152 commit d44ac5f
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 55 deletions.
2 changes: 2 additions & 0 deletions theme/src/client/components/VPDoc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -116,6 +117,7 @@ watch(
<div class="content-container">
<slot name="doc-before" />
<main class="main">
<VPDocBreadcrumbs />
<VPDocMeta />
<VPEncryptPage v-if="!isPageDecrypted" />
<Content
Expand Down
139 changes: 139 additions & 0 deletions theme/src/client/components/VPDocBreadcrumbs.vue
Original file line number Diff line number Diff line change
@@ -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>
55 changes: 1 addition & 54 deletions theme/src/client/components/VPDocMeta.vue
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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 => ({
Expand All @@ -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">&rsaquo;</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>
Expand All @@ -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;
Expand Down
6 changes: 5 additions & 1 deletion theme/src/client/composables/blog-extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/')
Expand All @@ -55,6 +55,10 @@ export function useBlogExtract() {

return {
hasBlogExtract,
blogLink,
tagsLink,
archiveLink,
categoriesLink,
tags,
archives,
categories,
Expand Down

0 comments on commit d44ac5f

Please sign in to comment.