Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions packages/chronicle/src/lib/api-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] }[] = []

Expand Down
41 changes: 5 additions & 36 deletions packages/chronicle/src/pages/ApiPage.tsx
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -14,16 +13,9 @@ export function ApiPage({ slug }: ApiPageProps) {
const { config, apiSpecs } = usePageContext();

if (slug.length === 0) {
return (
<>
<Head
title='API Reference'
description={`API documentation for ${config.site.title}`}
config={config}
/>
<ApiLanding specs={apiSpecs} />
</>
);
const firstUrl = getFirstApiUrl(apiSpecs);
if (firstUrl) return <Navigate to={firstUrl} replace />;
return null;
}

const match = findApiOperation(apiSpecs, slug);
Expand All @@ -48,26 +40,3 @@ export function ApiPage({ slug }: ApiPageProps) {
);
}

function ApiLanding({ specs }: { specs: ApiSpec[] }) {
return (
<Flex
direction='column'
gap='large'
style={{ padding: 'var(--rs-space-7)' }}
>
<Headline size='medium' as='h1'>
API Reference
</Headline>
{specs.map(spec => (
<Flex key={spec.name} direction='column' gap='small'>
<Headline size='small' as='h2'>
{spec.name}
</Headline>
{spec.document.info.description && (
<Text size={3}>{spec.document.info.description}</Text>
)}
</Flex>
))}
</Flex>
);
}
22 changes: 21 additions & 1 deletion packages/chronicle/src/pages/DocsPage.tsx
Original file line number Diff line number Diff line change
@@ -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[];
Expand All @@ -10,7 +23,14 @@ interface DocsPageProps {
export function DocsPage({ slug }: DocsPageProps) {
const { config, tree, page, isLoading, errorStatus } = usePageContext();

if (errorStatus === 404) return <NotFound />;
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 <Navigate to={firstUrl} replace />;
}
return <NotFound />;
}
if (errorStatus) return <NotFound />;
const { Page, Skeleton } = getTheme(config.theme?.name);

Expand Down
Loading