diff --git a/packages/chronicle/src/lib/api-routes.ts b/packages/chronicle/src/lib/api-routes.ts index bed4314..52e5842 100644 --- a/packages/chronicle/src/lib/api-routes.ts +++ b/packages/chronicle/src/lib/api-routes.ts @@ -17,6 +17,22 @@ function getOperationId(op: OpenAPIV3.OperationObject, method: string, path: str } +export function getFirstApiUrl(specs: ApiSpec[]): string | null { + for (const spec of specs) { + const specSlug = getSpecSlug(spec) + const paths = spec.document.paths ?? {} + for (const [pathStr, pathItem] of Object.entries(paths)) { + if (!pathItem) continue + for (const method of ['get', 'post', 'put', 'delete', 'patch'] as const) { + const op = pathItem[method] + if (!op) continue + return `/apis/${specSlug}/${encodeURIComponent(getOperationId(op, method, pathStr))}` + } + } + } + return null +} + export function buildApiRoutes(specs: ApiSpec[]): { slug: string[] }[] { const routes: { slug: string[] }[] = [] diff --git a/packages/chronicle/src/pages/ApiPage.tsx b/packages/chronicle/src/pages/ApiPage.tsx index 3fe0632..d1382bb 100644 --- a/packages/chronicle/src/pages/ApiPage.tsx +++ b/packages/chronicle/src/pages/ApiPage.tsx @@ -1,9 +1,8 @@ -import { Flex, Headline, Text } from '@raystack/apsara'; import type { OpenAPIV3 } from 'openapi-types'; +import { Navigate } from 'react-router'; import { ApiOverview } from '@/components/api'; -import { findApiOperation } from '@/lib/api-routes'; +import { findApiOperation, getFirstApiUrl } from '@/lib/api-routes'; import { Head } from '@/lib/head'; -import type { ApiSpec } from '@/lib/openapi'; import { usePageContext } from '@/lib/page-context'; interface ApiPageProps { @@ -14,16 +13,9 @@ export function ApiPage({ slug }: ApiPageProps) { const { config, apiSpecs } = usePageContext(); if (slug.length === 0) { - return ( - <> - - - - ); + const firstUrl = getFirstApiUrl(apiSpecs); + if (firstUrl) return ; + return null; } const match = findApiOperation(apiSpecs, slug); @@ -48,26 +40,3 @@ export function ApiPage({ slug }: ApiPageProps) { ); } -function ApiLanding({ specs }: { specs: ApiSpec[] }) { - return ( - - - API Reference - - {specs.map(spec => ( - - - {spec.name} - - {spec.document.info.description && ( - {spec.document.info.description} - )} - - ))} - - ); -} diff --git a/packages/chronicle/src/pages/DocsPage.tsx b/packages/chronicle/src/pages/DocsPage.tsx index 344b9f0..0492324 100644 --- a/packages/chronicle/src/pages/DocsPage.tsx +++ b/packages/chronicle/src/pages/DocsPage.tsx @@ -1,7 +1,20 @@ +import { Navigate } from 'react-router'; import { Head } from '@/lib/head'; import { usePageContext } from '@/lib/page-context'; import { NotFound } from '@/pages/NotFound'; import { getTheme } from '@/themes/registry'; +import type { Node } from 'fumadocs-core/page-tree'; + +function getFirstPageUrl(nodes: Node[]): string | null { + for (const node of nodes) { + if (node.type === 'page') return node.url; + if (node.type === 'folder') { + const url = getFirstPageUrl(node.children); + if (url) return url; + } + } + return null; +} interface DocsPageProps { slug: string[]; @@ -10,7 +23,14 @@ interface DocsPageProps { export function DocsPage({ slug }: DocsPageProps) { const { config, tree, page, isLoading, errorStatus } = usePageContext(); - if (errorStatus === 404) return ; + if (errorStatus === 404) { + const isContentRoot = config.content?.some(c => slug.length === 1 && slug[0] === c.dir); + if (isContentRoot) { + const firstUrl = getFirstPageUrl(tree.children); + if (firstUrl) return ; + } + return ; + } if (errorStatus) return ; const { Page, Skeleton } = getTheme(config.theme?.name);