1
- import type { EntryType } from "@/collections"
1
+ import type { EntryType , frontmatterSchema } from "@/collections"
2
2
import type { Metadata } from "next"
3
+ import type { z } from "zod"
4
+ import { cache } from "react"
3
5
import { notFound } from "next/navigation"
4
- import { CollectionInfo , getFileContent , getSections } from "@/collections"
6
+ import {
7
+ CollectionInfo ,
8
+ getFileContent ,
9
+ getSections ,
10
+ getTitle ,
11
+ } from "@/collections"
5
12
import { SiteBreadcrumb } from "@/components/breadcrumb"
6
13
import { Comments } from "@/components/comments"
7
14
import SectionGrid from "@/components/section-grid"
@@ -36,47 +43,42 @@ interface PageProps {
36
43
params : Promise < { slug : string [ ] } >
37
44
}
38
45
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 ) ,
45
52
)
46
53
47
54
const items = [ ]
48
55
49
56
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
51
60
try {
52
61
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" )
54
69
} catch ( e : unknown ) {
55
70
continue
56
71
}
57
72
58
- if ( isDirectory ( collection ) ) {
73
+ if ( ! frontmatter ) {
59
74
items . push ( {
60
75
title : collection . getTitle ( ) ,
61
76
path : [ "docs" , ...collection . getPathSegments ( ) ] ,
62
77
} )
63
78
} 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 )
78
80
items . push ( {
79
- title : frontmatter . navTitle ?? parentTitle ?? collection . getTitle ( ) ,
81
+ title,
80
82
path : [
81
83
"docs" ,
82
84
...removeFromArray ( collection . getPathSegments ( ) , [ "index" ] ) ,
@@ -86,17 +88,13 @@ async function getBreadcrumbItems(slug: string[]) {
86
88
}
87
89
88
90
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
+ } )
96
92
97
93
export async function generateMetadata ( props : PageProps ) : Promise < Metadata > {
98
94
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 )
100
98
101
99
return {
102
100
title : titles . join ( " - " ) ,
@@ -122,13 +120,21 @@ export default async function DocsPage(props: PageProps) {
122
120
// if we can't find an index file, but we have a valid directory
123
121
// use the directory component for rendering
124
122
if ( ! file && isDirectory ( collection ) ) {
125
- return < DirectoryContent source = { collection } />
123
+ return (
124
+ < >
125
+ < DirectoryContent source = { collection } />
126
+ </ >
127
+ )
126
128
}
127
129
128
130
// if we have a valid file ( including the index file )
129
131
// use the file component for rendering
130
132
if ( file ) {
131
- return < FileContent source = { collection } />
133
+ return (
134
+ < >
135
+ < FileContent source = { collection } />
136
+ </ >
137
+ )
132
138
}
133
139
134
140
// seems to be an invalid path
@@ -143,8 +149,8 @@ async function DirectoryContent({ source }: { source: EntryType }) {
143
149
return (
144
150
< >
145
151
< 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" >
148
154
< SiteBreadcrumb items = { breadcrumbItems } />
149
155
150
156
< article data-pagefind-body >
@@ -156,6 +162,7 @@ async function DirectoryContent({ source }: { source: EntryType }) {
156
162
"prose-code:before:hidden prose-code:after:hidden" ,
157
163
// use full width
158
164
"w-full max-w-full" ,
165
+ "prose-a:text-indigo-400 prose-a:hover:text-white" ,
159
166
) }
160
167
>
161
168
< h1
@@ -197,24 +204,34 @@ async function FileContent({ source }: { source: EntryType }) {
197
204
return (
198
205
< >
199
206
< div className = "container py-6" >
200
- { headings . length > 0 && < MobileTableOfContents toc = { headings } /> }
207
+ { headings . length > 0 && frontmatter . showToc && (
208
+ < MobileTableOfContents toc = { headings } />
209
+ ) }
201
210
202
211
< 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 ,
205
217
} ) }
206
218
>
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
+ >
208
225
< SiteBreadcrumb items = { breadcrumbItems } />
209
226
210
227
< div data-pagefind-body >
211
228
< 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"
213
230
data-pagefind-meta = "title"
214
231
>
215
232
{ frontmatter . title ?? source . getTitle ( ) }
216
233
</ 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" >
218
235
{ frontmatter . description ?? "" }
219
236
</ p >
220
237
< article >
@@ -256,27 +273,29 @@ async function FileContent({ source }: { source: EntryType }) {
256
273
< Comments />
257
274
</ div >
258
275
</ 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 >
276
289
</ 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 >
278
297
</ div >
279
- </ div >
298
+ ) : null }
280
299
</ div >
281
300
</ div >
282
301
</ >
0 commit comments