@raystack/chronicle 0.1.0-canary.6511afe → 0.1.0-canary.67113f8
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 +72 -18
- package/package.json +1 -1
- package/src/components/mdx/code.tsx +10 -1
- package/src/components/mdx/details.module.css +1 -26
- package/src/components/mdx/details.tsx +2 -3
- package/src/components/mdx/index.tsx +15 -1
- package/src/components/ui/breadcrumbs.tsx +8 -42
- package/src/lib/api-routes.ts +6 -8
- package/src/lib/mdx-loader.ts +21 -0
- package/src/lib/page-context.tsx +13 -16
- package/src/lib/source.ts +79 -105
- package/src/pages/DocsPage.tsx +1 -1
- package/src/server/api/page/[...slug].ts +5 -6
- package/src/server/entry-client.tsx +21 -25
- package/src/server/entry-server.tsx +28 -23
- package/src/server/routes/sitemap.xml.ts +3 -2
- package/src/server/vite-config.ts +45 -17
- package/src/themes/default/Layout.tsx +13 -10
- package/src/themes/default/Page.module.css +44 -0
- package/src/themes/default/Toc.tsx +14 -30
- package/src/themes/paper/ChapterNav.tsx +14 -14
- package/src/themes/paper/Page.module.css +5 -0
- package/src/themes/paper/Page.tsx +15 -41
- package/src/themes/paper/ReadingProgress.tsx +2 -2
- package/src/types/content.ts +5 -21
- package/src/types/globals.d.ts +0 -1
- package/src/types/theme.ts +4 -3
package/src/lib/source.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { loader } from 'fumadocs-core/source';
|
|
4
|
+
import type { Root, Node, Folder } from 'fumadocs-core/page-tree';
|
|
4
5
|
import matter from 'gray-matter';
|
|
5
6
|
import type { MDXContent } from 'mdx/types';
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
export interface SourcePage {
|
|
9
|
-
url: string;
|
|
10
|
-
slugs: string[];
|
|
11
|
-
filePath: string;
|
|
12
|
-
frontmatter: Frontmatter;
|
|
13
|
-
}
|
|
7
|
+
import type { TableOfContents } from 'fumadocs-core/toc';
|
|
8
|
+
import type { Frontmatter } from '@/types';
|
|
14
9
|
|
|
15
10
|
function getContentDir(): string {
|
|
16
11
|
return __CHRONICLE_CONTENT_DIR__ || path.join(process.cwd(), 'content');
|
|
@@ -43,14 +38,18 @@ async function scanFiles(contentDir: string) {
|
|
|
43
38
|
files.push({
|
|
44
39
|
type: 'page',
|
|
45
40
|
path: relativePath,
|
|
46
|
-
data: { ...data,
|
|
41
|
+
data: { ...data, _relativePath: relativePath }
|
|
47
42
|
});
|
|
48
43
|
} else if (entry.name === 'meta.json' || entry.name === 'meta.yaml') {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
try {
|
|
45
|
+
const raw = await fs.readFile(fullPath, 'utf-8');
|
|
46
|
+
const data = entry.name.endsWith('.json')
|
|
47
|
+
? JSON.parse(raw)
|
|
48
|
+
: matter(raw).data;
|
|
49
|
+
files.push({ type: 'meta', path: relativePath, data });
|
|
50
|
+
} catch {
|
|
51
|
+
/* malformed meta file */
|
|
52
|
+
}
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
} catch {
|
|
@@ -63,7 +62,6 @@ async function scanFiles(contentDir: string) {
|
|
|
63
62
|
}
|
|
64
63
|
|
|
65
64
|
let cachedSource: ReturnType<typeof loader> | null = null;
|
|
66
|
-
let cachedPages: SourcePage[] | null = null;
|
|
67
65
|
|
|
68
66
|
async function getSource() {
|
|
69
67
|
if (cachedSource) return cachedSource;
|
|
@@ -76,111 +74,87 @@ async function getSource() {
|
|
|
76
74
|
return cachedSource;
|
|
77
75
|
}
|
|
78
76
|
|
|
77
|
+
export { getSource as source };
|
|
78
|
+
|
|
79
79
|
export function invalidate() {
|
|
80
80
|
cachedSource = null;
|
|
81
|
-
cachedPages = null;
|
|
82
81
|
}
|
|
83
82
|
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
cachedPages = s.getPages().map(page => {
|
|
89
|
-
const data = page.data as Record<string, unknown>;
|
|
90
|
-
return {
|
|
91
|
-
url: page.url,
|
|
92
|
-
slugs: page.slugs,
|
|
93
|
-
filePath: (data._absolutePath as string) ?? '',
|
|
94
|
-
frontmatter: {
|
|
95
|
-
title:
|
|
96
|
-
(data.title as string) ??
|
|
97
|
-
page.slugs[page.slugs.length - 1] ??
|
|
98
|
-
'Untitled',
|
|
99
|
-
description: data.description as string | undefined,
|
|
100
|
-
order: data.order as number | undefined,
|
|
101
|
-
icon: data.icon as string | undefined,
|
|
102
|
-
lastModified: data.lastModified as string | undefined
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
return cachedPages;
|
|
83
|
+
function getOrder(node: Node, orderMap: Map<string, number>): number | undefined {
|
|
84
|
+
if (node.type === 'page') return orderMap.get(node.url);
|
|
85
|
+
if (node.type === 'folder' && node.index) return orderMap.get(node.index.url);
|
|
86
|
+
return undefined;
|
|
108
87
|
}
|
|
109
88
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
89
|
+
function sortNodes(nodes: Node[], orderMap: Map<string, number>): Node[] {
|
|
90
|
+
return [...nodes]
|
|
91
|
+
.map(n =>
|
|
92
|
+
n.type === 'folder'
|
|
93
|
+
? ({ ...n, children: sortNodes(n.children, orderMap) } as Folder)
|
|
94
|
+
: n
|
|
95
|
+
)
|
|
96
|
+
.sort(
|
|
97
|
+
(a, b) =>
|
|
98
|
+
(getOrder(a, orderMap) ?? Number.MAX_SAFE_INTEGER) -
|
|
99
|
+
(getOrder(b, orderMap) ?? Number.MAX_SAFE_INTEGER)
|
|
100
|
+
);
|
|
114
101
|
}
|
|
115
102
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return null;
|
|
103
|
+
function sortTreeByOrder(tree: Root, pages: { url: string; data: unknown }[]): Root {
|
|
104
|
+
const orderMap = new Map<string, number>();
|
|
105
|
+
for (const page of pages) {
|
|
106
|
+
const d = page.data as Record<string, unknown>;
|
|
107
|
+
const order = d.order as number | undefined;
|
|
108
|
+
if (order !== undefined) orderMap.set(page.url, order);
|
|
109
|
+
if (page.url === '/') orderMap.set('/', order ?? 0);
|
|
124
110
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return mod.default;
|
|
111
|
+
return { ...tree, children: sortNodes(tree.children, orderMap) };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function getPageTree(): Promise<Root> {
|
|
115
|
+
const s = await getSource();
|
|
116
|
+
return sortTreeByOrder(s.pageTree as Root, s.getPages());
|
|
132
117
|
}
|
|
133
118
|
|
|
134
|
-
export async function
|
|
119
|
+
export async function getPages() {
|
|
135
120
|
const s = await getSource();
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const rootPages: PageTreeItem[] = [];
|
|
121
|
+
return s.getPages();
|
|
122
|
+
}
|
|
139
123
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
type: 'page',
|
|
145
|
-
name: (data.title as string) ?? page.slugs.join('/') ?? 'Untitled',
|
|
146
|
-
url: page.url,
|
|
147
|
-
order: (data.order as number | undefined) ?? (isIndex ? 0 : undefined)
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
if (page.slugs.length > 1) {
|
|
151
|
-
const folder = page.slugs[0];
|
|
152
|
-
if (!folders.has(folder)) {
|
|
153
|
-
folders.set(folder, []);
|
|
154
|
-
}
|
|
155
|
-
folders.get(folder)?.push(item);
|
|
156
|
-
} else {
|
|
157
|
-
rootPages.push(item);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
124
|
+
export async function getPage(slugs?: string[]) {
|
|
125
|
+
const s = await getSource();
|
|
126
|
+
return s.getPage(slugs);
|
|
127
|
+
}
|
|
160
128
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
129
|
+
export function extractFrontmatter(page: { data: unknown }, fallbackTitle?: string): Frontmatter {
|
|
130
|
+
const d = page.data as Record<string, unknown>;
|
|
131
|
+
return {
|
|
132
|
+
title: (d.title as string) ?? fallbackTitle ?? 'Untitled',
|
|
133
|
+
description: d.description as string | undefined,
|
|
134
|
+
order: d.order as number | undefined,
|
|
135
|
+
icon: d.icon as string | undefined,
|
|
136
|
+
lastModified: d.lastModified as string | undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
167
139
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
for (const [folder, items] of folders) {
|
|
172
|
-
const sorted = sortByOrder(items);
|
|
173
|
-
const indexPage = items.find(item => item.url === `/${folder}`);
|
|
174
|
-
const folderOrder = indexPage?.order ?? sorted[0]?.order;
|
|
175
|
-
folderItems.push({
|
|
176
|
-
type: 'folder',
|
|
177
|
-
name: `${folder.charAt(0).toUpperCase()}${folder.slice(1)}`,
|
|
178
|
-
order: folderOrder,
|
|
179
|
-
children: sorted
|
|
180
|
-
});
|
|
181
|
-
}
|
|
140
|
+
export function getRelativePath(page: { data: unknown }): string {
|
|
141
|
+
return ((page.data as Record<string, unknown>)._relativePath as string) ?? '';
|
|
142
|
+
}
|
|
182
143
|
|
|
183
|
-
|
|
144
|
+
const ssrModules = import.meta.glob<{ default?: MDXContent; toc?: TableOfContents }>(
|
|
145
|
+
'../../.content/**/*.{mdx,md}'
|
|
146
|
+
);
|
|
184
147
|
|
|
185
|
-
|
|
148
|
+
export async function loadPageModule(
|
|
149
|
+
relativePath: string
|
|
150
|
+
): Promise<{ default: MDXContent | null; toc: TableOfContents }> {
|
|
151
|
+
if (!relativePath || relativePath.includes('..')) return { default: null, toc: [] };
|
|
152
|
+
const withoutExt = relativePath.replace(/\.(mdx|md)$/, '');
|
|
153
|
+
const key = relativePath.endsWith('.md')
|
|
154
|
+
? `../../.content/${withoutExt}.md`
|
|
155
|
+
: `../../.content/${withoutExt}.mdx`;
|
|
156
|
+
const loader = ssrModules[key];
|
|
157
|
+
if (!loader) return { default: null, toc: [] };
|
|
158
|
+
const mod = await loader();
|
|
159
|
+
return { default: mod.default ?? null, toc: mod.toc ?? [] };
|
|
186
160
|
}
|
package/src/pages/DocsPage.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
1
|
import { defineHandler, HTTPError } from 'nitro';
|
|
3
|
-
import { getPage } from '@/lib/source';
|
|
2
|
+
import { getPage, extractFrontmatter, getRelativePath } from '@/lib/source';
|
|
4
3
|
|
|
5
4
|
export default defineHandler(async event => {
|
|
6
5
|
const slugParam = event.context.params?.slug ?? '';
|
|
@@ -11,8 +10,8 @@ export default defineHandler(async event => {
|
|
|
11
10
|
throw new HTTPError({ status: 404, message: 'Page not found' });
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
return {
|
|
14
|
+
frontmatter: extractFrontmatter(page, slug[slug.length - 1]),
|
|
15
|
+
relativePath: getRelativePath(page),
|
|
16
|
+
};
|
|
18
17
|
});
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import '@vitejs/plugin-react/preamble';
|
|
2
|
-
import React from 'react';
|
|
3
2
|
import { hydrateRoot } from 'react-dom/client';
|
|
4
3
|
import { BrowserRouter } from 'react-router';
|
|
5
|
-
import {
|
|
4
|
+
import { ReactRouterProvider } from 'fumadocs-core/framework/react-router';
|
|
5
|
+
import { loadMdxModule } from '@/lib/mdx-loader';
|
|
6
6
|
import { PageProvider } from '@/lib/page-context';
|
|
7
|
-
import type { ChronicleConfig, Frontmatter,
|
|
7
|
+
import type { ChronicleConfig, Frontmatter, Root } from '@/types';
|
|
8
8
|
import type { ApiSpec } from '@/lib/openapi';
|
|
9
|
-
import type { ReactNode } from 'react';
|
|
10
9
|
import { App } from './App';
|
|
11
10
|
|
|
12
11
|
interface EmbeddedData {
|
|
13
12
|
config: ChronicleConfig;
|
|
14
|
-
tree:
|
|
13
|
+
tree: Root;
|
|
15
14
|
slug: string[];
|
|
16
15
|
frontmatter: Frontmatter;
|
|
17
16
|
relativePath: string;
|
|
@@ -26,7 +25,7 @@ async function hydrate() {
|
|
|
26
25
|
const config: ChronicleConfig = embedded?.config ?? {
|
|
27
26
|
title: 'Documentation'
|
|
28
27
|
};
|
|
29
|
-
const tree:
|
|
28
|
+
const tree: Root = embedded?.tree ?? { name: 'root', children: [] };
|
|
30
29
|
const isApiPage =
|
|
31
30
|
window.location.pathname.startsWith('/apis') && !!config.api?.length;
|
|
32
31
|
const apiSpecs: ApiSpec[] = isApiPage
|
|
@@ -36,20 +35,27 @@ async function hydrate() {
|
|
|
36
35
|
: [];
|
|
37
36
|
|
|
38
37
|
const page = embedded?.relativePath
|
|
39
|
-
?
|
|
38
|
+
? {
|
|
39
|
+
slug: embedded.slug,
|
|
40
|
+
frontmatter: embedded.frontmatter,
|
|
41
|
+
...(await loadMdxModule(embedded.relativePath)),
|
|
42
|
+
}
|
|
40
43
|
: null;
|
|
41
44
|
|
|
42
45
|
hydrateRoot(
|
|
43
46
|
document.getElementById('root') as HTMLElement,
|
|
44
47
|
<BrowserRouter>
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
<ReactRouterProvider>
|
|
49
|
+
<PageProvider
|
|
50
|
+
initialConfig={config}
|
|
51
|
+
initialTree={tree}
|
|
52
|
+
initialPage={page}
|
|
53
|
+
initialApiSpecs={apiSpecs}
|
|
54
|
+
loadMdx={loadMdxModule}
|
|
55
|
+
>
|
|
56
|
+
<App />
|
|
57
|
+
</PageProvider>
|
|
58
|
+
</ReactRouterProvider>
|
|
53
59
|
</BrowserRouter>
|
|
54
60
|
);
|
|
55
61
|
} catch (err) {
|
|
@@ -57,14 +63,4 @@ async function hydrate() {
|
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
async function loadPage(
|
|
61
|
-
embedded: EmbeddedData
|
|
62
|
-
): Promise<{ slug: string[]; frontmatter: Frontmatter; content: ReactNode }> {
|
|
63
|
-
const mod = await import(/* @vite-ignore */ `/.content/${embedded.relativePath}`);
|
|
64
|
-
const content = mod.default
|
|
65
|
-
? React.createElement(mod.default, { components: mdxComponents })
|
|
66
|
-
: null;
|
|
67
|
-
return { slug: embedded.slug, frontmatter: embedded.frontmatter, content };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
66
|
hydrate();
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import '@raystack/apsara/normalize.css';
|
|
2
2
|
import '@raystack/apsara/style.css';
|
|
3
|
-
import path from 'node:path';
|
|
4
3
|
import React from 'react';
|
|
5
4
|
import { renderToReadableStream } from 'react-dom/server.edge';
|
|
6
5
|
import { StaticRouter } from 'react-router';
|
|
6
|
+
import { ReactRouterProvider } from 'fumadocs-core/framework/react-router';
|
|
7
7
|
import { mdxComponents } from '@/components/mdx';
|
|
8
8
|
import { loadConfig } from '@/lib/config';
|
|
9
9
|
import { loadApiSpecs } from '@/lib/openapi';
|
|
10
10
|
import { PageProvider } from '@/lib/page-context';
|
|
11
|
-
import {
|
|
11
|
+
import { getPageTree, getPage, loadPageModule, extractFrontmatter, getRelativePath } from '@/lib/source';
|
|
12
12
|
import { App } from './App';
|
|
13
13
|
|
|
14
|
-
// @ts-expect-error virtual import from Nitro
|
|
15
14
|
import clientAssets from './entry-client?assets=client';
|
|
16
|
-
// @ts-expect-error virtual import from Nitro
|
|
17
15
|
import serverAssets from './entry-server?assets=ssr';
|
|
18
16
|
|
|
19
17
|
export default {
|
|
@@ -27,25 +25,25 @@ export default {
|
|
|
27
25
|
? await loadApiSpecs(config.api).catch(() => [])
|
|
28
26
|
: [];
|
|
29
27
|
|
|
30
|
-
const [tree,
|
|
31
|
-
|
|
28
|
+
const [tree, page] = await Promise.all([
|
|
29
|
+
getPageTree(),
|
|
32
30
|
getPage(slug),
|
|
33
31
|
]);
|
|
34
32
|
|
|
35
|
-
const
|
|
33
|
+
const relativePath = page ? getRelativePath(page) : null;
|
|
34
|
+
const mdxModule = relativePath ? await loadPageModule(relativePath) : null;
|
|
35
|
+
|
|
36
|
+
const pageData = page
|
|
36
37
|
? {
|
|
37
38
|
slug,
|
|
38
|
-
frontmatter:
|
|
39
|
-
content:
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
frontmatter: extractFrontmatter(page, slug[slug.length - 1]),
|
|
40
|
+
content: mdxModule?.default
|
|
41
|
+
? React.createElement(mdxModule.default, { components: mdxComponents })
|
|
42
|
+
: null,
|
|
43
|
+
toc: mdxModule?.toc ?? [],
|
|
42
44
|
}
|
|
43
45
|
: null;
|
|
44
46
|
|
|
45
|
-
const relativePath = sourcePage
|
|
46
|
-
? path.relative(__CHRONICLE_CONTENT_DIR__, sourcePage.filePath)
|
|
47
|
-
: null;
|
|
48
|
-
|
|
49
47
|
const embeddedData = {
|
|
50
48
|
config,
|
|
51
49
|
tree,
|
|
@@ -74,21 +72,28 @@ export default {
|
|
|
74
72
|
<body>
|
|
75
73
|
<div id="root">
|
|
76
74
|
<StaticRouter location={pathname}>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
<ReactRouterProvider>
|
|
76
|
+
<PageProvider
|
|
77
|
+
initialConfig={config}
|
|
78
|
+
initialTree={tree}
|
|
79
|
+
initialPage={pageData}
|
|
80
|
+
initialApiSpecs={apiSpecs}
|
|
81
|
+
loadMdx={async () => ({ content: null, toc: [] })}
|
|
82
|
+
>
|
|
83
|
+
<App />
|
|
84
|
+
</PageProvider>
|
|
85
|
+
</ReactRouterProvider>
|
|
85
86
|
</StaticRouter>
|
|
86
87
|
</div>
|
|
87
88
|
</body>
|
|
88
89
|
</html>,
|
|
89
90
|
);
|
|
90
91
|
|
|
92
|
+
const isApiRoute = pathname.startsWith('/apis');
|
|
93
|
+
const status = !page && !isApiRoute && slug.length > 0 ? 404 : 200;
|
|
94
|
+
|
|
91
95
|
return new Response(stream, {
|
|
96
|
+
status,
|
|
92
97
|
headers: { 'Content-Type': 'text/html;charset=utf-8' },
|
|
93
98
|
});
|
|
94
99
|
},
|
|
@@ -16,8 +16,9 @@ export default defineHandler(async event => {
|
|
|
16
16
|
|
|
17
17
|
const pages = await getPages();
|
|
18
18
|
const docPages = pages.map(page => {
|
|
19
|
-
const
|
|
20
|
-
|
|
19
|
+
const data = page.data as Record<string, unknown>;
|
|
20
|
+
const lastmod = data.lastModified
|
|
21
|
+
? `<lastmod>${new Date(data.lastModified as string).toISOString()}</lastmod>`
|
|
21
22
|
: '';
|
|
22
23
|
return `<url><loc>${baseUrl}/${page.slugs.join('/')}</loc>${lastmod}</url>`;
|
|
23
24
|
});
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import react from '@vitejs/plugin-react';
|
|
2
|
+
import { remarkDirectiveAdmonition, remarkMdxMermaid } from 'fumadocs-core/mdx-plugins';
|
|
3
|
+
import { defineConfig as defineFumadocsConfig } from 'fumadocs-mdx/config';
|
|
2
4
|
import mdx from 'fumadocs-mdx/vite';
|
|
3
5
|
import { nitro } from 'nitro/vite';
|
|
4
6
|
import path from 'node:path';
|
|
7
|
+
import remarkDirective from 'remark-directive';
|
|
5
8
|
import { type InlineConfig } from 'vite';
|
|
9
|
+
import remarkUnusedDirectives from '../lib/remark-unused-directives';
|
|
10
|
+
|
|
11
|
+
function resolveOutputDir(projectRoot: string, preset?: string): string {
|
|
12
|
+
if (preset === 'vercel' || preset === 'vercel-static') return path.resolve(projectRoot, '.vercel/output');
|
|
13
|
+
return path.resolve(projectRoot, '.output');
|
|
14
|
+
}
|
|
6
15
|
|
|
7
16
|
export interface ViteConfigOptions {
|
|
8
17
|
packageRoot: string;
|
|
@@ -23,25 +32,40 @@ export async function createViteConfig(
|
|
|
23
32
|
nitro({
|
|
24
33
|
serverDir: path.resolve(packageRoot, 'src/server'),
|
|
25
34
|
...(preset && { preset }),
|
|
26
|
-
alias: {
|
|
27
|
-
'@content': path.resolve(packageRoot, '.content'),
|
|
28
|
-
},
|
|
29
35
|
}),
|
|
30
|
-
mdx({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
mdx({
|
|
37
|
+
default: defineFumadocsConfig({
|
|
38
|
+
mdxOptions: {
|
|
39
|
+
remarkPlugins: [
|
|
40
|
+
remarkDirective,
|
|
41
|
+
[remarkDirectiveAdmonition, {
|
|
42
|
+
tags: {
|
|
43
|
+
CalloutContainer: 'Callout',
|
|
44
|
+
CalloutTitle: 'CalloutTitle',
|
|
45
|
+
CalloutDescription: 'CalloutDescription',
|
|
46
|
+
},
|
|
47
|
+
types: {
|
|
48
|
+
note: 'accent',
|
|
49
|
+
tip: 'accent',
|
|
50
|
+
info: 'accent',
|
|
51
|
+
warn: 'attention',
|
|
52
|
+
warning: 'attention',
|
|
53
|
+
danger: 'alert',
|
|
54
|
+
caution: 'alert',
|
|
55
|
+
success: 'success',
|
|
56
|
+
},
|
|
57
|
+
}],
|
|
58
|
+
remarkUnusedDirectives,
|
|
59
|
+
remarkMdxMermaid,
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
}, { index: false }),
|
|
64
|
+
react()
|
|
40
65
|
],
|
|
41
66
|
resolve: {
|
|
42
67
|
alias: {
|
|
43
68
|
'@': path.resolve(packageRoot, 'src'),
|
|
44
|
-
'@content': path.resolve(packageRoot, '.content'),
|
|
45
69
|
},
|
|
46
70
|
conditions: ['module-sync', 'import', 'node'],
|
|
47
71
|
dedupe: [
|
|
@@ -59,8 +83,7 @@ export async function createViteConfig(
|
|
|
59
83
|
},
|
|
60
84
|
define: {
|
|
61
85
|
__CHRONICLE_CONTENT_DIR__: JSON.stringify(contentDir),
|
|
62
|
-
__CHRONICLE_PROJECT_ROOT__: JSON.stringify(projectRoot)
|
|
63
|
-
__CHRONICLE_PACKAGE_ROOT__: JSON.stringify(packageRoot)
|
|
86
|
+
__CHRONICLE_PROJECT_ROOT__: JSON.stringify(projectRoot)
|
|
64
87
|
},
|
|
65
88
|
css: {
|
|
66
89
|
modules: {
|
|
@@ -78,6 +101,11 @@ export async function createViteConfig(
|
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
103
|
}
|
|
81
|
-
}
|
|
104
|
+
},
|
|
105
|
+
nitro: {
|
|
106
|
+
output: {
|
|
107
|
+
dir: resolveOutputDir(projectRoot, preset),
|
|
108
|
+
},
|
|
109
|
+
},
|
|
82
110
|
};
|
|
83
111
|
}
|
|
@@ -14,7 +14,8 @@ import { MethodBadge } from '@/components/api/method-badge';
|
|
|
14
14
|
import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher';
|
|
15
15
|
import { Footer } from '@/components/ui/footer';
|
|
16
16
|
import { Search } from '@/components/ui/search';
|
|
17
|
-
import type {
|
|
17
|
+
import type { Node } from 'fumadocs-core/page-tree';
|
|
18
|
+
import type { ThemeLayoutProps } from '@/types';
|
|
18
19
|
import styles from './Layout.module.css';
|
|
19
20
|
|
|
20
21
|
const iconMap: Record<string, React.ReactNode> = {
|
|
@@ -96,9 +97,9 @@ export function Layout({
|
|
|
96
97
|
className={cx(styles.sidebar, classNames?.sidebar)}
|
|
97
98
|
>
|
|
98
99
|
<Sidebar.Main ref={scrollRef}>
|
|
99
|
-
{tree.children.map(item => (
|
|
100
|
+
{tree.children.map((item, i) => (
|
|
100
101
|
<SidebarNode
|
|
101
|
-
key={item.url
|
|
102
|
+
key={item.type === 'page' ? item.url : (item.name?.toString() ?? i)}
|
|
102
103
|
item={item}
|
|
103
104
|
pathname={pathname}
|
|
104
105
|
/>
|
|
@@ -118,23 +119,24 @@ function SidebarNode({
|
|
|
118
119
|
item,
|
|
119
120
|
pathname
|
|
120
121
|
}: {
|
|
121
|
-
item:
|
|
122
|
+
item: Node;
|
|
122
123
|
pathname: string;
|
|
123
124
|
}) {
|
|
124
125
|
if (item.type === 'separator') {
|
|
125
126
|
return null;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
if (item.type === 'folder'
|
|
129
|
+
if (item.type === 'folder') {
|
|
130
|
+
const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
|
|
129
131
|
return (
|
|
130
132
|
<Sidebar.Group
|
|
131
|
-
label={item.name}
|
|
132
|
-
leadingIcon={
|
|
133
|
+
label={item.name?.toString() ?? ''}
|
|
134
|
+
leadingIcon={icon ?? undefined}
|
|
133
135
|
classNames={{ items: styles.groupItems }}
|
|
134
136
|
>
|
|
135
|
-
{item.children.map(child => (
|
|
137
|
+
{item.children.map((child, i) => (
|
|
136
138
|
<SidebarNode
|
|
137
|
-
key={child.url
|
|
139
|
+
key={child.type === 'page' ? child.url : (child.name?.toString() ?? i)}
|
|
138
140
|
item={child}
|
|
139
141
|
pathname={pathname}
|
|
140
142
|
/>
|
|
@@ -145,13 +147,14 @@ function SidebarNode({
|
|
|
145
147
|
|
|
146
148
|
const isActive = pathname === item.url;
|
|
147
149
|
const href = item.url ?? '#';
|
|
150
|
+
const icon = typeof item.icon === 'string' ? iconMap[item.icon] : item.icon;
|
|
148
151
|
const link = useMemo(() => <RouterLink to={href} />, [href]);
|
|
149
152
|
|
|
150
153
|
return (
|
|
151
154
|
<Sidebar.Item
|
|
152
155
|
href={href}
|
|
153
156
|
active={isActive}
|
|
154
|
-
leadingIcon={
|
|
157
|
+
leadingIcon={icon ?? undefined}
|
|
155
158
|
as={link}
|
|
156
159
|
>
|
|
157
160
|
{item.name}
|
|
@@ -38,9 +38,53 @@
|
|
|
38
38
|
margin-bottom: var(--rs-space-3);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
.content img {
|
|
42
|
+
max-width: 100%;
|
|
43
|
+
height: auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
.content table {
|
|
42
47
|
display: block;
|
|
43
48
|
max-width: 100%;
|
|
44
49
|
overflow-x: auto;
|
|
45
50
|
margin-bottom: var(--rs-space-5);
|
|
46
51
|
}
|
|
52
|
+
|
|
53
|
+
.content details {
|
|
54
|
+
border: 1px solid var(--rs-color-border-base-primary);
|
|
55
|
+
border-radius: var(--rs-radius-2);
|
|
56
|
+
margin: var(--rs-space-5) 0;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.content details summary {
|
|
61
|
+
padding: var(--rs-space-4) var(--rs-space-5);
|
|
62
|
+
cursor: pointer;
|
|
63
|
+
font-weight: 500;
|
|
64
|
+
font-size: var(--rs-font-size-small);
|
|
65
|
+
color: var(--rs-color-text-base-primary);
|
|
66
|
+
background: var(--rs-color-background-base-secondary);
|
|
67
|
+
list-style: none;
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: var(--rs-space-3);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.content details summary::-webkit-details-marker {
|
|
74
|
+
display: none;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.content details summary::before {
|
|
78
|
+
content: '▶';
|
|
79
|
+
font-size: 10px;
|
|
80
|
+
transition: transform 0.2s ease;
|
|
81
|
+
color: var(--rs-color-text-base-secondary);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.content details[open] > summary::before {
|
|
85
|
+
transform: rotate(90deg);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.content details > :not(summary) {
|
|
89
|
+
padding: var(--rs-space-4) var(--rs-space-5);
|
|
90
|
+
}
|