@aravindc26/velu 0.11.0 → 0.11.3
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 +1251 -115
- package/src/build.ts +1121 -304
- package/src/cli.ts +90 -26
- package/src/engine/_server.mjs +1684 -277
- package/src/engine/app/(docs)/[...slug]/layout.tsx +371 -0
- package/src/engine/app/(docs)/[...slug]/page.tsx +926 -0
- 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 +3157 -3
- package/src/engine/app/layout.tsx +56 -1
- 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 +63 -0
- package/src/engine/app/rss-file/[...slug]/route.ts +169 -0
- package/src/engine/app/sitemap.xml/route.ts +82 -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/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 +1682 -0
- package/src/engine/components/page-feedback.tsx +153 -0
- package/src/engine/components/product-switcher.tsx +27 -3
- package/src/engine/components/prompt.tsx +90 -0
- package/src/engine/components/providers.tsx +1 -6
- package/src/engine/components/search.tsx +4 -0
- package/src/engine/components/sidebar-links.tsx +13 -15
- package/src/engine/components/synced-tabs.tsx +57 -0
- package/src/engine/components/toc-examples.tsx +110 -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 +30 -2
- package/src/engine/lib/llms.ts +444 -0
- package/src/engine/lib/navigation-normalize.mjs +481 -412
- package/src/engine/lib/navigation-normalize.ts +261 -54
- package/src/engine/lib/redirects.ts +194 -0
- package/src/engine/lib/source.ts +107 -4
- package/src/engine/lib/velu.ts +368 -2
- package/src/engine/mdx-components.tsx +648 -0
- package/src/engine/middleware.ts +66 -0
- 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 +11 -3
- package/src/navigation-normalize.ts +252 -54
- package/src/themes.ts +6 -6
- package/src/validate.ts +119 -6
- package/src/engine/app/(docs)/[[...slug]]/layout.tsx +0 -87
- package/src/engine/app/(docs)/[[...slug]]/page.tsx +0 -146
package/src/validate.ts
CHANGED
|
@@ -3,6 +3,16 @@ import addFormats from "ajv-formats";
|
|
|
3
3
|
import { readFileSync, existsSync } from "node:fs";
|
|
4
4
|
import { resolve, join } from "node:path";
|
|
5
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
|
+
}
|
|
6
16
|
|
|
7
17
|
interface VeluSeparator {
|
|
8
18
|
separator: string;
|
|
@@ -12,12 +22,17 @@ interface VeluLink {
|
|
|
12
22
|
href: string;
|
|
13
23
|
label: string;
|
|
14
24
|
icon?: string;
|
|
25
|
+
iconType?: string;
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
interface VeluAnchor {
|
|
18
29
|
anchor: string;
|
|
19
30
|
href?: string;
|
|
20
31
|
icon?: string;
|
|
32
|
+
iconType?: string;
|
|
33
|
+
version?: string;
|
|
34
|
+
openapi?: VeluOpenApiSource;
|
|
35
|
+
asyncapi?: VeluOpenApiSource;
|
|
21
36
|
color?: {
|
|
22
37
|
light: string;
|
|
23
38
|
dark: string;
|
|
@@ -30,13 +45,18 @@ interface VeluGlobalTab {
|
|
|
30
45
|
tab: string;
|
|
31
46
|
href: string;
|
|
32
47
|
icon?: string;
|
|
48
|
+
iconType?: string;
|
|
33
49
|
}
|
|
34
50
|
|
|
35
51
|
interface VeluGroup {
|
|
36
52
|
group: string;
|
|
37
53
|
slug?: string;
|
|
38
54
|
icon?: string;
|
|
55
|
+
iconType?: string;
|
|
39
56
|
tag?: string;
|
|
57
|
+
version?: string;
|
|
58
|
+
openapi?: VeluOpenApiSource;
|
|
59
|
+
asyncapi?: VeluOpenApiSource;
|
|
40
60
|
expanded?: boolean;
|
|
41
61
|
description?: string;
|
|
42
62
|
hidden?: boolean;
|
|
@@ -46,6 +66,9 @@ interface VeluGroup {
|
|
|
46
66
|
interface VeluMenuItem {
|
|
47
67
|
item: string;
|
|
48
68
|
icon?: string;
|
|
69
|
+
iconType?: string;
|
|
70
|
+
openapi?: VeluOpenApiSource;
|
|
71
|
+
asyncapi?: VeluOpenApiSource;
|
|
49
72
|
groups?: VeluGroup[];
|
|
50
73
|
pages?: (string | VeluSeparator | VeluLink)[];
|
|
51
74
|
}
|
|
@@ -54,7 +77,11 @@ interface VeluTab {
|
|
|
54
77
|
tab: string;
|
|
55
78
|
slug?: string;
|
|
56
79
|
icon?: string;
|
|
80
|
+
iconType?: string;
|
|
81
|
+
version?: string;
|
|
57
82
|
href?: string;
|
|
83
|
+
openapi?: VeluOpenApiSource;
|
|
84
|
+
asyncapi?: VeluOpenApiSource;
|
|
58
85
|
pages?: (string | VeluSeparator | VeluLink)[];
|
|
59
86
|
groups?: VeluGroup[];
|
|
60
87
|
menu?: VeluMenuItem[];
|
|
@@ -62,28 +89,65 @@ interface VeluTab {
|
|
|
62
89
|
|
|
63
90
|
interface VeluLanguageNav {
|
|
64
91
|
language: string;
|
|
92
|
+
openapi?: VeluOpenApiSource;
|
|
93
|
+
asyncapi?: VeluOpenApiSource;
|
|
65
94
|
tabs: VeluTab[];
|
|
66
95
|
}
|
|
67
96
|
|
|
68
97
|
interface VeluProductNav {
|
|
69
98
|
product: string;
|
|
70
99
|
icon?: string;
|
|
100
|
+
iconType?: string;
|
|
101
|
+
openapi?: VeluOpenApiSource;
|
|
102
|
+
asyncapi?: VeluOpenApiSource;
|
|
71
103
|
tabs?: VeluTab[];
|
|
72
104
|
pages?: (string | VeluSeparator | VeluLink)[];
|
|
73
105
|
}
|
|
74
106
|
|
|
75
107
|
interface VeluVersionNav {
|
|
76
108
|
version: string;
|
|
109
|
+
openapi?: VeluOpenApiSource;
|
|
110
|
+
asyncapi?: VeluOpenApiSource;
|
|
77
111
|
tabs: VeluTab[];
|
|
78
112
|
}
|
|
79
113
|
|
|
114
|
+
type VeluOpenApiSource = string | string[] | Record<string, unknown>;
|
|
115
|
+
|
|
80
116
|
interface VeluConfig {
|
|
81
117
|
$schema?: string;
|
|
118
|
+
icons?: {
|
|
119
|
+
library?: "fontawesome" | "lucide" | "tabler";
|
|
120
|
+
};
|
|
82
121
|
theme?: string;
|
|
83
122
|
colors?: { primary?: string; light?: string; dark?: string };
|
|
84
123
|
appearance?: "system" | "light" | "dark";
|
|
85
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
|
+
};
|
|
86
148
|
navigation: {
|
|
149
|
+
openapi?: VeluOpenApiSource;
|
|
150
|
+
asyncapi?: VeluOpenApiSource;
|
|
87
151
|
tabs?: VeluTab[];
|
|
88
152
|
languages?: VeluLanguageNav[];
|
|
89
153
|
products?: VeluProductNav[];
|
|
@@ -96,6 +160,49 @@ interface VeluConfig {
|
|
|
96
160
|
};
|
|
97
161
|
}
|
|
98
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
|
+
|
|
99
206
|
function loadJson(filePath: string): unknown {
|
|
100
207
|
const raw = readFileSync(filePath, "utf-8");
|
|
101
208
|
return JSON.parse(raw);
|
|
@@ -150,9 +257,12 @@ function collectPages(config: VeluConfig): string[] {
|
|
|
150
257
|
function validateVeluConfig(docsDir: string, schemaPath: string): { valid: boolean; errors: string[] } {
|
|
151
258
|
const errors: string[] = [];
|
|
152
259
|
|
|
153
|
-
const configPath =
|
|
154
|
-
if (!
|
|
155
|
-
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
|
+
};
|
|
156
266
|
}
|
|
157
267
|
|
|
158
268
|
if (!existsSync(schemaPath)) {
|
|
@@ -176,12 +286,15 @@ function validateVeluConfig(docsDir: string, schemaPath: string): { valid: boole
|
|
|
176
286
|
|
|
177
287
|
const config = normalizeConfigNavigation(rawConfig);
|
|
178
288
|
|
|
179
|
-
// Validate that all referenced
|
|
289
|
+
// Validate that all referenced page files exist (.mdx or .md)
|
|
180
290
|
const pages = collectPages(config);
|
|
181
291
|
for (const page of pages) {
|
|
292
|
+
if (isOpenApiOperationReference(page)) continue;
|
|
293
|
+
if (isAsyncApiChannelReference(page)) continue;
|
|
294
|
+
const mdxPath = join(docsDir, `${page}.mdx`);
|
|
182
295
|
const mdPath = join(docsDir, `${page}.md`);
|
|
183
|
-
if (!existsSync(mdPath)) {
|
|
184
|
-
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})`);
|
|
185
298
|
}
|
|
186
299
|
}
|
|
187
300
|
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
3
|
-
import { baseOptions } from '@/lib/layout.shared';
|
|
4
|
-
import { source } from '@/lib/source';
|
|
5
|
-
import { getLanguages, getVersionOptions, getProductOptions, type VeluVersionOption, type VeluProductOption } from '@/lib/velu';
|
|
6
|
-
import { SidebarLinks } from '@/components/sidebar-links';
|
|
7
|
-
import { ProductSwitcher } from '@/components/product-switcher';
|
|
8
|
-
|
|
9
|
-
interface LayoutParams {
|
|
10
|
-
slug?: string[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface SlugLayoutProps {
|
|
14
|
-
children: ReactNode;
|
|
15
|
-
params: Promise<LayoutParams>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function resolveLocale(slugInput: string[] | undefined): string {
|
|
19
|
-
const languages = getLanguages();
|
|
20
|
-
const defaultLanguage = languages[0] ?? 'en';
|
|
21
|
-
const slug = slugInput ?? [];
|
|
22
|
-
const firstSeg = slug[0];
|
|
23
|
-
|
|
24
|
-
return languages.includes(firstSeg ?? '') ? firstSeg! : defaultLanguage;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function resolveCurrentVersion(slugInput: string[] | undefined, versions: VeluVersionOption[]): VeluVersionOption | undefined {
|
|
28
|
-
if (versions.length === 0) return undefined;
|
|
29
|
-
const firstSeg = (slugInput ?? [])[0] ?? '';
|
|
30
|
-
return versions.find((v) => v.slug === firstSeg) ?? versions.find((v) => v.isDefault) ?? versions[0];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function filterTreeBySlugPrefix<T extends { children?: unknown[] }>(tree: T, prefix?: string): T {
|
|
34
|
-
if (!prefix) return tree;
|
|
35
|
-
|
|
36
|
-
const children = Array.isArray(tree.children) ? tree.children : [];
|
|
37
|
-
|
|
38
|
-
const filtered = children.filter((node) => {
|
|
39
|
-
if (typeof node !== 'object' || node === null) return false;
|
|
40
|
-
const entry = node as { url?: unknown; path?: unknown; $ref?: { metaFile?: unknown; file?: unknown } };
|
|
41
|
-
const candidates = [entry.url, entry.path, entry.$ref?.metaFile, entry.$ref?.file]
|
|
42
|
-
.filter((value): value is string => typeof value === 'string');
|
|
43
|
-
return candidates.some((value) => value.includes(`${prefix}/`));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (filtered.length === 0) return tree;
|
|
47
|
-
return { ...tree, children: filtered } as T;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function resolveCurrentProduct(slugInput: string[] | undefined, products: VeluProductOption[]): VeluProductOption | undefined {
|
|
51
|
-
if (products.length === 0) return undefined;
|
|
52
|
-
const firstSeg = (slugInput ?? [])[0] ?? '';
|
|
53
|
-
return products.find((p) => p.slug === firstSeg) ?? products[0];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default async function SlugLayout({ children, params }: SlugLayoutProps) {
|
|
57
|
-
const resolvedParams = await params;
|
|
58
|
-
const locale = resolveLocale(resolvedParams.slug);
|
|
59
|
-
const versions = getVersionOptions();
|
|
60
|
-
const products = getProductOptions();
|
|
61
|
-
const currentVersion = resolveCurrentVersion(resolvedParams.slug, versions);
|
|
62
|
-
const currentProduct = resolveCurrentProduct(resolvedParams.slug, products);
|
|
63
|
-
|
|
64
|
-
const activePrefix = currentVersion?.slug ?? currentProduct?.slug;
|
|
65
|
-
const tree = filterTreeBySlugPrefix(source.getPageTree(locale), activePrefix);
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<DocsLayout
|
|
69
|
-
tree={tree}
|
|
70
|
-
sidebar={{
|
|
71
|
-
collapsible: false,
|
|
72
|
-
banner: products.length > 1 ? (
|
|
73
|
-
<div className="velu-sidebar-banner">
|
|
74
|
-
<ProductSwitcher products={products} />
|
|
75
|
-
</div>
|
|
76
|
-
) : undefined,
|
|
77
|
-
footer: <SidebarLinks />,
|
|
78
|
-
}}
|
|
79
|
-
{...baseOptions()}
|
|
80
|
-
themeSwitch={{ enabled: false }}
|
|
81
|
-
>
|
|
82
|
-
{children}
|
|
83
|
-
</DocsLayout>
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export { generateStaticParams } from './page';
|
|
@@ -1,146 +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 { getLanguages, getVersionOptions, getProductOptions } from '@/lib/velu';
|
|
13
|
-
import { CopyPageButton } from '@/components/copy-page';
|
|
14
|
-
|
|
15
|
-
interface RouteParams {
|
|
16
|
-
slug?: string[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface PageProps {
|
|
20
|
-
params: Promise<RouteParams>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function resolveLocaleSlug(slugInput: string[] | undefined) {
|
|
24
|
-
const languages = getLanguages();
|
|
25
|
-
const defaultLanguage = languages[0] ?? 'en';
|
|
26
|
-
const slug = slugInput ?? [];
|
|
27
|
-
const firstSeg = slug[0];
|
|
28
|
-
const hasLocalePrefix = languages.includes(firstSeg ?? '');
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
defaultLanguage,
|
|
32
|
-
locale: hasLocalePrefix ? firstSeg! : defaultLanguage,
|
|
33
|
-
pageSlug: hasLocalePrefix ? slug.slice(1) : slug,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function resolveContextFromSlug(slugInput: string[] | undefined) {
|
|
38
|
-
const languages = getLanguages();
|
|
39
|
-
const versions = getVersionOptions();
|
|
40
|
-
const products = getProductOptions();
|
|
41
|
-
const slug = slugInput ?? [];
|
|
42
|
-
|
|
43
|
-
// Check for language prefix
|
|
44
|
-
const firstSeg = slug[0];
|
|
45
|
-
const hasLocalePrefix = languages.includes(firstSeg ?? '');
|
|
46
|
-
const locale = hasLocalePrefix ? firstSeg! : (languages[0] ?? 'en');
|
|
47
|
-
const remainingSlug = hasLocalePrefix ? slug.slice(1) : slug;
|
|
48
|
-
|
|
49
|
-
// Check for version/product in remaining slug
|
|
50
|
-
const contextSeg = remainingSlug[0] ?? '';
|
|
51
|
-
const version = versions.find((v) => v.slug === contextSeg);
|
|
52
|
-
const product = products.find((p) => p.slug === contextSeg);
|
|
53
|
-
|
|
54
|
-
return {
|
|
55
|
-
locale,
|
|
56
|
-
version: version?.slug,
|
|
57
|
-
product: product?.slug,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export default async function Page({ params }: PageProps) {
|
|
62
|
-
const resolvedParams = await params;
|
|
63
|
-
const { locale, pageSlug } = resolveLocaleSlug(resolvedParams.slug);
|
|
64
|
-
const { locale: filterLocale, version, product } = resolveContextFromSlug(resolvedParams.slug);
|
|
65
|
-
const hasI18n = getLanguages().length > 1;
|
|
66
|
-
|
|
67
|
-
const page = hasI18n ? source.getPage(pageSlug, locale) : source.getPage(pageSlug);
|
|
68
|
-
|
|
69
|
-
if (!page) notFound();
|
|
70
|
-
|
|
71
|
-
const MDX = page.data.body;
|
|
72
|
-
|
|
73
|
-
// Build pagefind filter attributes
|
|
74
|
-
const metaAttrs: string[] = [`title:${page.data.title}`];
|
|
75
|
-
const filterAttrs: string[] = [];
|
|
76
|
-
if (hasI18n) {
|
|
77
|
-
metaAttrs.push(`language:${filterLocale}`);
|
|
78
|
-
filterAttrs.push(`language:${filterLocale}`);
|
|
79
|
-
}
|
|
80
|
-
if (version) {
|
|
81
|
-
metaAttrs.push(`version:${version}`);
|
|
82
|
-
filterAttrs.push(`version:${version}`);
|
|
83
|
-
}
|
|
84
|
-
if (product) {
|
|
85
|
-
metaAttrs.push(`product:${product}`);
|
|
86
|
-
filterAttrs.push(`product:${product}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return (
|
|
90
|
-
<DocsPage toc={page.data.toc} full={page.data.full}>
|
|
91
|
-
<div
|
|
92
|
-
data-pagefind-body
|
|
93
|
-
data-pagefind-meta={metaAttrs.join(',')}
|
|
94
|
-
data-pagefind-filter={filterAttrs.length > 0 ? filterAttrs.join(',') : undefined}
|
|
95
|
-
>
|
|
96
|
-
<div className="velu-title-row">
|
|
97
|
-
<DocsTitle>{page.data.title}</DocsTitle>
|
|
98
|
-
<CopyPageButton />
|
|
99
|
-
</div>
|
|
100
|
-
{page.data.description ? <DocsDescription>{page.data.description}</DocsDescription> : null}
|
|
101
|
-
<DocsBody>
|
|
102
|
-
<MDX
|
|
103
|
-
components={getMDXComponents({
|
|
104
|
-
a: createRelativeLink(source, page),
|
|
105
|
-
})}
|
|
106
|
-
/>
|
|
107
|
-
</DocsBody>
|
|
108
|
-
</div>
|
|
109
|
-
<footer className="velu-footer">
|
|
110
|
-
Powered by <a href="https://getvelu.com" target="_blank" rel="noopener noreferrer">Velu</a>
|
|
111
|
-
</footer>
|
|
112
|
-
</DocsPage>
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export async function generateStaticParams() {
|
|
117
|
-
const generated = source.generateParams('slug') as Array<{ slug?: string[] }>;
|
|
118
|
-
const seen = new Set<string>();
|
|
119
|
-
|
|
120
|
-
const nonRoot = generated.filter((entry) => {
|
|
121
|
-
const slug = entry.slug ?? [];
|
|
122
|
-
if (slug.length === 0) return false;
|
|
123
|
-
const key = slug.join('/');
|
|
124
|
-
if (seen.has(key)) return false;
|
|
125
|
-
seen.add(key);
|
|
126
|
-
return true;
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
// Include root variants for optional catch-all [[...slug]] in export mode.
|
|
130
|
-
return [{}, { slug: undefined }, { slug: [] }, ...nonRoot];
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
|
134
|
-
const resolvedParams = await params;
|
|
135
|
-
const { locale, pageSlug } = resolveLocaleSlug(resolvedParams.slug);
|
|
136
|
-
const hasI18n = getLanguages().length > 1;
|
|
137
|
-
|
|
138
|
-
const page = hasI18n ? source.getPage(pageSlug, locale) : source.getPage(pageSlug);
|
|
139
|
-
|
|
140
|
-
if (!page) notFound();
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
title: page.data.title,
|
|
144
|
-
description: page.data.description,
|
|
145
|
-
};
|
|
146
|
-
}
|