@raystack/chronicle 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/source.config.ts +1 -0
- package/src/app/[[...slug]]/page.tsx +61 -12
- package/src/app/apis/[[...slug]]/page.tsx +60 -0
- package/src/app/layout.tsx +32 -1
- package/src/app/og/route.tsx +62 -0
- package/src/app/robots.ts +10 -0
- package/src/app/sitemap.ts +29 -0
- package/src/lib/config.ts +1 -0
- package/src/types/config.ts +11 -0
- package/src/types/content.ts +1 -0
package/package.json
CHANGED
package/source.config.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Metadata, ResolvingMetadata } from 'next'
|
|
1
2
|
import { notFound } from 'next/navigation'
|
|
2
3
|
import type { MDXContent } from 'mdx/types'
|
|
3
4
|
import { loadConfig } from '@/lib/config'
|
|
@@ -16,6 +17,41 @@ interface PageData {
|
|
|
16
17
|
toc: { title: string; url: string; depth: number }[]
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
export async function generateMetadata(
|
|
21
|
+
{ params }: PageProps,
|
|
22
|
+
parent: ResolvingMetadata,
|
|
23
|
+
): Promise<Metadata> {
|
|
24
|
+
const { slug } = await params
|
|
25
|
+
const page = source.getPage(slug)
|
|
26
|
+
if (!page) return {}
|
|
27
|
+
const config = loadConfig()
|
|
28
|
+
const data = page.data as PageData
|
|
29
|
+
const parentMetadata = await parent
|
|
30
|
+
|
|
31
|
+
const metadata: Metadata = {
|
|
32
|
+
title: data.title,
|
|
33
|
+
description: data.description,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (config.url) {
|
|
37
|
+
const ogParams = new URLSearchParams({ title: data.title })
|
|
38
|
+
if (data.description) ogParams.set('description', data.description)
|
|
39
|
+
metadata.openGraph = {
|
|
40
|
+
...parentMetadata.openGraph,
|
|
41
|
+
title: data.title,
|
|
42
|
+
description: data.description,
|
|
43
|
+
images: [{ url: `/og?${ogParams.toString()}`, width: 1200, height: 630 }],
|
|
44
|
+
}
|
|
45
|
+
metadata.twitter = {
|
|
46
|
+
...parentMetadata.twitter,
|
|
47
|
+
title: data.title,
|
|
48
|
+
description: data.description,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return metadata
|
|
53
|
+
}
|
|
54
|
+
|
|
19
55
|
export default async function DocsPage({ params }: PageProps) {
|
|
20
56
|
const { slug } = await params
|
|
21
57
|
const config = loadConfig()
|
|
@@ -33,20 +69,33 @@ export default async function DocsPage({ params }: PageProps) {
|
|
|
33
69
|
|
|
34
70
|
const tree = buildPageTree()
|
|
35
71
|
|
|
72
|
+
const pageUrl = config.url ? `${config.url}/${(slug ?? []).join('/')}` : undefined
|
|
73
|
+
|
|
36
74
|
return (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
75
|
+
<>
|
|
76
|
+
<script type="application/ld+json">
|
|
77
|
+
{JSON.stringify({
|
|
78
|
+
'@context': 'https://schema.org',
|
|
79
|
+
'@type': 'Article',
|
|
80
|
+
headline: data.title,
|
|
42
81
|
description: data.description,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
82
|
+
...(pageUrl && { url: pageUrl }),
|
|
83
|
+
}, null, 2)}
|
|
84
|
+
</script>
|
|
85
|
+
<Page
|
|
86
|
+
page={{
|
|
87
|
+
slug: slug ?? [],
|
|
88
|
+
frontmatter: {
|
|
89
|
+
title: data.title,
|
|
90
|
+
description: data.description,
|
|
91
|
+
},
|
|
92
|
+
content: <MDXBody components={mdxComponents} />,
|
|
93
|
+
toc: data.toc ?? [],
|
|
94
|
+
}}
|
|
95
|
+
config={config}
|
|
96
|
+
tree={tree}
|
|
97
|
+
/>
|
|
98
|
+
</>
|
|
50
99
|
)
|
|
51
100
|
}
|
|
52
101
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Metadata, ResolvingMetadata } from 'next'
|
|
1
2
|
import { notFound } from 'next/navigation'
|
|
2
3
|
import type { OpenAPIV3 } from 'openapi-types'
|
|
3
4
|
import { Flex, Headline, Text } from '@raystack/apsara'
|
|
@@ -10,6 +11,65 @@ interface PageProps {
|
|
|
10
11
|
params: Promise<{ slug?: string[] }>
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
export async function generateMetadata(
|
|
15
|
+
{ params }: PageProps,
|
|
16
|
+
parent: ResolvingMetadata,
|
|
17
|
+
): Promise<Metadata> {
|
|
18
|
+
const { slug } = await params
|
|
19
|
+
const config = loadConfig()
|
|
20
|
+
const specs = loadApiSpecs(config.api ?? [])
|
|
21
|
+
const parentMetadata = await parent
|
|
22
|
+
|
|
23
|
+
if (!slug || slug.length === 0) {
|
|
24
|
+
const apiDescription = `API documentation for ${config.title}`
|
|
25
|
+
const metadata: Metadata = {
|
|
26
|
+
title: 'API Reference',
|
|
27
|
+
description: apiDescription,
|
|
28
|
+
}
|
|
29
|
+
if (config.url) {
|
|
30
|
+
metadata.openGraph = {
|
|
31
|
+
...parentMetadata.openGraph,
|
|
32
|
+
title: 'API Reference',
|
|
33
|
+
description: apiDescription,
|
|
34
|
+
images: [{ url: `/og?title=${encodeURIComponent('API Reference')}&description=${encodeURIComponent(apiDescription)}`, width: 1200, height: 630 }],
|
|
35
|
+
}
|
|
36
|
+
metadata.twitter = {
|
|
37
|
+
...parentMetadata.twitter,
|
|
38
|
+
title: 'API Reference',
|
|
39
|
+
description: apiDescription,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return metadata
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const match = findApiOperation(specs, slug)
|
|
46
|
+
if (!match) return {}
|
|
47
|
+
|
|
48
|
+
const operation = match.operation as OpenAPIV3.OperationObject
|
|
49
|
+
const title = operation.summary ?? `${match.method.toUpperCase()} ${match.path}`
|
|
50
|
+
const description = operation.description
|
|
51
|
+
|
|
52
|
+
const metadata: Metadata = { title, description }
|
|
53
|
+
|
|
54
|
+
if (config.url) {
|
|
55
|
+
const ogParams = new URLSearchParams({ title })
|
|
56
|
+
if (description) ogParams.set('description', description)
|
|
57
|
+
metadata.openGraph = {
|
|
58
|
+
...parentMetadata.openGraph,
|
|
59
|
+
title,
|
|
60
|
+
description,
|
|
61
|
+
images: [{ url: `/og?${ogParams.toString()}`, width: 1200, height: 630 }],
|
|
62
|
+
}
|
|
63
|
+
metadata.twitter = {
|
|
64
|
+
...parentMetadata.twitter,
|
|
65
|
+
title,
|
|
66
|
+
description,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return metadata
|
|
71
|
+
}
|
|
72
|
+
|
|
13
73
|
export default async function ApiPage({ params }: PageProps) {
|
|
14
74
|
const { slug } = await params
|
|
15
75
|
const config = loadConfig()
|
package/src/app/layout.tsx
CHANGED
|
@@ -7,8 +7,28 @@ import { Providers } from './providers'
|
|
|
7
7
|
const config = loadConfig()
|
|
8
8
|
|
|
9
9
|
export const metadata: Metadata = {
|
|
10
|
-
title:
|
|
10
|
+
title: {
|
|
11
|
+
default: config.title,
|
|
12
|
+
template: `%s | ${config.title}`,
|
|
13
|
+
},
|
|
11
14
|
description: config.description,
|
|
15
|
+
...(config.url && {
|
|
16
|
+
metadataBase: new URL(config.url),
|
|
17
|
+
openGraph: {
|
|
18
|
+
title: config.title,
|
|
19
|
+
description: config.description,
|
|
20
|
+
url: config.url,
|
|
21
|
+
siteName: config.title,
|
|
22
|
+
type: 'website',
|
|
23
|
+
images: [{ url: '/og?title=' + encodeURIComponent(config.title), width: 1200, height: 630 }],
|
|
24
|
+
},
|
|
25
|
+
twitter: {
|
|
26
|
+
card: 'summary_large_image',
|
|
27
|
+
title: config.title,
|
|
28
|
+
description: config.description,
|
|
29
|
+
images: ['/og?title=' + encodeURIComponent(config.title)],
|
|
30
|
+
},
|
|
31
|
+
}),
|
|
12
32
|
}
|
|
13
33
|
|
|
14
34
|
export default function RootLayout({
|
|
@@ -19,6 +39,17 @@ export default function RootLayout({
|
|
|
19
39
|
return (
|
|
20
40
|
<html lang="en" suppressHydrationWarning>
|
|
21
41
|
<body suppressHydrationWarning>
|
|
42
|
+
{config.url && (
|
|
43
|
+
<script type="application/ld+json">
|
|
44
|
+
{JSON.stringify({
|
|
45
|
+
'@context': 'https://schema.org',
|
|
46
|
+
'@type': 'WebSite',
|
|
47
|
+
name: config.title,
|
|
48
|
+
description: config.description,
|
|
49
|
+
url: config.url,
|
|
50
|
+
}, null, 2)}
|
|
51
|
+
</script>
|
|
52
|
+
)}
|
|
22
53
|
<Providers>{children}</Providers>
|
|
23
54
|
</body>
|
|
24
55
|
</html>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ImageResponse } from 'next/og'
|
|
2
|
+
import type { NextRequest } from 'next/server'
|
|
3
|
+
import { loadConfig } from '@/lib/config'
|
|
4
|
+
|
|
5
|
+
export async function GET(request: NextRequest) {
|
|
6
|
+
const { searchParams } = request.nextUrl
|
|
7
|
+
const title = searchParams.get('title') ?? loadConfig().title
|
|
8
|
+
const description = searchParams.get('description') ?? ''
|
|
9
|
+
const siteName = loadConfig().title
|
|
10
|
+
|
|
11
|
+
return new ImageResponse(
|
|
12
|
+
(
|
|
13
|
+
<div
|
|
14
|
+
style={{
|
|
15
|
+
height: '100%',
|
|
16
|
+
width: '100%',
|
|
17
|
+
display: 'flex',
|
|
18
|
+
flexDirection: 'column',
|
|
19
|
+
justifyContent: 'center',
|
|
20
|
+
padding: '60px 80px',
|
|
21
|
+
backgroundColor: '#0a0a0a',
|
|
22
|
+
color: '#fafafa',
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<div
|
|
26
|
+
style={{
|
|
27
|
+
fontSize: 24,
|
|
28
|
+
color: '#888',
|
|
29
|
+
marginBottom: 16,
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
{siteName}
|
|
33
|
+
</div>
|
|
34
|
+
<div
|
|
35
|
+
style={{
|
|
36
|
+
fontSize: 56,
|
|
37
|
+
fontWeight: 700,
|
|
38
|
+
lineHeight: 1.2,
|
|
39
|
+
marginBottom: 24,
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
{title}
|
|
43
|
+
</div>
|
|
44
|
+
{description && (
|
|
45
|
+
<div
|
|
46
|
+
style={{
|
|
47
|
+
fontSize: 24,
|
|
48
|
+
color: '#999',
|
|
49
|
+
lineHeight: 1.4,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{description}
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
),
|
|
57
|
+
{
|
|
58
|
+
width: 1200,
|
|
59
|
+
height: 630,
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MetadataRoute } from 'next'
|
|
2
|
+
import { loadConfig } from '@/lib/config'
|
|
3
|
+
|
|
4
|
+
export default function robots(): MetadataRoute.Robots {
|
|
5
|
+
const config = loadConfig()
|
|
6
|
+
return {
|
|
7
|
+
rules: { userAgent: '*', allow: '/' },
|
|
8
|
+
...(config.url && { sitemap: `${config.url}/sitemap.xml` }),
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { MetadataRoute } from 'next'
|
|
2
|
+
import { loadConfig } from '@/lib/config'
|
|
3
|
+
import { source } from '@/lib/source'
|
|
4
|
+
import { loadApiSpecs } from '@/lib/openapi'
|
|
5
|
+
import { buildApiRoutes } from '@/lib/api-routes'
|
|
6
|
+
|
|
7
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
8
|
+
const config = loadConfig()
|
|
9
|
+
if (!config.url) return []
|
|
10
|
+
|
|
11
|
+
const baseUrl = config.url.replace(/\/$/, '')
|
|
12
|
+
|
|
13
|
+
const docPages = source.getPages().map((page) => ({
|
|
14
|
+
url: `${baseUrl}/${page.slugs.join('/')}`,
|
|
15
|
+
...(page.data.lastModified && { lastModified: new Date(page.data.lastModified) }),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
const apiPages = config.api?.length
|
|
19
|
+
? buildApiRoutes(loadApiSpecs(config.api)).map((route) => ({
|
|
20
|
+
url: `${baseUrl}/apis/${route.slug.join('/')}`,
|
|
21
|
+
}))
|
|
22
|
+
: []
|
|
23
|
+
|
|
24
|
+
return [
|
|
25
|
+
{ url: baseUrl },
|
|
26
|
+
...docPages,
|
|
27
|
+
...apiPages,
|
|
28
|
+
]
|
|
29
|
+
}
|
package/src/lib/config.ts
CHANGED
package/src/types/config.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface ChronicleConfig {
|
|
2
2
|
title: string
|
|
3
3
|
description?: string
|
|
4
|
+
url?: string
|
|
4
5
|
logo?: LogoConfig
|
|
5
6
|
theme?: ThemeConfig
|
|
6
7
|
navigation?: NavigationConfig
|
|
@@ -8,12 +9,22 @@ export interface ChronicleConfig {
|
|
|
8
9
|
footer?: FooterConfig
|
|
9
10
|
api?: ApiConfig[]
|
|
10
11
|
llms?: LlmsConfig
|
|
12
|
+
analytics?: AnalyticsConfig
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export interface LlmsConfig {
|
|
14
16
|
enabled?: boolean
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
export interface AnalyticsConfig {
|
|
20
|
+
enabled?: boolean
|
|
21
|
+
googleAnalytics?: GoogleAnalyticsConfig
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface GoogleAnalyticsConfig {
|
|
25
|
+
measurementId: string
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
export interface ApiConfig {
|
|
18
29
|
name: string
|
|
19
30
|
spec: string
|