1- import type { EntryType } from "@/collections"
1+ import type { EntryType , frontmatterSchema } from "@/collections"
22import type { Metadata } from "next"
3+ import type { z } from "zod"
4+ import { cache } from "react"
35import { notFound } from "next/navigation"
4- import { CollectionInfo , getFileContent , getSections } from "@/collections"
6+ import {
7+ CollectionInfo ,
8+ getFileContent ,
9+ getSections ,
10+ getTitle ,
11+ } from "@/collections"
512import { SiteBreadcrumb } from "@/components/breadcrumb"
613import { Comments } from "@/components/comments"
714import SectionGrid from "@/components/section-grid"
@@ -36,47 +43,42 @@ interface PageProps {
3643 params : Promise < { slug : string [ ] } >
3744}
3845
39- async function getBreadcrumbItems ( slug : string [ ] ) {
40- // we do not want to have "docs " as breadcrumb element
41- // also, we do not need the index file in our breadcrumb
42- const combinations = removeFromArray ( slug , [ "docs" , "index" ] ) . reduce (
43- ( acc : string [ ] [ ] , curr ) => acc . concat ( acc . map ( ( sub ) => [ ... sub , curr ] ) ) ,
44- [ [ ] ] ,
46+ const getBreadcrumbItems = cache ( async ( slug : string [ ] ) => {
47+ // we do not want to have "index " as breadcrumb element
48+ const cleanedSlug = removeFromArray ( slug , [ " index" ] )
49+
50+ const combinations = cleanedSlug . map ( ( _ , index ) =>
51+ cleanedSlug . slice ( 0 , index + 1 ) ,
4552 )
4653
4754 const items = [ ]
4855
4956 for ( const currentPageSegement of combinations ) {
50- let collection
57+ let collection : EntryType
58+ let file : Awaited < ReturnType < typeof getFileContent > >
59+ let frontmatter : z . infer < typeof frontmatterSchema > | undefined
5160 try {
5261 collection = await CollectionInfo . getEntry ( currentPageSegement )
53- // eslint-disable-next-line @typescript-eslint/no-unused-vars
62+ if ( collection . getPathSegments ( ) . includes ( "index" ) ) {
63+ file = await getFileContent ( collection . getParent ( ) )
64+ } else {
65+ file = await getFileContent ( collection )
66+ }
67+
68+ frontmatter = await file ?. getExportValue ( "frontmatter" )
5469 } catch ( e : unknown ) {
5570 continue
5671 }
5772
58- if ( isDirectory ( collection ) ) {
73+ if ( ! frontmatter ) {
5974 items . push ( {
6075 title : collection . getTitle ( ) ,
6176 path : [ "docs" , ...collection . getPathSegments ( ) ] ,
6277 } )
6378 } else {
64- const file = await getFileContent ( collection )
65-
66- if ( ! file ) {
67- continue
68- }
69- const frontmatter = await file . getExportValue ( "frontmatter" )
70-
71- // in case we have an index file inside a directory
72- // we have also to fetch the directory name, otherwise we get "Index" as title
73- // if there is no `frontmatter.navTitle` defined
74- const parentTitle = collection . getPathSegments ( ) . includes ( "index" )
75- ? collection . getParent ( ) . getTitle ( )
76- : null
77-
79+ const title = getTitle ( collection , frontmatter , true )
7880 items . push ( {
79- title : frontmatter . navTitle ?? parentTitle ?? collection . getTitle ( ) ,
81+ title,
8082 path : [
8183 "docs" ,
8284 ...removeFromArray ( collection . getPathSegments ( ) , [ "index" ] ) ,
@@ -86,17 +88,13 @@ async function getBreadcrumbItems(slug: string[]) {
8688 }
8789
8890 return items
89- }
90-
91- async function getParentTitle ( slug : string [ ] ) {
92- const elements = await getBreadcrumbItems ( slug )
93-
94- return elements . map ( ( ele ) => ele . title )
95- }
91+ } )
9692
9793export async function generateMetadata ( props : PageProps ) : Promise < Metadata > {
9894 const params = await props . params
99- const titles = await getParentTitle ( params . slug )
95+ const breadcrumbItems = await getBreadcrumbItems ( params . slug )
96+
97+ const titles = breadcrumbItems . map ( ( ele ) => ele . title )
10098
10199 return {
102100 title : titles . join ( " - " ) ,
@@ -122,13 +120,21 @@ export default async function DocsPage(props: PageProps) {
122120 // if we can't find an index file, but we have a valid directory
123121 // use the directory component for rendering
124122 if ( ! file && isDirectory ( collection ) ) {
125- return < DirectoryContent source = { collection } />
123+ return (
124+ < >
125+ < DirectoryContent source = { collection } />
126+ </ >
127+ )
126128 }
127129
128130 // if we have a valid file ( including the index file )
129131 // use the file component for rendering
130132 if ( file ) {
131- return < FileContent source = { collection } />
133+ return (
134+ < >
135+ < FileContent source = { collection } />
136+ </ >
137+ )
132138 }
133139
134140 // seems to be an invalid path
@@ -143,8 +149,8 @@ async function DirectoryContent({ source }: { source: EntryType }) {
143149 return (
144150 < >
145151 < div className = "container py-6" >
146- < div className = { cn ( "flex flex-col gap-y-8 " ) } >
147- < div >
152+ < div className = { cn ( "gap-8 xl:grid " ) } >
153+ < div className = "mx-auto w-full 2xl:w-6xl" >
148154 < SiteBreadcrumb items = { breadcrumbItems } />
149155
150156 < article data-pagefind-body >
@@ -156,6 +162,7 @@ async function DirectoryContent({ source }: { source: EntryType }) {
156162 "prose-code:before:hidden prose-code:after:hidden" ,
157163 // use full width
158164 "w-full max-w-full" ,
165+ "prose-a:text-indigo-400 prose-a:hover:text-white" ,
159166 ) }
160167 >
161168 < h1
@@ -197,24 +204,34 @@ async function FileContent({ source }: { source: EntryType }) {
197204 return (
198205 < >
199206 < div className = "container py-6" >
200- { headings . length > 0 && < MobileTableOfContents toc = { headings } /> }
207+ { headings . length > 0 && frontmatter . showToc && (
208+ < MobileTableOfContents toc = { headings } />
209+ ) }
201210
202211 < div
203- className = { cn ( "gap-8 xl:grid xl:grid-cols-[1fr_300px]" , {
204- "mt-12 xl:mt-0" : headings . length > 0 ,
212+ className = { cn ( "gap-8 xl:grid" , {
213+ "mt-12 xl:mt-0" : frontmatter . showToc && headings . length > 0 ,
214+ "xl:grid-cols-[1fr_300px]" :
215+ frontmatter . showToc && headings . length > 0 ,
216+ "xl:grid-cols-1" : ! frontmatter . showToc || headings . length == 0 ,
205217 } ) }
206218 >
207- < div >
219+ < div
220+ className = { cn ( "mx-auto" , {
221+ "w-full 2xl:w-6xl" : ! frontmatter . showToc || headings . length == 0 ,
222+ "w-full 2xl:w-4xl" : frontmatter . showToc && headings . length > 0 ,
223+ } ) }
224+ >
208225 < SiteBreadcrumb items = { breadcrumbItems } />
209226
210227 < div data-pagefind-body >
211228 < h1
212- className = "no-prose mb-2 scroll-m-20 text-4xl font-light tracking-tight lg :text-5xl"
229+ className = "no-prose mb-2 scroll-m-20 text-3xl font-light tracking-tight sm:text-4xl md :text-5xl"
213230 data-pagefind-meta = "title"
214231 >
215232 { frontmatter . title ?? source . getTitle ( ) }
216233 </ h1 >
217- < p className = "mb-8 text-lg font-medium text-pretty text-gray-500 sm:text-xl/8" >
234+ < p className = "text-muted-foreground mb-8 text-lg font-medium text-pretty sm:text-xl/8" >
218235 { frontmatter . description ?? "" }
219236 </ p >
220237 < article >
@@ -256,27 +273,29 @@ async function FileContent({ source }: { source: EntryType }) {
256273 < Comments />
257274 </ div >
258275 </ div >
259- < div className = "hidden w-[19.5rem] xl:sticky xl:top-[4.75rem] xl:-mr-6 xl:block xl:h-[calc(100vh-4.75rem)] xl:flex-none xl:overflow-y-auto xl:pr-6 xl:pb-16" >
260- < TableOfContents toc = { headings } />
261-
262- < div className = "my-6 grid gap-y-4 border-t pt-6" >
263- < div >
264- < a
265- href = { file . getEditUrl ( ) }
266- target = "_blank"
267- className = "text-muted-foreground hover:text-foreground flex items-center text-sm no-underline transition-colors"
268- >
269- Edit this page < ExternalLinkIcon className = "ml-2 h-4 w-4" />
270- </ a >
271- </ div >
272-
273- { lastUpdate && (
274- < div className = "text-muted-foreground text-sm" >
275- Last updated: { format ( lastUpdate , "dd.MM.yyyy" ) }
276+ { frontmatter . showToc && headings . length > 0 ? (
277+ < div className = "hidden w-[19.5rem] xl:sticky xl:top-[4.75rem] xl:-mr-6 xl:block xl:h-[calc(100vh-4.75rem)] xl:flex-none xl:overflow-y-auto xl:pr-6 xl:pb-16" >
278+ < TableOfContents toc = { headings } />
279+
280+ < div className = "my-6 grid gap-y-4 border-t pt-6" >
281+ < div >
282+ < a
283+ href = { file . getEditUrl ( ) }
284+ target = "_blank"
285+ className = "text-muted-foreground hover:text-foreground flex items-center text-sm no-underline transition-colors"
286+ >
287+ Edit this page < ExternalLinkIcon className = "ml-2 h-4 w-4" />
288+ </ a >
276289 </ div >
277- ) }
290+
291+ { lastUpdate && (
292+ < div className = "text-muted-foreground text-sm" >
293+ Last updated: { format ( lastUpdate , "dd.MM.yyyy" ) }
294+ </ div >
295+ ) }
296+ </ div >
278297 </ div >
279- </ div >
298+ ) : null }
280299 </ div >
281300 </ div >
282301 </ >
0 commit comments