@raystack/chronicle 0.1.0-canary.5a2be79 → 0.1.0-canary.5a730d4

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.
Files changed (76) hide show
  1. package/dist/cli/index.js +391 -9834
  2. package/package.json +16 -11
  3. package/src/cli/__tests__/config.test.ts +25 -0
  4. package/src/cli/__tests__/scaffold.test.ts +10 -0
  5. package/src/cli/commands/build.ts +38 -18
  6. package/src/cli/commands/dev.ts +10 -22
  7. package/src/cli/commands/init.ts +28 -29
  8. package/src/cli/commands/serve.ts +38 -36
  9. package/src/cli/commands/start.ts +12 -21
  10. package/src/cli/utils/config.ts +1 -1
  11. package/src/cli/utils/index.ts +1 -2
  12. package/src/cli/utils/resolve.ts +2 -0
  13. package/src/cli/utils/scaffold.ts +4 -115
  14. package/src/components/mdx/code.tsx +10 -1
  15. package/src/components/mdx/details.module.css +1 -24
  16. package/src/components/mdx/details.tsx +2 -3
  17. package/src/components/mdx/image.tsx +5 -19
  18. package/src/components/mdx/index.tsx +3 -3
  19. package/src/components/mdx/link.tsx +10 -11
  20. package/src/components/ui/footer.tsx +3 -2
  21. package/src/components/ui/search.module.css +7 -0
  22. package/src/components/ui/search.tsx +58 -86
  23. package/src/lib/config.ts +1 -0
  24. package/src/lib/head.tsx +45 -0
  25. package/src/lib/page-context.tsx +95 -0
  26. package/src/lib/source.ts +92 -21
  27. package/src/{app/apis/[[...slug]]/layout.tsx → pages/ApiLayout.tsx} +10 -7
  28. package/src/pages/ApiPage.tsx +68 -0
  29. package/src/pages/DocsLayout.tsx +18 -0
  30. package/src/pages/DocsPage.tsx +43 -0
  31. package/src/pages/NotFound.tsx +10 -0
  32. package/src/pages/__tests__/head.test.tsx +57 -0
  33. package/src/server/App.tsx +59 -0
  34. package/src/server/__tests__/entry-server.test.tsx +35 -0
  35. package/src/server/__tests__/handlers.test.ts +77 -0
  36. package/src/server/__tests__/og.test.ts +23 -0
  37. package/src/server/__tests__/router.test.ts +72 -0
  38. package/src/server/__tests__/vite-config.test.ts +25 -0
  39. package/src/server/dev.ts +156 -0
  40. package/src/server/entry-client.tsx +74 -0
  41. package/src/server/entry-prod.ts +127 -0
  42. package/src/server/entry-server.tsx +35 -0
  43. package/src/server/handlers/apis-proxy.ts +52 -0
  44. package/src/{app/api/health/route.ts → server/handlers/health.ts} +1 -1
  45. package/src/server/handlers/llms.ts +58 -0
  46. package/src/server/handlers/og.ts +87 -0
  47. package/src/server/handlers/robots.ts +11 -0
  48. package/src/server/handlers/search.ts +140 -0
  49. package/src/server/handlers/sitemap.ts +39 -0
  50. package/src/server/handlers/specs.ts +9 -0
  51. package/src/server/index.html +12 -0
  52. package/src/server/prod.ts +18 -0
  53. package/src/server/router.ts +42 -0
  54. package/src/server/vite-config.ts +65 -0
  55. package/src/themes/default/Layout.tsx +9 -10
  56. package/src/themes/default/Page.module.css +56 -0
  57. package/src/themes/default/font.ts +4 -6
  58. package/src/themes/paper/ChapterNav.tsx +5 -6
  59. package/src/themes/paper/Page.tsx +8 -9
  60. package/src/types/config.ts +11 -0
  61. package/src/types/content.ts +1 -0
  62. package/tsconfig.json +2 -3
  63. package/next.config.mjs +0 -10
  64. package/source.config.ts +0 -50
  65. package/src/app/[[...slug]]/layout.tsx +0 -15
  66. package/src/app/[[...slug]]/page.tsx +0 -57
  67. package/src/app/api/apis-proxy/route.ts +0 -59
  68. package/src/app/api/search/route.ts +0 -90
  69. package/src/app/apis/[[...slug]]/page.tsx +0 -57
  70. package/src/app/layout.tsx +0 -26
  71. package/src/app/llms-full.txt/route.ts +0 -18
  72. package/src/app/llms.txt/route.ts +0 -15
  73. package/src/app/providers.tsx +0 -8
  74. package/src/cli/utils/process.ts +0 -7
  75. package/src/lib/get-llm-text.ts +0 -10
  76. /package/src/{app/apis/[[...slug]]/layout.module.css → pages/ApiLayout.module.css} +0 -0
@@ -0,0 +1,140 @@
1
+ import fs from 'fs/promises'
2
+ import path from 'path'
3
+ import matter from 'gray-matter'
4
+ import MiniSearch from 'minisearch'
5
+ import type { OpenAPIV3 } from 'openapi-types'
6
+ import { loadConfig } from '@/lib/config'
7
+ import { loadApiSpecs, type ApiSpec } from '@/lib/openapi'
8
+ import { getSpecSlug } from '@/lib/api-routes'
9
+
10
+ interface SearchDocument {
11
+ id: string
12
+ url: string
13
+ title: string
14
+ content: string
15
+ type: 'page' | 'api'
16
+ }
17
+
18
+ let searchIndex: MiniSearch<SearchDocument> | null = null
19
+
20
+ function getContentDir(): string {
21
+ return process.env.CHRONICLE_CONTENT_DIR || path.join(process.cwd(), 'content')
22
+ }
23
+
24
+ async function scanContent(): Promise<SearchDocument[]> {
25
+ const contentDir = getContentDir()
26
+ const docs: SearchDocument[] = []
27
+
28
+ async function scan(dir: string, prefix: string[] = []) {
29
+ let entries
30
+ try { entries = await fs.readdir(dir, { withFileTypes: true }) }
31
+ catch { return }
32
+
33
+ for (const entry of entries) {
34
+ if (entry.name.startsWith('.') || entry.name === 'node_modules') continue
35
+ const fullPath = path.join(dir, entry.name)
36
+
37
+ if (entry.isDirectory()) {
38
+ await scan(fullPath, [...prefix, entry.name])
39
+ continue
40
+ }
41
+
42
+ if (!entry.name.endsWith('.mdx') && !entry.name.endsWith('.md')) continue
43
+
44
+ const raw = await fs.readFile(fullPath, 'utf-8')
45
+ const { data: fm, content } = matter(raw)
46
+ const baseName = entry.name.replace(/\.(mdx|md)$/, '')
47
+ const slugs = baseName === 'index' ? prefix : [...prefix, baseName]
48
+ const url = slugs.length === 0 ? '/' : '/' + slugs.join('/')
49
+
50
+ docs.push({
51
+ id: url,
52
+ url,
53
+ title: fm.title ?? baseName,
54
+ content: content.slice(0, 5000),
55
+ type: 'page',
56
+ })
57
+ }
58
+ }
59
+
60
+ await scan(contentDir)
61
+ return docs
62
+ }
63
+
64
+ function buildApiDocs(): SearchDocument[] {
65
+ const config = loadConfig()
66
+ if (!config.api?.length) return []
67
+
68
+ const docs: SearchDocument[] = []
69
+ const specs = loadApiSpecs(config.api)
70
+
71
+ for (const spec of specs) {
72
+ const specSlug = getSpecSlug(spec)
73
+ const paths = spec.document.paths ?? {}
74
+ for (const [pathStr, pathItem] of Object.entries(paths)) {
75
+ if (!pathItem) continue
76
+ for (const method of ['get', 'post', 'put', 'delete', 'patch'] as const) {
77
+ const op = pathItem[method] as OpenAPIV3.OperationObject | undefined
78
+ if (!op?.operationId) continue
79
+ const url = `/apis/${specSlug}/${encodeURIComponent(op.operationId)}`
80
+ docs.push({
81
+ id: url,
82
+ url,
83
+ title: `${method.toUpperCase()} ${op.summary ?? op.operationId}`,
84
+ content: op.description ?? '',
85
+ type: 'api',
86
+ })
87
+ }
88
+ }
89
+ }
90
+
91
+ return docs
92
+ }
93
+
94
+ async function getIndex(): Promise<MiniSearch<SearchDocument>> {
95
+ if (searchIndex) return searchIndex
96
+
97
+ const [contentDocs, apiDocs] = await Promise.all([
98
+ scanContent(),
99
+ Promise.resolve(buildApiDocs()),
100
+ ])
101
+
102
+ searchIndex = new MiniSearch<SearchDocument>({
103
+ fields: ['title', 'content'],
104
+ storeFields: ['url', 'title', 'type'],
105
+ searchOptions: {
106
+ boost: { title: 2 },
107
+ fuzzy: 0.2,
108
+ prefix: true,
109
+ },
110
+ })
111
+
112
+ searchIndex.addAll([...contentDocs, ...apiDocs])
113
+ return searchIndex
114
+ }
115
+
116
+ export async function handleSearch(req: Request): Promise<Response> {
117
+ const url = new URL(req.url)
118
+ const query = url.searchParams.get('query') ?? ''
119
+ const index = await getIndex()
120
+
121
+ if (!query) {
122
+ const contentDocs = await scanContent()
123
+ const suggestions = contentDocs.slice(0, 8).map((d) => ({
124
+ id: d.id,
125
+ url: d.url,
126
+ type: d.type,
127
+ content: d.title,
128
+ }))
129
+ return Response.json(suggestions)
130
+ }
131
+
132
+ const results = index.search(query).map((r) => ({
133
+ id: r.id,
134
+ url: r.url,
135
+ type: r.type,
136
+ content: r.title,
137
+ }))
138
+
139
+ return Response.json(results)
140
+ }
@@ -0,0 +1,39 @@
1
+ import { loadConfig } from '@/lib/config'
2
+ import { getPages } from '@/lib/source'
3
+ import { loadApiSpecs } from '@/lib/openapi'
4
+ import { buildApiRoutes } from '@/lib/api-routes'
5
+
6
+ export async function handleSitemap(): Promise<Response> {
7
+ const config = loadConfig()
8
+ if (!config.url) {
9
+ return new Response('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"/>', {
10
+ headers: { 'Content-Type': 'application/xml' },
11
+ })
12
+ }
13
+
14
+ const baseUrl = config.url.replace(/\/$/, '')
15
+
16
+ const pages = await getPages()
17
+ const docPages = pages.map((page) => {
18
+ const lastmod = page.frontmatter.lastModified
19
+ ? `<lastmod>${new Date(page.frontmatter.lastModified).toISOString()}</lastmod>`
20
+ : ''
21
+ return `<url><loc>${baseUrl}/${page.slugs.join('/')}</loc>${lastmod}</url>`
22
+ })
23
+
24
+ const apiPages = config.api?.length
25
+ ? buildApiRoutes(loadApiSpecs(config.api)).map((route) =>
26
+ `<url><loc>${baseUrl}/apis/${route.slug.join('/')}</loc></url>`
27
+ )
28
+ : []
29
+
30
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
31
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
32
+ <url><loc>${baseUrl}</loc></url>
33
+ ${[...docPages, ...apiPages].join('\n')}
34
+ </urlset>`
35
+
36
+ return new Response(xml, {
37
+ headers: { 'Content-Type': 'application/xml' },
38
+ })
39
+ }
@@ -0,0 +1,9 @@
1
+ import { loadConfig } from '@/lib/config'
2
+ import { loadApiSpecs } from '@/lib/openapi'
3
+
4
+ export function handleSpecs(): Response {
5
+ const config = loadConfig()
6
+ const specs = config.api?.length ? loadApiSpecs(config.api) : []
7
+
8
+ return Response.json(specs)
9
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <!--head-outlet-->
7
+ </head>
8
+ <body>
9
+ <div id="root"><!--ssr-outlet--></div>
10
+ <script type="module" src="/src/server/entry-client.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,18 @@
1
+ import path from 'path'
2
+ import chalk from 'chalk'
3
+
4
+ export interface ProdServerOptions {
5
+ port: number
6
+ root: string
7
+ distDir: string
8
+ }
9
+
10
+ export async function startProdServer(options: ProdServerOptions) {
11
+ const { port, distDir } = options
12
+
13
+ const serverEntry = path.resolve(distDir, 'server/entry-prod.js')
14
+ const { startServer } = await import(serverEntry)
15
+
16
+ console.log(chalk.cyan('Starting production server...'))
17
+ return startServer({ port, distDir })
18
+ }
@@ -0,0 +1,42 @@
1
+ import { handleHealth } from './handlers/health'
2
+ import { handleSearch } from './handlers/search'
3
+ import { handleApisProxy } from './handlers/apis-proxy'
4
+ import { handleOg } from './handlers/og'
5
+ import { handleLlms } from './handlers/llms'
6
+ import { handleSitemap } from './handlers/sitemap'
7
+ import { handleRobots } from './handlers/robots'
8
+ import { handleSpecs } from './handlers/specs'
9
+
10
+ export type RouteHandler = (req: Request) => Response | Promise<Response>
11
+
12
+ interface Route {
13
+ pattern: URLPattern
14
+ handler: RouteHandler
15
+ }
16
+
17
+ const routes: Route[] = []
18
+
19
+ function addRoute(path: string, handler: RouteHandler) {
20
+ routes.push({
21
+ pattern: new URLPattern({ pathname: path }),
22
+ handler,
23
+ })
24
+ }
25
+
26
+ addRoute('/api/health', handleHealth)
27
+ addRoute('/api/search', handleSearch)
28
+ addRoute('/api/apis-proxy', handleApisProxy)
29
+ addRoute('/api/specs', handleSpecs)
30
+ addRoute('/og', handleOg)
31
+ addRoute('/llms.txt', handleLlms)
32
+ addRoute('/sitemap.xml', handleSitemap)
33
+ addRoute('/robots.txt', handleRobots)
34
+
35
+ export function matchRoute(url: string): RouteHandler | null {
36
+ for (const route of routes) {
37
+ if (route.pattern.test(url)) {
38
+ return route.handler
39
+ }
40
+ }
41
+ return null
42
+ }
@@ -0,0 +1,65 @@
1
+ import path from 'path'
2
+ import { type InlineConfig } from 'vite'
3
+ import react from '@vitejs/plugin-react'
4
+ import mdx from '@mdx-js/rollup'
5
+ import remarkDirective from 'remark-directive'
6
+ import remarkGfm from 'remark-gfm'
7
+ import remarkFrontmatter from 'remark-frontmatter'
8
+ import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
9
+ import rehypeShiki from '@shikijs/rehype'
10
+
11
+ export interface ViteConfigOptions {
12
+ root: string
13
+ contentDir: string
14
+ isDev?: boolean
15
+ }
16
+
17
+ export async function createViteConfig(options: ViteConfigOptions): Promise<InlineConfig> {
18
+ const { root, contentDir, isDev = false } = options
19
+
20
+ return {
21
+ root,
22
+ configFile: false,
23
+ resolve: {
24
+ alias: {
25
+ '@': path.resolve(root, 'src'),
26
+ '@content': contentDir,
27
+ },
28
+ },
29
+ plugins: [
30
+ mdx({
31
+ remarkPlugins: [
32
+ remarkFrontmatter,
33
+ remarkMdxFrontmatter,
34
+ remarkGfm,
35
+ remarkDirective,
36
+ ],
37
+ rehypePlugins: [
38
+ [rehypeShiki, { themes: { light: 'github-light', dark: 'github-dark' } }],
39
+ ],
40
+ mdExtensions: ['.md'],
41
+ mdxExtensions: ['.mdx'],
42
+ }),
43
+ react(),
44
+ ],
45
+ define: {
46
+ 'process.env.CHRONICLE_CONTENT_DIR': JSON.stringify(contentDir),
47
+ 'process.env.CHRONICLE_PROJECT_ROOT': JSON.stringify(root),
48
+ },
49
+ css: {
50
+ modules: {
51
+ localsConvention: 'camelCase',
52
+ },
53
+ },
54
+ ssr: {
55
+ noExternal: ['@raystack/apsara'],
56
+ },
57
+ build: {
58
+ rolldownOptions: {
59
+ input: isDev ? undefined : {
60
+ client: path.resolve(root, 'src/server/index.html'),
61
+ },
62
+ },
63
+ },
64
+ }
65
+ }
@@ -1,10 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import { useMemo, useEffect, useRef } from "react";
4
- import { usePathname } from "next/navigation";
5
- import NextLink from "next/link";
4
+ import { useLocation, Link } from "react-router-dom";
6
5
  import { cx } from "class-variance-authority";
7
- import { Flex, Navbar, Headline, Link, Sidebar, Button } from "@raystack/apsara";
6
+ import { Flex, Navbar, Headline, Sidebar, Button } from "@raystack/apsara";
8
7
  import { RectangleStackIcon } from "@heroicons/react/24/outline";
9
8
  import { ClientThemeSwitcher } from "@/components/ui/client-theme-switcher";
10
9
  import { Search } from "@/components/ui/search";
@@ -25,7 +24,7 @@ const iconMap: Record<string, React.ReactNode> = {
25
24
  let savedScrollTop = 0;
26
25
 
27
26
  export function Layout({ children, config, tree, classNames }: ThemeLayoutProps) {
28
- const pathname = usePathname();
27
+ const { pathname } = useLocation();
29
28
  const scrollRef = useRef<HTMLDivElement>(null);
30
29
 
31
30
  useEffect(() => {
@@ -45,21 +44,21 @@ export function Layout({ children, config, tree, classNames }: ThemeLayoutProps)
45
44
  <Flex direction="column" className={cx(styles.layout, classNames?.layout)}>
46
45
  <Navbar className={styles.header}>
47
46
  <Navbar.Start>
48
- <NextLink href="/" style={{ textDecoration: 'none', color: 'inherit' }}>
47
+ <Link to="/" style={{ textDecoration: 'none', color: 'inherit' }}>
49
48
  <Headline size="small" weight="medium" as="h1">
50
49
  {config.title}
51
50
  </Headline>
52
- </NextLink>
51
+ </Link>
53
52
  </Navbar.Start>
54
53
  <Navbar.End>
55
54
  <Flex gap="medium" align="center" className={styles.navActions}>
56
55
  {config.api?.map((api) => (
57
- <NextLink key={api.basePath} href={api.basePath} className={styles.navButton}>
56
+ <Link key={api.name} to={api.basePath} className={styles.navButton}>
58
57
  {api.name} API
59
- </NextLink>
58
+ </Link>
60
59
  ))}
61
60
  {config.navigation?.links?.map((link) => (
62
- <Link key={link.href} href={link.href}>
61
+ <Link key={link.href} to={link.href}>
63
62
  {link.label}
64
63
  </Link>
65
64
  ))}
@@ -118,7 +117,7 @@ function SidebarNode({
118
117
 
119
118
  const isActive = pathname === item.url;
120
119
  const href = item.url ?? "#";
121
- const link = useMemo(() => <NextLink href={href} scroll={false} />, [href]);
120
+ const link = useMemo(() => <Link to={href} />, [href]);
122
121
 
123
122
  return (
124
123
  <Sidebar.Item
@@ -38,9 +38,65 @@
38
38
  margin-bottom: var(--rs-space-3);
39
39
  }
40
40
 
41
+ .content :global(pre code span) {
42
+ color: var(--shiki-light);
43
+ }
44
+
45
+ :global([data-theme="dark"]) .content :global(pre code span) {
46
+ color: var(--shiki-dark);
47
+ }
48
+
49
+ :global([data-theme="dark"]) .content :global(pre) {
50
+ background-color: var(--shiki-dark-bg, #24292e);
51
+ }
52
+
53
+ .content img {
54
+ max-width: 100%;
55
+ height: auto;
56
+ }
57
+
41
58
  .content table {
42
59
  display: block;
43
60
  max-width: 100%;
44
61
  overflow-x: auto;
45
62
  margin-bottom: var(--rs-space-5);
46
63
  }
64
+
65
+ .content details {
66
+ border: 1px solid var(--rs-color-border-base-primary);
67
+ border-radius: var(--rs-radius-2);
68
+ margin: var(--rs-space-5) 0;
69
+ overflow: hidden;
70
+ }
71
+
72
+ .content details summary {
73
+ padding: var(--rs-space-4) var(--rs-space-5);
74
+ cursor: pointer;
75
+ font-weight: 500;
76
+ font-size: var(--rs-font-size-small);
77
+ color: var(--rs-color-text-base-primary);
78
+ background: var(--rs-color-background-base-secondary);
79
+ list-style: none;
80
+ display: flex;
81
+ align-items: center;
82
+ gap: var(--rs-space-3);
83
+ }
84
+
85
+ .content details summary::-webkit-details-marker {
86
+ display: none;
87
+ }
88
+
89
+ .content details summary::before {
90
+ content: '▶';
91
+ font-size: 10px;
92
+ transition: transform 0.2s ease;
93
+ color: var(--rs-color-text-base-secondary);
94
+ }
95
+
96
+ .content details[open] > summary::before {
97
+ transform: rotate(90deg);
98
+ }
99
+
100
+ .content details > :not(summary) {
101
+ padding: var(--rs-space-4) var(--rs-space-5);
102
+ }
@@ -1,6 +1,4 @@
1
- import { Inter } from 'next/font/google'
2
-
3
- export const inter = Inter({
4
- subsets: ['latin'],
5
- display: 'swap',
6
- })
1
+ export const inter = {
2
+ className: 'chronicle-inter',
3
+ style: { fontFamily: "'Inter', system-ui, -apple-system, sans-serif" },
4
+ }
@@ -1,7 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { usePathname } from 'next/navigation'
4
- import NextLink from 'next/link'
3
+ import { useLocation, Link } from 'react-router-dom'
5
4
  import { MethodBadge } from '@/components/api/method-badge'
6
5
  import type { PageTree, PageTreeItem } from '@/types'
7
6
  import styles from './ChapterNav.module.css'
@@ -31,7 +30,7 @@ function buildChapterIndices(children: PageTreeItem[]): Map<PageTreeItem, number
31
30
  }
32
31
 
33
32
  export function ChapterNav({ tree }: ChapterNavProps) {
34
- const pathname = usePathname()
33
+ const { pathname } = useLocation()
35
34
  const chapterIndices = buildChapterIndices(tree.children)
36
35
 
37
36
  return (
@@ -84,13 +83,13 @@ function ChapterItem({ item, pathname }: { item: PageTreeItem; pathname: string
84
83
 
85
84
  return (
86
85
  <li>
87
- <NextLink
88
- href={item.url ?? '#'}
86
+ <Link
87
+ to={item.url ?? '#'}
89
88
  className={`${styles.link} ${isActive ? styles.active : ''}`}
90
89
  >
91
90
  {icon && <span className={styles.icon}>{icon}</span>}
92
91
  <span>{item.name}</span>
93
- </NextLink>
92
+ </Link>
94
93
  </li>
95
94
  )
96
95
  }
@@ -1,8 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { useMemo } from 'react'
4
- import { usePathname } from 'next/navigation'
5
- import NextLink from 'next/link'
4
+ import { useLocation, Link } from 'react-router-dom'
6
5
  import { Flex } from '@raystack/apsara'
7
6
  import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
8
7
  import type { ThemePageProps, PageTreeItem } from '@/types'
@@ -41,7 +40,7 @@ function findInTree(items: PageTreeItem[], path: string): PageTreeItem | undefin
41
40
  }
42
41
 
43
42
  export function Page({ page, config, tree }: ThemePageProps) {
44
- const pathname = usePathname()
43
+ const { pathname } = useLocation()
45
44
 
46
45
  const { prev, next, crumbs } = useMemo(() => {
47
46
  const pages = flattenTree(tree.children)
@@ -59,18 +58,18 @@ export function Page({ page, config, tree }: ThemePageProps) {
59
58
  <Flex align="center" className={styles.navbar}>
60
59
  <Flex align="center" gap="small" className={styles.navLeft}>
61
60
  {prev ? (
62
- <NextLink href={prev.url!} className={styles.arrow} aria-label="Previous page">
61
+ <Link to={prev.url!} className={styles.arrow} aria-label="Previous page">
63
62
  <ChevronLeftIcon width={14} height={14} />
64
- </NextLink>
63
+ </Link>
65
64
  ) : (
66
65
  <button disabled className={styles.arrowDisabled} aria-label="Previous page">
67
66
  <ChevronLeftIcon width={14} height={14} />
68
67
  </button>
69
68
  )}
70
69
  {next ? (
71
- <NextLink href={next.url!} className={styles.arrow} aria-label="Next page">
70
+ <Link to={next.url!} className={styles.arrow} aria-label="Next page">
72
71
  <ChevronRightIcon width={14} height={14} />
73
- </NextLink>
72
+ </Link>
74
73
  ) : (
75
74
  <button disabled className={styles.arrowDisabled} aria-label="Next page">
76
75
  <ChevronRightIcon width={14} height={14} />
@@ -83,9 +82,9 @@ export function Page({ page, config, tree }: ThemePageProps) {
83
82
  {i === crumbs.length - 1 ? (
84
83
  <span className={styles.crumbActive}>{crumb.label}</span>
85
84
  ) : (
86
- <NextLink href={crumb.href} className={styles.crumbLink}>
85
+ <Link to={crumb.href} className={styles.crumbLink}>
87
86
  {crumb.label}
88
- </NextLink>
87
+ </Link>
89
88
  )}
90
89
  </span>
91
90
  ))}
@@ -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
@@ -5,6 +5,7 @@ export interface Frontmatter {
5
5
  description?: string
6
6
  order?: number
7
7
  icon?: string
8
+ lastModified?: string
8
9
  }
9
10
 
10
11
  export interface Page {
package/tsconfig.json CHANGED
@@ -21,10 +21,9 @@
21
21
  "lib": ["ES2022", "DOM", "DOM.Iterable"],
22
22
  "moduleResolution": "bundler",
23
23
  "paths": {
24
- "@/*": ["./src/*"],
25
- "@/.source/*": ["./.source/*"]
24
+ "@/*": ["./src/*"]
26
25
  }
27
26
  },
28
- "include": ["src", ".source", "source.config.ts"],
27
+ "include": ["src"],
29
28
  "exclude": ["node_modules", "dist"]
30
29
  }
package/next.config.mjs DELETED
@@ -1,10 +0,0 @@
1
- import { createMDX } from 'fumadocs-mdx/next'
2
-
3
- const withMDX = createMDX()
4
-
5
- /** @type {import('next').NextConfig} */
6
- const nextConfig = {
7
- reactStrictMode: true,
8
- }
9
-
10
- export default withMDX(nextConfig)
package/source.config.ts DELETED
@@ -1,50 +0,0 @@
1
- import { defineDocs, defineConfig, frontmatterSchema } from 'fumadocs-mdx/config'
2
- import { z } from 'zod'
3
- import remarkDirective from 'remark-directive'
4
- import { remarkDirectiveAdmonition, remarkMdxMermaid } from 'fumadocs-core/mdx-plugins'
5
- import remarkUnusedDirectives from './src/lib/remark-unused-directives'
6
-
7
- const contentDir = process.env.CHRONICLE_CONTENT_DIR || './content'
8
-
9
- export const docs = defineDocs({
10
- dir: contentDir,
11
- docs: {
12
- schema: frontmatterSchema.extend({
13
- order: z.number().optional(),
14
- }),
15
- postprocess: {
16
- includeProcessedMarkdown: true,
17
- },
18
- files: ['**/*.mdx', '**/*.md', '!**/node_modules/**'],
19
- },
20
- })
21
-
22
- export default defineConfig({
23
- mdxOptions: {
24
- remarkPlugins: [
25
- remarkDirective,
26
- [
27
- remarkDirectiveAdmonition,
28
- {
29
- tags: {
30
- CalloutContainer: 'Callout',
31
- CalloutTitle: 'CalloutTitle',
32
- CalloutDescription: 'CalloutDescription',
33
- },
34
- types: {
35
- note: 'accent',
36
- tip: 'accent',
37
- info: 'accent',
38
- warn: 'attention',
39
- warning: 'attention',
40
- danger: 'alert',
41
- caution: 'alert',
42
- success: 'success',
43
- },
44
- },
45
- ],
46
- remarkUnusedDirectives,
47
- remarkMdxMermaid,
48
- ],
49
- },
50
- })