mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
docs(website): derive navigation targets from the publication manifest
The navigation bar named `/guide/` while the manifest published the guide's first page at `guide/quickstart.md`, so the item served a 404 in both locales. `landingLink` resolves each item against `orderedPages`, the ordering the sidebar already renders, and a test asserts every navigation target is a route the manifest publishes.
This commit is contained in:
@@ -5,33 +5,30 @@ import { resolve } from 'node:path'
|
||||
import type { DefaultTheme, PageData } from 'vitepress'
|
||||
import type { ViteDevServer } from 'vite'
|
||||
import { withMermaid } from 'vitepress-plugin-mermaid'
|
||||
import { docsPages, sectionSpec, type DocsLocale, type DocsPage } from '../docs.ts'
|
||||
import { landingLink, orderedPages, routeLink, sectionSpec, type DocsLocale, type DocsPage } from '../docs.ts'
|
||||
import { docsSourceFiles, projectDocs } from '../../scripts/project-doc-site.ts'
|
||||
|
||||
projectDocs()
|
||||
|
||||
function sidebar(locale: DocsLocale, collection: DocsPage['sidebar']): DefaultTheme.SidebarItem[] {
|
||||
const pages = docsPages.filter(page => page.locale === locale && page.sidebar === collection)
|
||||
function sidebar(locale: DocsLocale, collection: NonNullable<DocsPage['sidebar']>): DefaultTheme.SidebarItem[] {
|
||||
// `orderedPages` already sorts by section placement, so insertion order
|
||||
// carries the group order and each group keeps its pages in sequence.
|
||||
const groups = new Map<string, DocsPage[]>()
|
||||
for (const page of pages) {
|
||||
for (const page of orderedPages(locale, collection)) {
|
||||
const entries = groups.get(page.section) ?? []
|
||||
entries.push(page)
|
||||
groups.set(page.section, entries)
|
||||
}
|
||||
return [...groups.entries()]
|
||||
.sort(([left], [right]) => sectionSpec(locale, left).index - sectionSpec(locale, right).index)
|
||||
.map(([text, entries]) => {
|
||||
const { collapsed } = sectionSpec(locale, text)
|
||||
return {
|
||||
text,
|
||||
// A present `collapsed` is what makes the default theme render the
|
||||
// group as collapsible at all, so an open group must omit the key.
|
||||
...(collapsed === undefined ? {} : { collapsed }),
|
||||
items: entries
|
||||
.sort((left, right) => left.order - right.order)
|
||||
.map(page => ({ text: page.label, link: `/${page.route.replace(/(?:index)?\.md$/, '')}` })),
|
||||
}
|
||||
})
|
||||
return [...groups.entries()].map(([text, entries]) => {
|
||||
const { collapsed } = sectionSpec(locale, text)
|
||||
return {
|
||||
text,
|
||||
// A present `collapsed` is what makes the default theme render the
|
||||
// group as collapsible at all, so an open group must omit the key.
|
||||
...(collapsed === undefined ? {} : { collapsed }),
|
||||
items: entries.map(page => ({ text: page.label, link: routeLink(page.route) })),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function watchCanonicalDocs(server: ViteDevServer): void {
|
||||
@@ -199,9 +196,9 @@ export default withMermaid({
|
||||
themeConfig: {
|
||||
siteTitle: siteTitle('技术预览'),
|
||||
nav: [
|
||||
{ text: '入门', link: '/guide/', activeMatch: '^/guide/' },
|
||||
{ text: '开发', link: '/develop/basic/', activeMatch: '^/develop/' },
|
||||
{ text: '参考', link: '/reference/', activeMatch: '^/reference/' },
|
||||
{ text: '入门', link: landingLink('root', 'zh-guide'), activeMatch: '^/guide/' },
|
||||
{ text: '开发', link: landingLink('root', 'zh-develop'), activeMatch: '^/develop/' },
|
||||
{ text: '参考', link: landingLink('root', 'zh-reference'), activeMatch: '^/reference/' },
|
||||
],
|
||||
sidebar: {
|
||||
'/guide/': sidebar('root', 'zh-guide'),
|
||||
@@ -226,9 +223,9 @@ export default withMermaid({
|
||||
themeConfig: {
|
||||
siteTitle: siteTitle('Preview'),
|
||||
nav: [
|
||||
{ text: 'Guide', link: '/en/guide/', activeMatch: '^/en/guide/' },
|
||||
{ text: 'Develop', link: '/en/develop/basic/', activeMatch: '^/en/develop/' },
|
||||
{ text: 'Reference', link: '/en/reference/', activeMatch: '^/en/reference/' },
|
||||
{ text: 'Guide', link: landingLink('en', 'en-guide'), activeMatch: '^/en/guide/' },
|
||||
{ text: 'Develop', link: landingLink('en', 'en-develop'), activeMatch: '^/en/develop/' },
|
||||
{ text: 'Reference', link: landingLink('en', 'en-reference'), activeMatch: '^/en/reference/' },
|
||||
],
|
||||
sidebar: {
|
||||
'/en/guide/': sidebar('en', 'en-guide'),
|
||||
|
||||
+45
-1
@@ -11,7 +11,7 @@
|
||||
export type DocsLocale = 'root' | 'en'
|
||||
|
||||
/** Sidebar collection rendered for one locale and top-level module. */
|
||||
type DocsSidebar =
|
||||
export type DocsSidebar =
|
||||
| 'zh-guide'
|
||||
| 'zh-develop'
|
||||
| 'zh-reference'
|
||||
@@ -478,3 +478,47 @@ export const docsPages: DocsPage[] = [
|
||||
...subsystemsReference,
|
||||
...reference,
|
||||
]
|
||||
|
||||
/**
|
||||
* Pages of one sidebar collection, in the order the sidebar lists them.
|
||||
*
|
||||
* @param locale - Route tree whose sidebar is being built.
|
||||
* @param collection - Sidebar collection to read.
|
||||
* @returns The collection's pages, ordered by section placement then by `order`.
|
||||
*/
|
||||
export function orderedPages(locale: DocsLocale, collection: DocsSidebar): DocsPage[] {
|
||||
return docsPages
|
||||
.filter(page => page.locale === locale && page.sidebar === collection)
|
||||
.sort((left, right) => (
|
||||
sectionSpec(locale, left.section).index - sectionSpec(locale, right.section).index
|
||||
|| left.order - right.order
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* Site-relative link for a published route.
|
||||
*
|
||||
* @param route - Manifest route, including its `.md` suffix.
|
||||
* @returns The link VitePress serves the route at.
|
||||
*/
|
||||
export function routeLink(route: string): string {
|
||||
return `/${route.replace(/(?:index)?\.md$/, '')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Where a top-level navigation item lands.
|
||||
*
|
||||
* The target is derived rather than written down: a collection whose first page
|
||||
* is renamed or reordered would otherwise leave the navigation bar pointing at
|
||||
* a route the manifest no longer publishes.
|
||||
*
|
||||
* @param locale - Route tree the navigation item belongs to.
|
||||
* @param collection - Sidebar collection the item opens.
|
||||
* @returns Site-relative link of the collection's first page.
|
||||
* @throws When the collection publishes no page.
|
||||
*/
|
||||
export function landingLink(locale: DocsLocale, collection: DocsSidebar): string {
|
||||
const first = orderedPages(locale, collection)[0]
|
||||
if (first === undefined) throw new Error(`Sidebar collection "${collection}" publishes no page.`)
|
||||
return routeLink(first.route)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user