feat(docs):Added dev logs and improved navigation (#713)

This commit is contained in:
Lovehsigure_520
2025-11-03 00:42:34 +08:00
committed by GitHub
parent 38eda5f330
commit 2ef450dd48
129 changed files with 3156 additions and 613 deletions
+130 -12
View File
@@ -1,17 +1,130 @@
import { useData, withBase } from 'vitepress'
import { computed } from 'vue'
import { data as blogPosts } from '../functions/blog.data'
import { getFlatSideBarLinks, getSidebar, isActive } from './sidebar'
/**
* Compute previous/next navigation targets for the current page.
* - For blog pages, keeps navigation within the same language blog directory.
* - For docs pages, falls back to sidebar-based navigation.
* - Respects frontmatter overrides and hides when disabled.
*/
export function usePrevNext() {
const { page, theme, frontmatter } = useData()
const { page, theme, frontmatter, lang } = useData()
return computed(() => {
// Blog-specific navigation: ensure next/prev stay within same language blog directory
// This handles the case where clicking the next button on a blog post should navigate
// to the next post within `/zh-Hans/blog/` or `/en/blog/`, preserving language prefix.
const isBlogPage = page.value.relativePath.includes('/blog/')
// Determine visibility from theme/frontmatter first
const hidePrev
= (theme.value.docFooter?.prev === false && !frontmatter.value.prev)
|| frontmatter.value.prev === false
const hideNext
= (theme.value.docFooter?.next === false && !frontmatter.value.next)
|| frontmatter.value.next === false
if (isBlogPage) {
// Filter posts by current language and exclude blog index page
const sameLangPosts = blogPosts
.filter(p => p.lang === (lang.value || 'en'))
.filter(p => p.urlWithoutLang !== '/blog/')
// Find current post index by matching normalized URLs
let currentPath = page.value.relativePath
if (currentPath.startsWith('/')) {
currentPath = currentPath.slice(1)
}
const currentUrl = withBase(`/${currentPath}`)
const currentIndex = sameLangPosts.findIndex(p => isActive(currentUrl, withBase(p.url)))
// Gracefully handle not found index
const prevPost = currentIndex > 0 ? sameLangPosts[currentIndex - 1] : undefined
const nextPost = currentIndex >= 0 && currentIndex < sameLangPosts.length - 1 ? sameLangPosts[currentIndex + 1] : undefined
// For blog pages, do NOT render next when it's the last article,
// even if frontmatter provides a manual next link.
const blogPrev = hidePrev
? undefined
: prevPost
? {
text:
(typeof frontmatter.value.prev === 'string'
? frontmatter.value.prev
: typeof frontmatter.value.prev === 'object'
? frontmatter.value.prev.text
: undefined)
?? prevPost.title,
link: withBase(prevPost.url),
}
: undefined
const blogNext = hideNext
? undefined
: nextPost
? {
text:
(typeof frontmatter.value.next === 'string'
? frontmatter.value.next
: typeof frontmatter.value.next === 'object'
? frontmatter.value.next.text
: undefined)
?? nextPost.title,
link: withBase(nextPost.url),
}
: undefined
return {
prev: blogPrev,
next: blogNext,
} as {
prev?: { text?: string, link?: string }
next?: { text?: string, link?: string }
}
}
// Default docs navigation via sidebar for non-blog pages
const sidebar = getSidebar(theme.value.sidebar, page.value.relativePath)
const links = getFlatSideBarLinks(sidebar)
// ignore inner-page links with hashes
const candidates = uniqBy(links, link => link.link.replace(/[?#].*$/, ''))
let candidates = uniqBy(links, link => link.link.replace(/[?#].*$/, ''))
// Restrict docs navigation within the same docs section (e.g., overview vs manual)
// This prevents crossing into unrelated sections like `/zh-Hans/docs/manual/`.
let normalizedPath = page.value.relativePath
if (normalizedPath.startsWith('/')) {
normalizedPath = normalizedPath.slice(1)
}
const currentFullUrl = withBase(`/${normalizedPath}`)
const sectionPrefix = getDocsSectionPrefix(currentFullUrl)
if (sectionPrefix) {
const sectionBase = withBase(sectionPrefix)
// If current page is the section root (e.g., /zh-Hans/docs/overview/),
// do not render prev/next for docs to avoid showing a next button here.
const isSectionRoot = currentFullUrl.replace(/[?#].*$/, '') === sectionBase
if (isSectionRoot) {
return {
prev: undefined,
next: undefined,
} as { prev?: { text?: string, link?: string }, next?: { text?: string, link?: string } }
}
// Keep navigation within the same docs section and exclude the section root itself
// to avoid showing a "next" link that points back to the section index.
const filtered = candidates
.filter(l => l.link.replace(/[?#].*$/, '').startsWith(sectionBase))
.filter(l => l.link.replace(/[?#].*$/, '') !== sectionBase)
// Fallback to all candidates if filter would drop the current page
const wouldDropCurrent = filtered.findIndex(l => isActive(currentFullUrl, l.link)) < 0
if (!wouldDropCurrent) {
candidates = filtered
}
}
const index = candidates.findIndex((link) => {
let path = page.value.relativePath
@@ -22,16 +135,8 @@ export function usePrevNext() {
return isActive(withBase(`/${path}`), link.link)
})
const hidePrev
= (theme.value.docFooter?.prev === false && !frontmatter.value.prev)
|| frontmatter.value.prev === false
const hideNext
= (theme.value.docFooter?.next === false && !frontmatter.value.next)
|| frontmatter.value.next === false
return {
prev: hidePrev
prev: hidePrev || index <= 0
? undefined
: {
text:
@@ -47,7 +152,7 @@ export function usePrevNext() {
? frontmatter.value.prev.link
: undefined) ?? candidates[index - 1]?.link,
},
next: hideNext
next: hideNext || index < 0 || index >= candidates.length - 1
? undefined
: {
text:
@@ -70,6 +175,19 @@ export function usePrevNext() {
})
}
/**
* Extract docs section prefix like `/<lang>/docs/<section>/` from a full URL.
* Keeps navigation within the same section to avoid crossing into unrelated docs.
*/
function getDocsSectionPrefix(fullUrl: string): string | undefined {
const m = fullUrl.match(/\/(en|zh-Hans)\/docs\/([^/]+)\//)
return m ? `/${m[1]}/docs/${m[2]}/` : undefined
}
/**
* Returns a unique array by key function result.
* Used to remove inner-page duplicate links based on URL normalization.
*/
function uniqBy<T>(array: T[], keyFn: (item: T) => any): T[] {
const seen = new Set()
return array.filter((item) => {