@aravindc26/velu 0.10.0 → 0.11.1
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/package.json +15 -6
- package/schema/velu.schema.json +1864 -30
- package/src/build.ts +1161 -180
- package/src/cli.ts +121 -16
- package/src/engine/_server.mjs +1708 -192
- package/src/engine/app/(docs)/[...slug]/layout.tsx +377 -0
- package/src/engine/app/(docs)/[...slug]/page.tsx +917 -0
- package/src/engine/app/(docs)/layout.tsx +1 -13
- package/src/engine/app/api/proxy/route.ts +23 -0
- package/src/engine/app/copy-page.css +59 -1
- package/src/engine/app/global.css +3487 -6
- package/src/engine/app/layout.tsx +59 -8
- package/src/engine/app/llms-file/route.ts +87 -0
- package/src/engine/app/llms-full-file/route.ts +62 -0
- package/src/engine/app/md-file/[...slug]/route.ts +409 -0
- package/src/engine/app/page.tsx +45 -0
- package/src/engine/app/robots.txt/route.ts +61 -0
- package/src/engine/app/rss-file/[...slug]/route.ts +176 -0
- package/src/engine/app/search.css +20 -0
- package/src/engine/app/sitemap.xml/route.ts +80 -0
- package/src/engine/components/assistant.tsx +16 -5
- package/src/engine/components/changelog-filters.tsx +114 -0
- package/src/engine/components/code-group.tsx +383 -0
- package/src/engine/components/color.tsx +118 -0
- package/src/engine/components/expandable.tsx +77 -0
- package/src/engine/components/icon.tsx +136 -0
- package/src/engine/components/image-zoom-fallback.tsx +147 -0
- package/src/engine/components/image.tsx +111 -0
- package/src/engine/components/lang-switcher.tsx +95 -0
- package/src/engine/components/manual-api-playground.tsx +154 -0
- package/src/engine/components/mermaid.tsx +142 -0
- package/src/engine/components/openapi-toc-sync.tsx +59 -0
- package/src/engine/components/openapi.tsx +1679 -0
- package/src/engine/components/page-feedback.tsx +153 -0
- package/src/engine/components/product-switcher.tsx +102 -0
- package/src/engine/components/prompt.tsx +90 -0
- package/src/engine/components/providers.tsx +21 -0
- package/src/engine/components/search.tsx +70 -3
- package/src/engine/components/sidebar-links.tsx +49 -0
- package/src/engine/components/synced-tabs.tsx +57 -0
- package/src/engine/components/theme-toggle.tsx +39 -0
- package/src/engine/components/toc-examples.tsx +110 -0
- package/src/engine/components/version-switcher.tsx +89 -0
- package/src/engine/components/view.tsx +344 -0
- package/src/engine/generated/redirects.ts +3 -0
- package/src/engine/lib/changelog.ts +246 -0
- package/src/engine/lib/layout.shared.ts +57 -7
- package/src/engine/lib/llms.ts +444 -0
- package/src/engine/lib/navigation-normalize.mjs +525 -0
- package/src/engine/lib/navigation-normalize.ts +695 -0
- package/src/engine/lib/redirects.ts +194 -0
- package/src/engine/lib/source.ts +121 -4
- package/src/engine/lib/velu.ts +635 -5
- package/src/engine/mdx-components.tsx +648 -0
- package/src/engine/middleware.ts +66 -0
- package/src/engine/next.config.mjs +2 -2
- package/src/engine/public/icons/cursor-dark.svg +12 -0
- package/src/engine/public/icons/cursor-light.svg +12 -0
- package/src/engine/source.config.ts +98 -1
- package/src/engine/src/components/PageTitle.astro +16 -5
- package/src/engine/src/lib/velu.ts +97 -16
- package/src/navigation-normalize.ts +686 -0
- package/src/themes.ts +6 -6
- package/src/validate.ts +235 -24
- package/src/engine/app/(docs)/[[...slug]]/page.tsx +0 -69
package/src/themes.ts
CHANGED
|
@@ -129,11 +129,11 @@ function generateThemeCss(config: ThemeConfig): string {
|
|
|
129
129
|
if (lightAccent) {
|
|
130
130
|
const palette = deriveAccentPalette(lightAccent);
|
|
131
131
|
lines.push(":root {");
|
|
132
|
-
lines.push(` --color-fd-primary: ${
|
|
133
|
-
lines.push(` --color-fd-primary-foreground: ${textColorFor(
|
|
132
|
+
lines.push(` --color-fd-primary: ${lightAccent};`);
|
|
133
|
+
lines.push(` --color-fd-primary-foreground: ${textColorFor(lightAccent)};`);
|
|
134
134
|
lines.push(` --color-fd-accent: ${palette.light.accentLow};`);
|
|
135
135
|
lines.push(` --color-fd-accent-foreground: ${textColorFor(palette.light.accentLow)};`);
|
|
136
|
-
lines.push(` --color-fd-ring: ${
|
|
136
|
+
lines.push(` --color-fd-ring: ${lightAccent};`);
|
|
137
137
|
lines.push("}");
|
|
138
138
|
lines.push("");
|
|
139
139
|
}
|
|
@@ -141,11 +141,11 @@ function generateThemeCss(config: ThemeConfig): string {
|
|
|
141
141
|
if (darkAccent) {
|
|
142
142
|
const palette = deriveAccentPalette(darkAccent);
|
|
143
143
|
lines.push(".dark {");
|
|
144
|
-
lines.push(` --color-fd-primary: ${
|
|
145
|
-
lines.push(` --color-fd-primary-foreground: ${textColorFor(
|
|
144
|
+
lines.push(` --color-fd-primary: ${darkAccent};`);
|
|
145
|
+
lines.push(` --color-fd-primary-foreground: ${textColorFor(darkAccent)};`);
|
|
146
146
|
lines.push(` --color-fd-accent: ${palette.dark.accentLow};`);
|
|
147
147
|
lines.push(` --color-fd-accent-foreground: ${textColorFor(palette.dark.accentLow)};`);
|
|
148
|
-
lines.push(` --color-fd-ring: ${
|
|
148
|
+
lines.push(` --color-fd-ring: ${darkAccent};`);
|
|
149
149
|
lines.push("}");
|
|
150
150
|
lines.push("");
|
|
151
151
|
}
|
package/src/validate.ts
CHANGED
|
@@ -2,57 +2,240 @@ import Ajv, { type AnySchema } from "ajv";
|
|
|
2
2
|
import addFormats from "ajv-formats";
|
|
3
3
|
import { readFileSync, existsSync } from "node:fs";
|
|
4
4
|
import { resolve, join } from "node:path";
|
|
5
|
+
import { normalizeConfigNavigation } from "./navigation-normalize.js";
|
|
6
|
+
const PRIMARY_CONFIG_NAME = "docs.json";
|
|
7
|
+
const LEGACY_CONFIG_NAME = "velu.json";
|
|
8
|
+
|
|
9
|
+
function resolveConfigPath(docsDir: string): string | null {
|
|
10
|
+
const primary = join(docsDir, PRIMARY_CONFIG_NAME);
|
|
11
|
+
if (existsSync(primary)) return primary;
|
|
12
|
+
const legacy = join(docsDir, LEGACY_CONFIG_NAME);
|
|
13
|
+
if (existsSync(legacy)) return legacy;
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface VeluSeparator {
|
|
18
|
+
separator: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface VeluLink {
|
|
22
|
+
href: string;
|
|
23
|
+
label: string;
|
|
24
|
+
icon?: string;
|
|
25
|
+
iconType?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface VeluAnchor {
|
|
29
|
+
anchor: string;
|
|
30
|
+
href?: string;
|
|
31
|
+
icon?: string;
|
|
32
|
+
iconType?: string;
|
|
33
|
+
version?: string;
|
|
34
|
+
openapi?: VeluOpenApiSource;
|
|
35
|
+
asyncapi?: VeluOpenApiSource;
|
|
36
|
+
color?: {
|
|
37
|
+
light: string;
|
|
38
|
+
dark: string;
|
|
39
|
+
};
|
|
40
|
+
tabs?: VeluTab[];
|
|
41
|
+
hidden?: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface VeluGlobalTab {
|
|
45
|
+
tab: string;
|
|
46
|
+
href: string;
|
|
47
|
+
icon?: string;
|
|
48
|
+
iconType?: string;
|
|
49
|
+
}
|
|
5
50
|
|
|
6
51
|
interface VeluGroup {
|
|
7
52
|
group: string;
|
|
8
|
-
slug
|
|
53
|
+
slug?: string;
|
|
9
54
|
icon?: string;
|
|
55
|
+
iconType?: string;
|
|
10
56
|
tag?: string;
|
|
57
|
+
version?: string;
|
|
58
|
+
openapi?: VeluOpenApiSource;
|
|
59
|
+
asyncapi?: VeluOpenApiSource;
|
|
11
60
|
expanded?: boolean;
|
|
12
|
-
|
|
61
|
+
description?: string;
|
|
62
|
+
hidden?: boolean;
|
|
63
|
+
pages: (string | VeluGroup | VeluSeparator | VeluLink)[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface VeluMenuItem {
|
|
67
|
+
item: string;
|
|
68
|
+
icon?: string;
|
|
69
|
+
iconType?: string;
|
|
70
|
+
openapi?: VeluOpenApiSource;
|
|
71
|
+
asyncapi?: VeluOpenApiSource;
|
|
72
|
+
groups?: VeluGroup[];
|
|
73
|
+
pages?: (string | VeluSeparator | VeluLink)[];
|
|
13
74
|
}
|
|
14
75
|
|
|
15
76
|
interface VeluTab {
|
|
16
77
|
tab: string;
|
|
17
|
-
slug
|
|
78
|
+
slug?: string;
|
|
18
79
|
icon?: string;
|
|
80
|
+
iconType?: string;
|
|
81
|
+
version?: string;
|
|
19
82
|
href?: string;
|
|
20
|
-
|
|
83
|
+
openapi?: VeluOpenApiSource;
|
|
84
|
+
asyncapi?: VeluOpenApiSource;
|
|
85
|
+
pages?: (string | VeluSeparator | VeluLink)[];
|
|
21
86
|
groups?: VeluGroup[];
|
|
87
|
+
menu?: VeluMenuItem[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface VeluLanguageNav {
|
|
91
|
+
language: string;
|
|
92
|
+
openapi?: VeluOpenApiSource;
|
|
93
|
+
asyncapi?: VeluOpenApiSource;
|
|
94
|
+
tabs: VeluTab[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface VeluProductNav {
|
|
98
|
+
product: string;
|
|
99
|
+
icon?: string;
|
|
100
|
+
iconType?: string;
|
|
101
|
+
openapi?: VeluOpenApiSource;
|
|
102
|
+
asyncapi?: VeluOpenApiSource;
|
|
103
|
+
tabs?: VeluTab[];
|
|
104
|
+
pages?: (string | VeluSeparator | VeluLink)[];
|
|
22
105
|
}
|
|
23
106
|
|
|
107
|
+
interface VeluVersionNav {
|
|
108
|
+
version: string;
|
|
109
|
+
openapi?: VeluOpenApiSource;
|
|
110
|
+
asyncapi?: VeluOpenApiSource;
|
|
111
|
+
tabs: VeluTab[];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type VeluOpenApiSource = string | string[] | Record<string, unknown>;
|
|
115
|
+
|
|
24
116
|
interface VeluConfig {
|
|
25
117
|
$schema?: string;
|
|
118
|
+
icons?: {
|
|
119
|
+
library?: "fontawesome" | "lucide" | "tabler";
|
|
120
|
+
};
|
|
26
121
|
theme?: string;
|
|
27
122
|
colors?: { primary?: string; light?: string; dark?: string };
|
|
28
123
|
appearance?: "system" | "light" | "dark";
|
|
29
124
|
styling?: { codeblocks?: { theme?: string | { light: string; dark: string } } };
|
|
125
|
+
openapi?: VeluOpenApiSource;
|
|
126
|
+
asyncapi?: VeluOpenApiSource;
|
|
127
|
+
api?: {
|
|
128
|
+
baseUrl?: string;
|
|
129
|
+
playground?: {
|
|
130
|
+
mode?: string;
|
|
131
|
+
display?: string;
|
|
132
|
+
proxy?: boolean;
|
|
133
|
+
};
|
|
134
|
+
examples?: {
|
|
135
|
+
languages?: string[];
|
|
136
|
+
defaults?: "required" | "all";
|
|
137
|
+
prefill?: boolean;
|
|
138
|
+
autogenerate?: boolean;
|
|
139
|
+
};
|
|
140
|
+
mdx?: {
|
|
141
|
+
server?: string;
|
|
142
|
+
auth?: {
|
|
143
|
+
method?: "bearer" | "basic" | "key" | "none";
|
|
144
|
+
name?: string;
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
};
|
|
30
148
|
navigation: {
|
|
31
|
-
|
|
149
|
+
openapi?: VeluOpenApiSource;
|
|
150
|
+
asyncapi?: VeluOpenApiSource;
|
|
151
|
+
tabs?: VeluTab[];
|
|
152
|
+
languages?: VeluLanguageNav[];
|
|
153
|
+
products?: VeluProductNav[];
|
|
154
|
+
versions?: VeluVersionNav[];
|
|
155
|
+
anchors?: VeluAnchor[];
|
|
156
|
+
global?: {
|
|
157
|
+
anchors?: VeluAnchor[];
|
|
158
|
+
tabs?: VeluGlobalTab[];
|
|
159
|
+
};
|
|
32
160
|
};
|
|
33
161
|
}
|
|
34
162
|
|
|
163
|
+
const HTTP_METHODS = new Set([
|
|
164
|
+
"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD", "TRACE", "CONNECT", "WEBHOOK",
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
function isOpenApiOperationReference(value: string): boolean {
|
|
168
|
+
const trimmed = value.trim();
|
|
169
|
+
if (!trimmed) return false;
|
|
170
|
+
const withSpec = trimmed.match(/^(\S+)\s+([A-Za-z]+)\s+(.+)$/);
|
|
171
|
+
if (withSpec) {
|
|
172
|
+
const method = withSpec[2].toUpperCase();
|
|
173
|
+
const endpoint = withSpec[3].trim();
|
|
174
|
+
if (!HTTP_METHODS.has(method)) return false;
|
|
175
|
+
if (method === "WEBHOOK") return endpoint.length > 0;
|
|
176
|
+
return endpoint.startsWith("/");
|
|
177
|
+
}
|
|
178
|
+
const noSpec = trimmed.match(/^([A-Za-z]+)\s+(.+)$/);
|
|
179
|
+
if (!noSpec) return false;
|
|
180
|
+
const method = noSpec[1].toUpperCase();
|
|
181
|
+
const endpoint = noSpec[2].trim();
|
|
182
|
+
if (!HTTP_METHODS.has(method)) return false;
|
|
183
|
+
if (method === "WEBHOOK") return endpoint.length > 0;
|
|
184
|
+
return endpoint.startsWith("/");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isAsyncApiChannelReference(value: string): boolean {
|
|
188
|
+
const trimmed = value.trim();
|
|
189
|
+
if (!trimmed) return false;
|
|
190
|
+
const withSpec = trimmed.match(/^(\S+)\s+(.+)$/);
|
|
191
|
+
if (!withSpec) return false;
|
|
192
|
+
const first = withSpec[1].trim();
|
|
193
|
+
const maybeMethod = first.toUpperCase();
|
|
194
|
+
if (HTTP_METHODS.has(maybeMethod)) return false;
|
|
195
|
+
const looksLikeSpec =
|
|
196
|
+
first.startsWith('/') ||
|
|
197
|
+
first.startsWith('./') ||
|
|
198
|
+
first.startsWith('../') ||
|
|
199
|
+
/^https?:\/\//i.test(first) ||
|
|
200
|
+
first.endsWith('.json') ||
|
|
201
|
+
first.endsWith('.yaml') ||
|
|
202
|
+
first.endsWith('.yml');
|
|
203
|
+
return looksLikeSpec && withSpec[2].trim().length > 0;
|
|
204
|
+
}
|
|
205
|
+
|
|
35
206
|
function loadJson(filePath: string): unknown {
|
|
36
207
|
const raw = readFileSync(filePath, "utf-8");
|
|
37
208
|
return JSON.parse(raw);
|
|
38
209
|
}
|
|
39
210
|
|
|
40
|
-
function
|
|
211
|
+
function isGroup(item: unknown): item is VeluGroup {
|
|
212
|
+
return typeof item === "object" && item !== null && "group" in item;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isPageString(item: unknown): item is string {
|
|
216
|
+
return typeof item === "string";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function collectPagesFromTabs(tabs: VeluTab[]): string[] {
|
|
41
220
|
const pages: string[] = [];
|
|
42
221
|
|
|
43
222
|
function collectFromGroup(group: VeluGroup) {
|
|
44
223
|
for (const item of group.pages) {
|
|
45
|
-
if (
|
|
224
|
+
if (isPageString(item)) {
|
|
46
225
|
pages.push(item);
|
|
47
|
-
} else {
|
|
226
|
+
} else if (isGroup(item)) {
|
|
48
227
|
collectFromGroup(item);
|
|
49
228
|
}
|
|
50
229
|
}
|
|
51
230
|
}
|
|
52
231
|
|
|
53
|
-
for (const tab of
|
|
232
|
+
for (const tab of tabs) {
|
|
54
233
|
if (tab.pages) {
|
|
55
|
-
|
|
234
|
+
for (const item of tab.pages) {
|
|
235
|
+
if (isPageString(item)) {
|
|
236
|
+
pages.push(item);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
56
239
|
}
|
|
57
240
|
if (tab.groups) {
|
|
58
241
|
for (const group of tab.groups) {
|
|
@@ -64,12 +247,22 @@ function collectPages(config: VeluConfig): string[] {
|
|
|
64
247
|
return pages;
|
|
65
248
|
}
|
|
66
249
|
|
|
250
|
+
function collectPages(config: VeluConfig): string[] {
|
|
251
|
+
const tabs = config.navigation.languages && config.navigation.languages.length > 0
|
|
252
|
+
? config.navigation.languages.flatMap((lang) => lang.tabs)
|
|
253
|
+
: (config.navigation.tabs ?? []);
|
|
254
|
+
return collectPagesFromTabs(tabs);
|
|
255
|
+
}
|
|
256
|
+
|
|
67
257
|
function validateVeluConfig(docsDir: string, schemaPath: string): { valid: boolean; errors: string[] } {
|
|
68
258
|
const errors: string[] = [];
|
|
69
259
|
|
|
70
|
-
const configPath =
|
|
71
|
-
if (!
|
|
72
|
-
return {
|
|
260
|
+
const configPath = resolveConfigPath(docsDir);
|
|
261
|
+
if (!configPath) {
|
|
262
|
+
return {
|
|
263
|
+
valid: false,
|
|
264
|
+
errors: [`docs.json or velu.json not found at ${join(docsDir, PRIMARY_CONFIG_NAME)}`],
|
|
265
|
+
};
|
|
73
266
|
}
|
|
74
267
|
|
|
75
268
|
if (!existsSync(schemaPath)) {
|
|
@@ -77,13 +270,13 @@ function validateVeluConfig(docsDir: string, schemaPath: string): { valid: boole
|
|
|
77
270
|
}
|
|
78
271
|
|
|
79
272
|
const schema = loadJson(schemaPath) as AnySchema;
|
|
80
|
-
const
|
|
273
|
+
const rawConfig = loadJson(configPath) as VeluConfig;
|
|
81
274
|
|
|
82
275
|
// Validate against JSON schema
|
|
83
276
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
84
277
|
addFormats(ajv);
|
|
85
278
|
const validate = ajv.compile(schema);
|
|
86
|
-
const schemaValid = validate(
|
|
279
|
+
const schemaValid = validate(rawConfig);
|
|
87
280
|
|
|
88
281
|
if (!schemaValid && validate.errors) {
|
|
89
282
|
for (const err of validate.errors) {
|
|
@@ -91,25 +284,43 @@ function validateVeluConfig(docsDir: string, schemaPath: string): { valid: boole
|
|
|
91
284
|
}
|
|
92
285
|
}
|
|
93
286
|
|
|
94
|
-
|
|
287
|
+
const config = normalizeConfigNavigation(rawConfig);
|
|
288
|
+
|
|
289
|
+
// Validate that all referenced page files exist (.mdx or .md)
|
|
95
290
|
const pages = collectPages(config);
|
|
96
291
|
for (const page of pages) {
|
|
292
|
+
if (isOpenApiOperationReference(page)) continue;
|
|
293
|
+
if (isAsyncApiChannelReference(page)) continue;
|
|
294
|
+
const mdxPath = join(docsDir, `${page}.mdx`);
|
|
97
295
|
const mdPath = join(docsDir, `${page}.md`);
|
|
98
|
-
if (!existsSync(mdPath)) {
|
|
99
|
-
errors.push(`Missing page: ${page}.md (expected at ${mdPath})`);
|
|
296
|
+
if (!existsSync(mdxPath) && !existsSync(mdPath)) {
|
|
297
|
+
errors.push(`Missing page: ${page}.md or ${page}.mdx (expected at ${mdPath})`);
|
|
100
298
|
}
|
|
101
299
|
}
|
|
102
300
|
|
|
103
301
|
// Check for duplicate page references
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
302
|
+
if (config.navigation.languages && config.navigation.languages.length > 0) {
|
|
303
|
+
for (const lang of config.navigation.languages) {
|
|
304
|
+
const seen = new Set<string>();
|
|
305
|
+
const langPages = collectPagesFromTabs(lang.tabs);
|
|
306
|
+
for (const page of langPages) {
|
|
307
|
+
if (seen.has(page)) {
|
|
308
|
+
errors.push(`Duplicate page reference in language '${lang.language}': ${page}`);
|
|
309
|
+
}
|
|
310
|
+
seen.add(page);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
const seen = new Set<string>();
|
|
315
|
+
for (const page of pages) {
|
|
316
|
+
if (seen.has(page)) {
|
|
317
|
+
errors.push(`Duplicate page reference: ${page}`);
|
|
318
|
+
}
|
|
319
|
+
seen.add(page);
|
|
108
320
|
}
|
|
109
|
-
seen.add(page);
|
|
110
321
|
}
|
|
111
322
|
|
|
112
323
|
return { valid: errors.length === 0, errors };
|
|
113
324
|
}
|
|
114
325
|
|
|
115
|
-
export { validateVeluConfig, collectPages, VeluConfig, VeluGroup, VeluTab };
|
|
326
|
+
export { validateVeluConfig, collectPages, VeluConfig, VeluGroup, VeluTab, VeluSeparator, VeluLink, VeluAnchor };
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { Metadata } from 'next';
|
|
2
|
-
import { notFound } from 'next/navigation';
|
|
3
|
-
import { createRelativeLink } from 'fumadocs-ui/mdx';
|
|
4
|
-
import {
|
|
5
|
-
DocsBody,
|
|
6
|
-
DocsDescription,
|
|
7
|
-
DocsPage,
|
|
8
|
-
DocsTitle,
|
|
9
|
-
} from 'fumadocs-ui/layouts/docs/page';
|
|
10
|
-
import { getMDXComponents } from '@/mdx-components';
|
|
11
|
-
import { source } from '@/lib/source';
|
|
12
|
-
import { CopyPageButton } from '@/components/copy-page';
|
|
13
|
-
|
|
14
|
-
interface RouteParams {
|
|
15
|
-
slug?: string[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface PageProps {
|
|
19
|
-
params: Promise<RouteParams>;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export default async function Page({ params }: PageProps) {
|
|
23
|
-
const resolvedParams = await params;
|
|
24
|
-
const page = source.getPage(resolvedParams.slug);
|
|
25
|
-
|
|
26
|
-
if (!page) notFound();
|
|
27
|
-
|
|
28
|
-
const MDX = page.data.body;
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<DocsPage toc={page.data.toc} full={page.data.full}>
|
|
32
|
-
<div data-pagefind-body data-pagefind-meta={`title:${page.data.title}`}>
|
|
33
|
-
<div className="velu-title-row">
|
|
34
|
-
<DocsTitle>{page.data.title}</DocsTitle>
|
|
35
|
-
<CopyPageButton />
|
|
36
|
-
</div>
|
|
37
|
-
{page.data.description ? <DocsDescription>{page.data.description}</DocsDescription> : null}
|
|
38
|
-
<DocsBody>
|
|
39
|
-
<MDX
|
|
40
|
-
components={getMDXComponents({
|
|
41
|
-
a: createRelativeLink(source, page),
|
|
42
|
-
})}
|
|
43
|
-
/>
|
|
44
|
-
</DocsBody>
|
|
45
|
-
</div>
|
|
46
|
-
<footer className="velu-footer">
|
|
47
|
-
Powered by <a href="https://getvelu.com" target="_blank" rel="noopener noreferrer">Velu</a>
|
|
48
|
-
</footer>
|
|
49
|
-
</DocsPage>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export async function generateStaticParams() {
|
|
54
|
-
const params = source.generateParams();
|
|
55
|
-
// Include root path for the optional catch-all [[...slug]]
|
|
56
|
-
return [{ slug: [] }, ...params];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
60
|
-
const resolvedParams = await params;
|
|
61
|
-
const page = source.getPage(resolvedParams.slug);
|
|
62
|
-
|
|
63
|
-
if (!page) notFound();
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
title: page.data.title,
|
|
67
|
-
description: page.data.description,
|
|
68
|
-
};
|
|
69
|
-
}
|