@raystack/chronicle 0.10.0 → 0.10.2
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/dist/cli/index.js +24 -0
- package/package.json +3 -2
- package/src/cli/commands/dev.ts +12 -0
- package/src/cli/commands/start.ts +12 -0
- package/src/components/api/api-overview.tsx +2 -2
- package/src/components/api/playground-dialog.tsx +42 -20
- package/src/components/mdx/link.tsx +5 -31
- package/src/components/ui/PrefetchProvider.tsx +70 -0
- package/src/components/ui/search.module.css +6 -0
- package/src/components/ui/search.tsx +4 -1
- package/src/lib/env.ts +9 -0
- package/src/lib/openapi.ts +2 -1
- package/src/lib/page-context.tsx +11 -6
- package/src/lib/preload.ts +37 -0
- package/src/lib/source.ts +21 -2
- package/src/pages/DocsLayout.tsx +11 -8
- package/src/server/App.module.css +4 -0
- package/src/server/App.tsx +32 -15
- package/src/server/api/page.ts +2 -2
- package/src/server/api/search.ts +16 -6
- package/src/server/entry-client.tsx +18 -14
- package/src/server/entry-server.tsx +6 -2
- package/src/server/routes/[...slug].md.ts +5 -1
- package/src/server/{routes/apis/[...slug].md.ts → utils/api-markdown.ts} +3 -6
- package/src/themes/default/ContentDirButtons.tsx +1 -1
- package/src/themes/default/Layout.tsx +38 -21
- package/src/themes/default/Page.module.css +9 -0
- package/src/themes/default/Page.tsx +6 -2
- package/src/themes/default/Skeleton.tsx +5 -15
- package/src/themes/default/VersionSwitcher.tsx +2 -2
- package/src/themes/paper/VersionSwitcher.tsx +2 -2
- package/src/types/content.ts +1 -0
- package/src/components/common/breadcrumb.tsx +0 -3
- package/src/components/common/button.tsx +0 -3
- package/src/components/common/code-block.tsx +0 -3
- package/src/components/common/dialog.tsx +0 -3
- package/src/components/common/index.ts +0 -10
- package/src/components/common/input-field.tsx +0 -3
- package/src/components/common/sidebar.tsx +0 -3
- package/src/components/common/switch.tsx +0 -3
- package/src/components/common/table.tsx +0 -3
- package/src/components/common/tabs.tsx +0 -3
package/src/server/App.tsx
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import '@raystack/apsara/normalize.css';
|
|
2
2
|
import '@raystack/apsara/style.css';
|
|
3
|
-
import { ThemeProvider } from '@raystack/apsara';
|
|
3
|
+
import { ThemeProvider, Skeleton, Flex } from '@raystack/apsara';
|
|
4
|
+
import { lazy, Suspense } from 'react';
|
|
4
5
|
import { Navigate, useLocation } from 'react-router';
|
|
5
6
|
import { Head } from '@/lib/head';
|
|
6
7
|
import { usePageContext } from '@/lib/page-context';
|
|
7
8
|
import { resolveRoute, RouteType } from '@/lib/route-resolver';
|
|
8
|
-
import { ApiLayout } from '@/pages/ApiLayout';
|
|
9
|
-
import { ApiPage } from '@/pages/ApiPage';
|
|
10
|
-
import { DocsLayout } from '@/pages/DocsLayout';
|
|
11
|
-
import { DocsPage } from '@/pages/DocsPage';
|
|
12
|
-
import { LandingPage } from '@/pages/LandingPage';
|
|
13
9
|
import type { ChronicleConfig } from '@/types';
|
|
14
10
|
import { getThemeConfig } from '@/themes/registry';
|
|
11
|
+
import styles from './App.module.css';
|
|
12
|
+
|
|
13
|
+
const ApiLayout = lazy(() => import('@/pages/ApiLayout').then(m => ({ default: m.ApiLayout })));
|
|
14
|
+
const ApiPage = lazy(() => import('@/pages/ApiPage').then(m => ({ default: m.ApiPage })));
|
|
15
|
+
const DocsLayout = lazy(() => import('@/pages/DocsLayout').then(m => ({ default: m.DocsLayout })));
|
|
16
|
+
const DocsPage = lazy(() => import('@/pages/DocsPage').then(m => ({ default: m.DocsPage })));
|
|
17
|
+
const LandingPage = lazy(() => import('@/pages/LandingPage').then(m => ({ default: m.LandingPage })));
|
|
15
18
|
|
|
16
19
|
export function App() {
|
|
17
20
|
const { pathname } = useLocation();
|
|
@@ -35,19 +38,33 @@ export function App() {
|
|
|
35
38
|
forcedTheme={themeConfig.forcedTheme}
|
|
36
39
|
>
|
|
37
40
|
<RootHead config={config} />
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
<Suspense fallback={<PageFallback />}>
|
|
42
|
+
{isApi ? (
|
|
43
|
+
<ApiLayout>
|
|
44
|
+
<ApiPage slug={apiSlug} />
|
|
45
|
+
</ApiLayout>
|
|
46
|
+
) : (
|
|
47
|
+
<DocsLayout hideSidebar={isLanding}>
|
|
48
|
+
{isLanding ? <LandingPage /> : <DocsPage slug={docsSlug} />}
|
|
49
|
+
</DocsLayout>
|
|
50
|
+
)}
|
|
51
|
+
</Suspense>
|
|
47
52
|
</ThemeProvider>
|
|
48
53
|
);
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
function PageFallback() {
|
|
57
|
+
return (
|
|
58
|
+
<Flex direction="column" gap={4} className={styles.fallback}>
|
|
59
|
+
<Skeleton width="40%" height="var(--rs-line-height-t2)" />
|
|
60
|
+
<Skeleton width="60%" height="var(--rs-line-height-regular)" />
|
|
61
|
+
{[...new Array(12)].map((_, i) => (
|
|
62
|
+
<Skeleton key={i} width="100%" height="var(--rs-line-height-regular)" />
|
|
63
|
+
))}
|
|
64
|
+
</Flex>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
51
68
|
function RootHead({ config }: { config: ChronicleConfig }) {
|
|
52
69
|
return (
|
|
53
70
|
<Head
|
package/src/server/api/page.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { defineHandler, HTTPError } from 'nitro';
|
|
2
|
-
import { getPage, getPageNav, extractFrontmatter, getRelativePath, getOriginalPath } from '@/lib/source';
|
|
2
|
+
import { getPage, getPageNav, extractFrontmatter, getRelativePath, getOriginalPath, isDraft } from '@/lib/source';
|
|
3
3
|
|
|
4
4
|
export default defineHandler(async event => {
|
|
5
5
|
const slugParam = event.url.searchParams.get('slug') ?? '';
|
|
6
6
|
const slug = slugParam ? slugParam.split(',').filter(Boolean) : [];
|
|
7
7
|
const page = await getPage(slug);
|
|
8
8
|
|
|
9
|
-
if (!page) {
|
|
9
|
+
if (!page || isDraft(page)) {
|
|
10
10
|
throw new HTTPError({ status: 404, message: 'Page not found' });
|
|
11
11
|
}
|
|
12
12
|
|
package/src/server/api/search.ts
CHANGED
|
@@ -14,6 +14,7 @@ interface SearchDocument {
|
|
|
14
14
|
headings: string;
|
|
15
15
|
body: string;
|
|
16
16
|
type: 'page' | 'api';
|
|
17
|
+
section: string;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
import fs from 'node:fs/promises';
|
|
@@ -61,7 +62,8 @@ async function buildIndex(ctx: VersionContext, key: string) {
|
|
|
61
62
|
headings TEXT NOT NULL,
|
|
62
63
|
body TEXT NOT NULL,
|
|
63
64
|
type TEXT NOT NULL,
|
|
64
|
-
version TEXT NOT NULL
|
|
65
|
+
version TEXT NOT NULL,
|
|
66
|
+
section TEXT NOT NULL DEFAULT ''
|
|
65
67
|
)`);
|
|
66
68
|
|
|
67
69
|
await db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(
|
|
@@ -74,8 +76,8 @@ async function buildIndex(ctx: VersionContext, key: string) {
|
|
|
74
76
|
|
|
75
77
|
const docs = await buildDocs(ctx);
|
|
76
78
|
for (const doc of docs) {
|
|
77
|
-
await db.sql`INSERT INTO search_docs (id, url, title, headings, body, type, version)
|
|
78
|
-
VALUES (${doc.id}, ${doc.url}, ${doc.title}, ${doc.headings}, ${doc.body}, ${doc.type}, ${key})`;
|
|
79
|
+
await db.sql`INSERT INTO search_docs (id, url, title, headings, body, type, version, section)
|
|
80
|
+
VALUES (${doc.id}, ${doc.url}, ${doc.title}, ${doc.headings}, ${doc.body}, ${doc.type}, ${key}, ${doc.section})`;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
83
|
await db.sql`INSERT INTO search_fts (rowid, title, headings, body)
|
|
@@ -86,11 +88,15 @@ async function buildIndex(ctx: VersionContext, key: string) {
|
|
|
86
88
|
|
|
87
89
|
async function buildDocs(ctx: VersionContext): Promise<SearchDocument[]> {
|
|
88
90
|
const docs: SearchDocument[] = [];
|
|
91
|
+
const config = loadConfig();
|
|
92
|
+
const contentEntries = config.content ?? [];
|
|
89
93
|
|
|
90
94
|
const pages = await getPagesForVersion(ctx);
|
|
91
95
|
for (const p of pages) {
|
|
92
96
|
const fm = extractFrontmatter(p);
|
|
93
97
|
const { headings, body } = await getPageSearchContent(p);
|
|
98
|
+
const dir = p.url.replace(/^\//, '').split('/')[0];
|
|
99
|
+
const entry = contentEntries.find(c => c.dir === dir);
|
|
94
100
|
docs.push({
|
|
95
101
|
id: p.url,
|
|
96
102
|
url: p.url,
|
|
@@ -98,10 +104,10 @@ async function buildDocs(ctx: VersionContext): Promise<SearchDocument[]> {
|
|
|
98
104
|
headings,
|
|
99
105
|
body: [fm.description ?? '', body].join(' '),
|
|
100
106
|
type: 'page',
|
|
107
|
+
section: entry?.label ?? dir ?? '',
|
|
101
108
|
});
|
|
102
109
|
}
|
|
103
110
|
|
|
104
|
-
const config = loadConfig();
|
|
105
111
|
const apiConfigs = getApiConfigsForVersion(config, ctx.dir);
|
|
106
112
|
if (apiConfigs.length) {
|
|
107
113
|
const specs = await loadApiSpecs(apiConfigs);
|
|
@@ -122,6 +128,7 @@ async function buildDocs(ctx: VersionContext): Promise<SearchDocument[]> {
|
|
|
122
128
|
headings: op.summary ?? opId,
|
|
123
129
|
body: [op.description ?? '', pathStr, method.toUpperCase()].join(' '),
|
|
124
130
|
type: 'api',
|
|
131
|
+
section: spec.name,
|
|
125
132
|
});
|
|
126
133
|
}
|
|
127
134
|
}
|
|
@@ -183,7 +190,7 @@ export default defineHandler(async event => {
|
|
|
183
190
|
const key = versionKey(ctx);
|
|
184
191
|
|
|
185
192
|
if (!query) {
|
|
186
|
-
const result = await db.sql`SELECT id, url, title, type FROM search_docs
|
|
193
|
+
const result = await db.sql`SELECT id, url, title, type, section FROM search_docs
|
|
187
194
|
WHERE version = ${key} AND type = 'page'
|
|
188
195
|
LIMIT 8`;
|
|
189
196
|
return Response.json((result.rows ?? []).map(r => ({
|
|
@@ -191,11 +198,12 @@ export default defineHandler(async event => {
|
|
|
191
198
|
url: r.url,
|
|
192
199
|
type: r.type,
|
|
193
200
|
content: r.title,
|
|
201
|
+
section: r.section || null,
|
|
194
202
|
})));
|
|
195
203
|
}
|
|
196
204
|
|
|
197
205
|
const searchTerm = query.split(/\s+/).map(t => `"${t}"*`).join(' ');
|
|
198
|
-
const result = await db.sql`SELECT s.id, s.url, s.title, s.headings, s.body, s.type,
|
|
206
|
+
const result = await db.sql`SELECT s.id, s.url, s.title, s.headings, s.body, s.type, s.section,
|
|
199
207
|
bm25(search_fts, 10.0, 5.0, 1.0) AS score
|
|
200
208
|
FROM search_fts f
|
|
201
209
|
JOIN search_docs s ON s.rowid = f.rowid
|
|
@@ -214,6 +222,8 @@ export default defineHandler(async event => {
|
|
|
214
222
|
content: r.title,
|
|
215
223
|
match,
|
|
216
224
|
snippet,
|
|
225
|
+
section: r.section || null,
|
|
217
226
|
};
|
|
218
227
|
}));
|
|
219
228
|
});
|
|
229
|
+
|
|
@@ -3,9 +3,11 @@ import React from 'react';
|
|
|
3
3
|
import { hydrateRoot } from 'react-dom/client';
|
|
4
4
|
import { BrowserRouter } from 'react-router';
|
|
5
5
|
import { ReactRouterProvider } from 'fumadocs-core/framework/react-router';
|
|
6
|
+
import { QueryClientProvider } from '@tanstack/react-query';
|
|
6
7
|
import { mdxComponents } from '@/components/mdx';
|
|
7
8
|
import { getApiConfigsForVersion } from '@/lib/config';
|
|
8
9
|
import { PageProvider } from '@/lib/page-context';
|
|
10
|
+
import { queryClient } from '@/lib/preload';
|
|
9
11
|
import { resolveRoute, RouteType } from '@/lib/route-resolver';
|
|
10
12
|
import { resolveVersionFromUrl, type VersionContext } from '@/lib/version-source';
|
|
11
13
|
import type { ChronicleConfig, Frontmatter, PageNavLink, Root, TableOfContents } from '@/types';
|
|
@@ -93,20 +95,22 @@ async function hydrate() {
|
|
|
93
95
|
|
|
94
96
|
hydrateRoot(
|
|
95
97
|
document.getElementById('root') as HTMLElement,
|
|
96
|
-
<
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
<QueryClientProvider client={queryClient}>
|
|
99
|
+
<BrowserRouter>
|
|
100
|
+
<ReactRouterProvider>
|
|
101
|
+
<PageProvider
|
|
102
|
+
initialConfig={config}
|
|
103
|
+
initialTree={tree}
|
|
104
|
+
initialPage={page}
|
|
105
|
+
initialApiSpecs={apiSpecs}
|
|
106
|
+
initialVersion={version}
|
|
107
|
+
loadMdx={loadMdxModule}
|
|
108
|
+
>
|
|
109
|
+
<App />
|
|
110
|
+
</PageProvider>
|
|
111
|
+
</ReactRouterProvider>
|
|
112
|
+
</BrowserRouter>
|
|
113
|
+
</QueryClientProvider>
|
|
110
114
|
);
|
|
111
115
|
} catch (err) {
|
|
112
116
|
console.error('Hydration failed:', err);
|
|
@@ -9,7 +9,7 @@ import { getApiConfigsForVersion, loadConfig } from '@/lib/config';
|
|
|
9
9
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
10
10
|
import { PageProvider } from '@/lib/page-context';
|
|
11
11
|
import { resolveRoute, RouteType } from '@/lib/route-resolver';
|
|
12
|
-
import { getPageTree, getPage, getPageNav, loadPageModule, extractFrontmatter, getRelativePath, getOriginalPath } from '@/lib/source';
|
|
12
|
+
import { getPageTree, getPage, getPageNav, loadPageModule, extractFrontmatter, getRelativePath, getOriginalPath, isDraft } from '@/lib/source';
|
|
13
13
|
import { getFirstApiUrl } from '@/lib/api-routes';
|
|
14
14
|
import { StatusCodes } from 'http-status-codes';
|
|
15
15
|
import { resolveDocsRedirect } from '@/lib/tree-utils';
|
|
@@ -44,10 +44,12 @@ export default {
|
|
|
44
44
|
: [];
|
|
45
45
|
const apiSpecs = apiConfigs.length ? await loadApiSpecs(apiConfigs) : [];
|
|
46
46
|
|
|
47
|
-
const [tree,
|
|
47
|
+
const [tree, rawPage] = await Promise.all([
|
|
48
48
|
getPageTree(),
|
|
49
49
|
route.type === RouteType.DocsPage ? getPage(route.slug) : Promise.resolve(null),
|
|
50
50
|
]);
|
|
51
|
+
const page = rawPage && isDraft(rawPage) ? null : rawPage;
|
|
52
|
+
|
|
51
53
|
// SSR redirects for index pages
|
|
52
54
|
if (route.type === RouteType.ApiIndex) {
|
|
53
55
|
const firstUrl = getFirstApiUrl(apiSpecs);
|
|
@@ -115,6 +117,8 @@ export default {
|
|
|
115
117
|
<head>
|
|
116
118
|
<meta charSet="UTF-8" />
|
|
117
119
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
120
|
+
<link rel="icon" href="/favicon.ico" />
|
|
121
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
118
122
|
{assets.css.map((attr: { href: string }) => (
|
|
119
123
|
<link key={attr.href} rel="stylesheet" {...attr} />
|
|
120
124
|
))}
|
|
@@ -3,11 +3,15 @@ import matter from 'gray-matter';
|
|
|
3
3
|
import { defineHandler, HTTPError } from 'nitro';
|
|
4
4
|
import { getPage, getOriginalPath } from '@/lib/source';
|
|
5
5
|
import { safePath } from '@/server/utils/safe-path';
|
|
6
|
+
import { handleApiMarkdown } from '@/server/utils/api-markdown';
|
|
6
7
|
|
|
7
8
|
export default defineHandler(async event => {
|
|
8
9
|
const pathname = event.path || event.req.url?.split('?')[0] || '';
|
|
9
10
|
if (!pathname.endsWith('.md')) return;
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
if (pathname.startsWith('/apis/')) {
|
|
13
|
+
return handleApiMarkdown(pathname);
|
|
14
|
+
}
|
|
11
15
|
|
|
12
16
|
const stripped = pathname.replace(/\.md$/, '');
|
|
13
17
|
const parts = stripped === '/index' || stripped === '/'
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import type { OpenAPIV3 } from 'openapi-types'
|
|
2
|
-
import {
|
|
2
|
+
import { HTTPError } from 'nitro'
|
|
3
3
|
import { loadConfig } from '@/lib/config'
|
|
4
4
|
import { loadApiSpecs } from '@/lib/openapi'
|
|
5
5
|
import { findApiOperation } from '@/lib/api-routes'
|
|
6
6
|
import { flattenSchema, generateExampleJson, type SchemaField } from '@/lib/schema'
|
|
7
7
|
import { generateCurl } from '@/lib/snippet-generators'
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
const pathname = event.path || event.req.url?.split('?')[0] || ''
|
|
11
|
-
if (!pathname.endsWith('.md')) return
|
|
12
|
-
|
|
9
|
+
export async function handleApiMarkdown(pathname: string) {
|
|
13
10
|
const stripped = pathname.replace(/\.md$/, '').replace(/^\/apis\//, '')
|
|
14
11
|
const slug = stripped.split('/').filter(Boolean)
|
|
15
12
|
if (slug.length < 2) {
|
|
@@ -26,7 +23,7 @@ export default defineHandler(async event => {
|
|
|
26
23
|
|
|
27
24
|
const md = generateApiMarkdown(match.method, match.path, match.operation, match.spec.server.url, match.spec.auth)
|
|
28
25
|
return new Response(md, { headers: { 'Content-Type': 'text/markdown; charset=utf-8' } })
|
|
29
|
-
}
|
|
26
|
+
}
|
|
30
27
|
|
|
31
28
|
function generateApiMarkdown(
|
|
32
29
|
method: string,
|
|
@@ -19,7 +19,7 @@ export function ContentDirButtons() {
|
|
|
19
19
|
const { visible, overflow } = splitContentButtons(entries, MAX_VISIBLE);
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<Flex gap=
|
|
22
|
+
<Flex gap={3} align='center'>
|
|
23
23
|
{visible.map(entry => (
|
|
24
24
|
<RouterLink
|
|
25
25
|
key={entry.href}
|
|
@@ -9,12 +9,13 @@ import {
|
|
|
9
9
|
import { Flex, IconButton, Button, Sidebar } from '@raystack/apsara';
|
|
10
10
|
import { PlayIcon } from '@radix-ui/react-icons';
|
|
11
11
|
import { cx } from 'class-variance-authority';
|
|
12
|
-
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
12
|
+
import { useState, useEffect, useMemo, useRef, lazy, Suspense } from 'react';
|
|
13
13
|
import { Link as RouterLink, useLocation, useNavigate } from 'react-router';
|
|
14
14
|
import type { OpenAPIV3 } from 'openapi-types';
|
|
15
15
|
import { MethodBadge } from '@/components/api/method-badge';
|
|
16
16
|
import { useApiOperation } from '@/lib/use-api-operation';
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
const PlaygroundDialog = lazy(() => import('@/components/api/playground-dialog').then(m => ({ default: m.PlaygroundDialog })));
|
|
18
19
|
import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher';
|
|
19
20
|
import { Search } from '@/components/ui/search';
|
|
20
21
|
import { Breadcrumbs } from '@/components/ui/breadcrumbs';
|
|
@@ -22,6 +23,7 @@ import { getLandingEntries } from '@/lib/config';
|
|
|
22
23
|
import { getActiveContentDir } from '@/lib/navigation';
|
|
23
24
|
import { usePageContext } from '@/lib/page-context';
|
|
24
25
|
import type { Node, Root } from 'fumadocs-core/page-tree';
|
|
26
|
+
import { NodeType } from '@/lib/tree-utils';
|
|
25
27
|
import type { ThemeLayoutProps } from '@/types';
|
|
26
28
|
import styles from './Layout.module.css';
|
|
27
29
|
import { OpenInAI } from './OpenInAI';
|
|
@@ -117,7 +119,7 @@ export function Layout({
|
|
|
117
119
|
>
|
|
118
120
|
<Sidebar.Header className={styles.sidebarHeader}>
|
|
119
121
|
<SidebarLogo config={config} />
|
|
120
|
-
<Flex gap=
|
|
122
|
+
<Flex gap={3} align='center'>
|
|
121
123
|
{config.search?.enabled && <Search />}
|
|
122
124
|
<ClientThemeSwitcher size={16} />
|
|
123
125
|
</Flex>
|
|
@@ -143,7 +145,7 @@ export function Layout({
|
|
|
143
145
|
))}
|
|
144
146
|
{apiEntries.map(api => (
|
|
145
147
|
<Sidebar.Item
|
|
146
|
-
key={api.basePath}
|
|
148
|
+
key={`${api.basePath}-${api.name}`}
|
|
147
149
|
href={api.basePath}
|
|
148
150
|
active={isApiBase(api.basePath)}
|
|
149
151
|
leadingIcon={renderConfigIcon(
|
|
@@ -186,8 +188,8 @@ export function Layout({
|
|
|
186
188
|
<div className={styles.cardWrapper}>
|
|
187
189
|
<div className={styles.card}>
|
|
188
190
|
<nav className={styles.subNav}>
|
|
189
|
-
<Flex align='center' gap=
|
|
190
|
-
<Flex align='center' gap=
|
|
191
|
+
<Flex align='center' gap={3} className={styles.subNavLeft}>
|
|
192
|
+
<Flex align='center' gap={2}>
|
|
191
193
|
<IconButton
|
|
192
194
|
size={2}
|
|
193
195
|
disabled={!prev}
|
|
@@ -207,7 +209,7 @@ export function Layout({
|
|
|
207
209
|
</Flex>
|
|
208
210
|
<Breadcrumbs slug={slug} tree={tree} />
|
|
209
211
|
</Flex>
|
|
210
|
-
<Flex align='center' gap=
|
|
212
|
+
<Flex align='center' gap={3}>
|
|
211
213
|
{isApiRoute && <TestRequestButton />}
|
|
212
214
|
{isApiRoute && <ViewDocsButton />}
|
|
213
215
|
<OpenInAI />
|
|
@@ -224,6 +226,15 @@ export function Layout({
|
|
|
224
226
|
);
|
|
225
227
|
}
|
|
226
228
|
|
|
229
|
+
function hasActiveDescendant(node: Node, pathname: string): boolean {
|
|
230
|
+
if (node.type === NodeType.Page) return pathname === node.url;
|
|
231
|
+
if (node.type === NodeType.Folder) {
|
|
232
|
+
if (node.index && pathname === node.index.url) return true;
|
|
233
|
+
return node.children.some(child => hasActiveDescendant(child, pathname));
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
|
|
227
238
|
function SidebarNode({
|
|
228
239
|
item,
|
|
229
240
|
pathname,
|
|
@@ -240,6 +251,7 @@ function SidebarNode({
|
|
|
240
251
|
if (item.type === 'folder') {
|
|
241
252
|
if (depth > 1) return null;
|
|
242
253
|
const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
|
|
254
|
+
const hasActiveChild = hasActiveDescendant(item, pathname);
|
|
243
255
|
return (
|
|
244
256
|
<Sidebar.Group
|
|
245
257
|
className={styles.navGroup}
|
|
@@ -247,6 +259,7 @@ function SidebarNode({
|
|
|
247
259
|
label={item.name?.toString() ?? ''}
|
|
248
260
|
leadingIcon={icon ?? undefined}
|
|
249
261
|
collapsible={depth === 1}
|
|
262
|
+
defaultOpen={hasActiveChild}
|
|
250
263
|
classNames={{
|
|
251
264
|
items: styles.groupItems,
|
|
252
265
|
header: styles.navGroupHeader,
|
|
@@ -305,7 +318,7 @@ function ApiSidebarNode({ item, pathname }: { item: Node; pathname: string }) {
|
|
|
305
318
|
|
|
306
319
|
if (item.type === 'folder') {
|
|
307
320
|
return (
|
|
308
|
-
<Flex direction='column' gap=
|
|
321
|
+
<Flex direction='column' gap={3} className={styles.apiGroup}>
|
|
309
322
|
<span className={styles.apiGroupLabel}>{item.name?.toString()}</span>
|
|
310
323
|
<Flex direction='column'>
|
|
311
324
|
{item.children.map((child, i) => (
|
|
@@ -329,7 +342,7 @@ function ApiSidebarNode({ item, pathname }: { item: Node; pathname: string }) {
|
|
|
329
342
|
return (
|
|
330
343
|
<Flex
|
|
331
344
|
align='center'
|
|
332
|
-
gap=
|
|
345
|
+
gap={3}
|
|
333
346
|
className={`${styles.apiItem} ${isActive ? styles.apiItemActive : ''}`}
|
|
334
347
|
render={<RouterLink to={href} />}
|
|
335
348
|
>
|
|
@@ -359,18 +372,22 @@ function TestRequestButton() {
|
|
|
359
372
|
>
|
|
360
373
|
Test request
|
|
361
374
|
</Button>
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
375
|
+
{open && (
|
|
376
|
+
<Suspense fallback={null}>
|
|
377
|
+
<PlaygroundDialog
|
|
378
|
+
key={`${match.spec.name}-${match.path}-${match.method}`}
|
|
379
|
+
open={open}
|
|
380
|
+
onOpenChange={setOpen}
|
|
381
|
+
method={match.method}
|
|
382
|
+
path={match.path}
|
|
383
|
+
operation={match.operation}
|
|
384
|
+
serverUrl={match.spec.server.url}
|
|
385
|
+
specName={match.spec.name}
|
|
386
|
+
auth={match.spec.auth}
|
|
387
|
+
document={match.spec.document}
|
|
388
|
+
/>
|
|
389
|
+
</Suspense>
|
|
390
|
+
)}
|
|
374
391
|
</>
|
|
375
392
|
);
|
|
376
393
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { Flex, Headline } from '@raystack/apsara';
|
|
4
|
+
import { lazy, Suspense } from 'react';
|
|
4
5
|
import type { ThemePageProps } from '@/types';
|
|
5
6
|
import styles from './Page.module.css';
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
const Toc = lazy(() => import('./Toc').then(m => ({ default: m.Toc })));
|
|
7
9
|
|
|
8
10
|
export function Page({ page }: ThemePageProps) {
|
|
9
11
|
return (
|
|
@@ -16,7 +18,9 @@ export function Page({ page }: ThemePageProps) {
|
|
|
16
18
|
)}
|
|
17
19
|
<div className={styles.content}>{page.content}</div>
|
|
18
20
|
</article>
|
|
19
|
-
<
|
|
21
|
+
<Suspense fallback={null}>
|
|
22
|
+
<Toc items={page.toc} />
|
|
23
|
+
</Suspense>
|
|
20
24
|
</Flex>
|
|
21
25
|
);
|
|
22
26
|
}
|
|
@@ -6,21 +6,11 @@ export function PageSkeleton() {
|
|
|
6
6
|
return (
|
|
7
7
|
<Flex className={styles.page}>
|
|
8
8
|
<article className={styles.article}>
|
|
9
|
-
<Skeleton width="40%" height="
|
|
10
|
-
<Skeleton
|
|
11
|
-
|
|
12
|
-
<Skeleton width="
|
|
13
|
-
|
|
14
|
-
<Skeleton width="100%" height="16px" />
|
|
15
|
-
<Skeleton width="60%" height="16px" />
|
|
16
|
-
</Skeleton.Provider>
|
|
17
|
-
<Skeleton width="30%" height="24px" />
|
|
18
|
-
<Skeleton.Provider duration={2}>
|
|
19
|
-
<Skeleton width="100%" height="16px" />
|
|
20
|
-
<Skeleton width="90%" height="16px" />
|
|
21
|
-
<Skeleton width="100%" height="16px" />
|
|
22
|
-
<Skeleton width="70%" height="16px" />
|
|
23
|
-
</Skeleton.Provider>
|
|
9
|
+
<Skeleton width="40%" height="var(--rs-line-height-t2)" containerClassName={styles.headerLoader} />
|
|
10
|
+
<Skeleton width="60%" height="var(--rs-line-height-regular)" containerClassName={styles.headerLoader} />
|
|
11
|
+
{[...new Array(20)].map((_, i) => (
|
|
12
|
+
<Skeleton key={i} width="100%" height="var(--rs-line-height-regular)" containerClassName={styles.loader} />
|
|
13
|
+
))}
|
|
24
14
|
</article>
|
|
25
15
|
</Flex>
|
|
26
16
|
);
|
|
@@ -28,7 +28,7 @@ export function VersionSwitcher() {
|
|
|
28
28
|
/>
|
|
29
29
|
}
|
|
30
30
|
>
|
|
31
|
-
<Flex gap=
|
|
31
|
+
<Flex gap={3} align='center'>
|
|
32
32
|
{active?.label ?? 'Version'}
|
|
33
33
|
{active?.badge ? (
|
|
34
34
|
<Badge variant={active.badge.variant} size='micro'>
|
|
@@ -43,7 +43,7 @@ export function VersionSwitcher() {
|
|
|
43
43
|
key={v.dir ?? '_latest'}
|
|
44
44
|
onClick={() => navigate(getVersionHomeHref(config, v.dir))}
|
|
45
45
|
>
|
|
46
|
-
<Flex gap=
|
|
46
|
+
<Flex gap={3} align='center'>
|
|
47
47
|
{v.label}
|
|
48
48
|
{v.badge ? (
|
|
49
49
|
<Badge variant={v.badge.variant} size='micro'>
|
|
@@ -29,7 +29,7 @@ export function VersionSwitcher() {
|
|
|
29
29
|
/>
|
|
30
30
|
}
|
|
31
31
|
>
|
|
32
|
-
<Flex gap=
|
|
32
|
+
<Flex gap={3} align='center' justify='start'>
|
|
33
33
|
{active?.label ?? 'Version'}
|
|
34
34
|
{active?.badge ? (
|
|
35
35
|
<Badge variant={active.badge.variant} size='micro'>
|
|
@@ -44,7 +44,7 @@ export function VersionSwitcher() {
|
|
|
44
44
|
key={v.dir ?? '_latest'}
|
|
45
45
|
onClick={() => navigate(getVersionHomeHref(config, v.dir))}
|
|
46
46
|
>
|
|
47
|
-
<Flex gap=
|
|
47
|
+
<Flex gap={3} align='center'>
|
|
48
48
|
{v.label}
|
|
49
49
|
{v.badge ? (
|
|
50
50
|
<Badge variant={v.badge.variant} size='micro'>
|
package/src/types/content.ts
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export { Sidebar } from './sidebar'
|
|
2
|
-
export { Table } from './table'
|
|
3
|
-
export { Dialog } from './dialog'
|
|
4
|
-
export { InputField } from './input-field'
|
|
5
|
-
export { Tabs } from './tabs'
|
|
6
|
-
export { Breadcrumb } from './breadcrumb'
|
|
7
|
-
export { Button } from './button'
|
|
8
|
-
export { CodeBlock } from './code-block'
|
|
9
|
-
export { Callout } from './callout'
|
|
10
|
-
export { Switch } from './switch'
|