Catch-all route at app/(app)/manual/[[...slug]]/page.tsx with generateStaticParams covering every TOC entry. Server-side MarkdownView uses react-markdown with remark-gfm, rehype-slug, and rehype-autolink-headings; mermaid code blocks are routed to a client-only MermaidBlock that dynamic-imports mermaid on mount. ManualSidebar (client) reads the typed TOC and highlights the active chapter via usePathname. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
42 lines
1.1 KiB
TypeScript
42 lines
1.1 KiB
TypeScript
import type { Metadata } from 'next'
|
|
import { notFound } from 'next/navigation'
|
|
import { getManualChapter, getManualToc } from '@/lib/manual-server'
|
|
import { MarkdownView } from '../_components/markdown-view'
|
|
|
|
type Params = { slug?: string[] }
|
|
|
|
export async function generateStaticParams(): Promise<Params[]> {
|
|
return getManualToc().map((entry) => ({
|
|
slug: entry.slug.length > 0 ? [...entry.slug] : undefined,
|
|
}))
|
|
}
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<Params>
|
|
}): Promise<Metadata> {
|
|
const { slug = [] } = await params
|
|
const chapter = getManualChapter(slug)
|
|
if (!chapter) return { title: 'Manual — not found' }
|
|
return {
|
|
title: `${chapter.entry.title} — Scrum4Me Manual`,
|
|
description: chapter.entry.description.slice(0, 200),
|
|
}
|
|
}
|
|
|
|
export default async function ManualChapterPage({
|
|
params,
|
|
}: {
|
|
params: Promise<Params>
|
|
}) {
|
|
const { slug = [] } = await params
|
|
const chapter = getManualChapter(slug)
|
|
if (!chapter) notFound()
|
|
|
|
return (
|
|
<div className="mx-auto w-full max-w-3xl px-6 py-8">
|
|
<MarkdownView markdown={chapter.body} />
|
|
</div>
|
|
)
|
|
}
|