radiant-docs 0.1.34 → 0.1.38
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 +1 -1
- package/template/astro.config.mjs +27 -0
- package/template/package-lock.json +1027 -513
- package/template/package.json +3 -2
- package/template/scripts/generate-proxy-allowed-origins.mjs +217 -0
- package/template/scripts/generate-robots-txt.mjs +19 -0
- package/template/scripts/stamp-image-versions.mjs +63 -11
- package/template/src/components/Footer.astro +1 -1
- package/template/src/components/Header.astro +9 -9
- package/template/src/components/LogoLink.astro +2 -1
- package/template/src/components/OpenApiPage.astro +18 -18
- package/template/src/components/Search.astro +18 -18
- package/template/src/components/Sidebar.astro +4 -2
- package/template/src/components/SidebarDropdown.astro +82 -79
- package/template/src/components/SidebarGroup.astro +3 -0
- package/template/src/components/SidebarMenu.astro +14 -1
- package/template/src/components/SidebarSegmented.astro +5 -5
- package/template/src/components/SidebarSubgroup.astro +35 -12
- package/template/src/components/TableOfContents.astro +24 -15
- package/template/src/components/ThemeSwitcher.astro +15 -8
- package/template/src/components/chat/AskAiWidget.tsx +10 -5
- package/template/src/components/endpoint/PlaygroundBar.astro +3 -3
- package/template/src/components/endpoint/PlaygroundButton.astro +3 -3
- package/template/src/components/endpoint/PlaygroundField.astro +53 -53
- package/template/src/components/endpoint/PlaygroundForm.astro +51 -37
- package/template/src/components/endpoint/RequestSnippets.astro +54 -21
- package/template/src/components/endpoint/ResponseDisplay.astro +24 -24
- package/template/src/components/endpoint/ResponseFieldTree.astro +12 -12
- package/template/src/components/endpoint/ResponseFields.astro +19 -19
- package/template/src/components/endpoint/ResponseSnippets.astro +66 -29
- package/template/src/components/sidebar/SidebarEndpointLink.astro +18 -15
- package/template/src/components/sidebar/SidebarOpenApiPageLink.astro +56 -0
- package/template/src/components/ui/CodeTabEdge.astro +6 -4
- package/template/src/components/ui/Field.astro +7 -7
- package/template/src/components/ui/Icon.astro +2 -1
- package/template/src/components/ui/demo/Demo.astro +1 -1
- package/template/src/components/user/Accordion.astro +3 -3
- package/template/src/components/user/Callout.astro +8 -8
- package/template/src/components/user/CodeBlock.astro +57 -22
- package/template/src/components/user/CodeGroup.astro +14 -10
- package/template/src/components/user/ComponentPreviewBlock.astro +38 -12
- package/template/src/components/user/Image.astro +6 -2
- package/template/src/components/user/Step.astro +4 -4
- package/template/src/components/user/Tab.astro +1 -1
- package/template/src/components/user/Tabs.astro +15 -20
- package/template/src/layouts/Layout.astro +9 -4
- package/template/src/lib/code/code-block.ts +150 -15
- package/template/src/lib/mdx/remark-resolve-internal-links.ts +639 -0
- package/template/src/lib/pagefind.ts +2 -1
- package/template/src/lib/routes.ts +134 -58
- package/template/src/lib/static-asset-url.ts +62 -0
- package/template/src/lib/utils.ts +48 -0
- package/template/src/lib/validation.ts +115 -27
- package/template/src/pages/404.astro +44 -0
- package/template/src/styles/global.css +28 -19
- package/template/scripts/rewrite-static-asset-host.mjs +0 -408
package/template/package.json
CHANGED
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
"dev": "astro dev",
|
|
7
7
|
"start": "tsx runner.ts",
|
|
8
8
|
"prebuild": "rm -rf public/pagefind",
|
|
9
|
-
"build": "astro build && node scripts/generate-og-metadata.mjs && node scripts/generate-og-images.mjs && node scripts/stamp-og-image-versions.mjs && node scripts/stamp-image-versions.mjs && pagefind --site dist && node scripts/stamp-pagefind-runtime-version.mjs && node scripts/
|
|
9
|
+
"build": "astro build && node scripts/generate-og-metadata.mjs && node scripts/generate-og-images.mjs && node scripts/stamp-og-image-versions.mjs && node scripts/stamp-image-versions.mjs && pagefind --site dist && node scripts/stamp-pagefind-runtime-version.mjs && node scripts/generate-proxy-allowed-origins.mjs && node scripts/generate-robots-txt.mjs",
|
|
10
10
|
"preview": "astro preview",
|
|
11
11
|
"astro": "astro",
|
|
12
|
-
"search:index": "astro build && node scripts/generate-og-metadata.mjs && node scripts/generate-og-images.mjs && node scripts/stamp-og-image-versions.mjs && node scripts/stamp-image-versions.mjs && pagefind --site dist && node scripts/stamp-pagefind-runtime-version.mjs && node scripts/
|
|
12
|
+
"search:index": "astro build && node scripts/generate-og-metadata.mjs && node scripts/generate-og-images.mjs && node scripts/stamp-og-image-versions.mjs && node scripts/stamp-image-versions.mjs && pagefind --site dist && node scripts/stamp-pagefind-runtime-version.mjs && node scripts/generate-proxy-allowed-origins.mjs && node scripts/generate-robots-txt.mjs && cp -r dist/pagefind public/"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@alpinejs/collapse": "^3.15.2",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"@astrojs/alpinejs": "^0.4.9",
|
|
19
19
|
"@astrojs/mdx": "^4.3.12",
|
|
20
20
|
"@astrojs/preact": "^4.1.3",
|
|
21
|
+
"@astrojs/sitemap": "^3.7.2",
|
|
21
22
|
"@aws-sdk/client-s3": "^3.964.0",
|
|
22
23
|
"@fontsource/google-sans-flex": "^5.2.2",
|
|
23
24
|
"@iconify-json/lucide": "^1.2.79",
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
|
|
5
|
+
const CWD = process.cwd();
|
|
6
|
+
const DOCS_DIR = path.join(CWD, "src/content/docs");
|
|
7
|
+
const DOCS_CONFIG_PATH = path.join(DOCS_DIR, "docs.json");
|
|
8
|
+
const OUTPUT_DIR = path.join(CWD, ".radiant");
|
|
9
|
+
const OUTPUT_FILE = path.join(OUTPUT_DIR, "proxy-allowed-origins.json");
|
|
10
|
+
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === "object" && value !== null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isHttpUrl(value) {
|
|
16
|
+
return /^https?:\/\//i.test(String(value).trim());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function collectOpenApiSourcesFromNavigation(navigationNode, target) {
|
|
20
|
+
if (!isRecord(navigationNode)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const openApiConfig = navigationNode.openapi;
|
|
25
|
+
if (typeof openApiConfig === "string") {
|
|
26
|
+
const trimmed = openApiConfig.trim();
|
|
27
|
+
if (trimmed) {
|
|
28
|
+
target.add(trimmed);
|
|
29
|
+
}
|
|
30
|
+
} else if (
|
|
31
|
+
isRecord(openApiConfig) &&
|
|
32
|
+
typeof openApiConfig.source === "string"
|
|
33
|
+
) {
|
|
34
|
+
const trimmed = openApiConfig.source.trim();
|
|
35
|
+
if (trimmed) {
|
|
36
|
+
target.add(trimmed);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const menu = navigationNode.menu;
|
|
41
|
+
if (!isRecord(menu) || !Array.isArray(menu.items)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const menuItem of menu.items) {
|
|
46
|
+
if (!isRecord(menuItem) || !isRecord(menuItem.submenu)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
collectOpenApiSourcesFromNavigation(menuItem.submenu, target);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveServerTemplateUrl(rawUrl, rawVariables) {
|
|
54
|
+
let unresolved = false;
|
|
55
|
+
const variables = isRecord(rawVariables) ? rawVariables : null;
|
|
56
|
+
|
|
57
|
+
const resolved = rawUrl.replace(/\{([^}]+)\}/g, (_match, tokenName) => {
|
|
58
|
+
if (!variables) {
|
|
59
|
+
unresolved = true;
|
|
60
|
+
return "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const variableConfig = variables[tokenName];
|
|
64
|
+
if (
|
|
65
|
+
!isRecord(variableConfig) ||
|
|
66
|
+
typeof variableConfig.default !== "string"
|
|
67
|
+
) {
|
|
68
|
+
unresolved = true;
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const defaultValue = variableConfig.default.trim();
|
|
73
|
+
if (!defaultValue) {
|
|
74
|
+
unresolved = true;
|
|
75
|
+
return "";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return defaultValue;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (unresolved) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return resolved;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeAllowedOrigin(rawUrl) {
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = new URL(rawUrl);
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (parsed.protocol !== "https:") {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!parsed.hostname || parsed.username || parsed.password) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return parsed.origin.toLowerCase();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function loadOpenApiSpec(source) {
|
|
108
|
+
let fileContent;
|
|
109
|
+
|
|
110
|
+
if (isHttpUrl(source)) {
|
|
111
|
+
const response = await fetch(source);
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Failed to fetch OpenAPI spec (${response.status} ${response.statusText})`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
fileContent = await response.text();
|
|
118
|
+
} else {
|
|
119
|
+
const absolutePath = path.join(DOCS_DIR, source);
|
|
120
|
+
fileContent = await fs.readFile(absolutePath, "utf8");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const trimmed = fileContent.trim();
|
|
124
|
+
if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html")) {
|
|
125
|
+
throw new Error("OpenAPI source returned HTML instead of JSON or YAML");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
return JSON.parse(fileContent);
|
|
130
|
+
} catch {
|
|
131
|
+
return YAML.parse(fileContent);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function buildAllowedOrigins() {
|
|
136
|
+
const docsConfigRaw = await fs.readFile(DOCS_CONFIG_PATH, "utf8");
|
|
137
|
+
const docsConfig = JSON.parse(docsConfigRaw);
|
|
138
|
+
|
|
139
|
+
if (!isRecord(docsConfig) || !isRecord(docsConfig.navigation)) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const sources = new Set();
|
|
144
|
+
collectOpenApiSourcesFromNavigation(docsConfig.navigation, sources);
|
|
145
|
+
if (sources.size === 0) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const allowedOrigins = new Set();
|
|
150
|
+
|
|
151
|
+
for (const source of sources) {
|
|
152
|
+
try {
|
|
153
|
+
const spec = await loadOpenApiSpec(source);
|
|
154
|
+
const servers =
|
|
155
|
+
isRecord(spec) && Array.isArray(spec.servers) ? spec.servers : [];
|
|
156
|
+
|
|
157
|
+
for (const server of servers) {
|
|
158
|
+
if (!isRecord(server) || typeof server.url !== "string") {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const resolvedUrl = resolveServerTemplateUrl(
|
|
163
|
+
server.url,
|
|
164
|
+
server.variables,
|
|
165
|
+
);
|
|
166
|
+
if (!resolvedUrl) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const normalizedOrigin = normalizeAllowedOrigin(resolvedUrl);
|
|
171
|
+
if (normalizedOrigin) {
|
|
172
|
+
allowedOrigins.add(normalizedOrigin);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.warn(
|
|
177
|
+
`⚠️ Failed to extract OpenAPI server origins from "${source}":`,
|
|
178
|
+
error instanceof Error ? error.message : String(error),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return Array.from(allowedOrigins).sort();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function run() {
|
|
187
|
+
let allowedOrigins = [];
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
allowedOrigins = await buildAllowedOrigins();
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.warn(
|
|
193
|
+
"⚠️ Failed to generate proxy allowed origins. Falling back to empty allowlist.",
|
|
194
|
+
error instanceof Error ? error.message : String(error),
|
|
195
|
+
);
|
|
196
|
+
allowedOrigins = [];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
200
|
+
await fs.writeFile(
|
|
201
|
+
OUTPUT_FILE,
|
|
202
|
+
`${JSON.stringify(allowedOrigins, null, 2)}\n`,
|
|
203
|
+
"utf8",
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
console.log(
|
|
207
|
+
`✅ Wrote ${allowedOrigins.length} proxy allowed origins to ${path.relative(CWD, OUTPUT_FILE)}`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
run().catch((error) => {
|
|
212
|
+
console.error(
|
|
213
|
+
"❌ Failed to write proxy allowed origins manifest:",
|
|
214
|
+
error instanceof Error ? error.message : String(error),
|
|
215
|
+
);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const CWD = process.cwd();
|
|
5
|
+
const DIST_DIR = path.join(CWD, "dist");
|
|
6
|
+
const ROBOTS_TXT_PATH = path.join(DIST_DIR, "robots.txt");
|
|
7
|
+
|
|
8
|
+
function main() {
|
|
9
|
+
if (!fs.existsSync(DIST_DIR)) {
|
|
10
|
+
console.log("Skipping robots.txt generation: dist directory not found.");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const robotsTxt = "User-agent: *\nAllow: /\nSitemap: /sitemap-index.xml\n";
|
|
15
|
+
fs.writeFileSync(ROBOTS_TXT_PATH, robotsTxt, "utf8");
|
|
16
|
+
console.log("✅ robots.txt generated.");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
main();
|
|
@@ -6,6 +6,28 @@ const CWD = process.cwd();
|
|
|
6
6
|
const DIST_DIR = path.join(CWD, "dist");
|
|
7
7
|
const VERSION_LENGTH = 12;
|
|
8
8
|
const DIST_ROOT = path.resolve(DIST_DIR);
|
|
9
|
+
const CONFIGURED_STATIC_HOST = (() => {
|
|
10
|
+
const raw =
|
|
11
|
+
typeof process.env.STATIC_ASSET_HOST === "string"
|
|
12
|
+
? process.env.STATIC_ASSET_HOST.trim()
|
|
13
|
+
: "";
|
|
14
|
+
if (!raw) return null;
|
|
15
|
+
const withScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(raw)
|
|
16
|
+
? raw
|
|
17
|
+
: `https://${raw}`;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const parsed = new URL(withScheme);
|
|
21
|
+
return parsed.hostname.toLowerCase();
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
})();
|
|
26
|
+
const CONFIGURED_PREFIX =
|
|
27
|
+
typeof process.env.R2_BUCKET_PREFIX === "string" &&
|
|
28
|
+
process.env.R2_BUCKET_PREFIX.trim().length > 0
|
|
29
|
+
? process.env.R2_BUCKET_PREFIX.trim().replace(/^\/+|\/+$/g, "")
|
|
30
|
+
: null;
|
|
9
31
|
const VERSIONED_EXTENSIONS = new Set([
|
|
10
32
|
".avif",
|
|
11
33
|
".gif",
|
|
@@ -76,6 +98,24 @@ function getHash(filePath) {
|
|
|
76
98
|
return hash;
|
|
77
99
|
}
|
|
78
100
|
|
|
101
|
+
function normalizePathForDistLookup(pathname) {
|
|
102
|
+
const normalizedPathname = path.posix.normalize(pathname);
|
|
103
|
+
if (!CONFIGURED_PREFIX) {
|
|
104
|
+
return normalizedPathname;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const prefix = `/${CONFIGURED_PREFIX}`;
|
|
108
|
+
if (
|
|
109
|
+
normalizedPathname === prefix ||
|
|
110
|
+
normalizedPathname.startsWith(`${prefix}/`)
|
|
111
|
+
) {
|
|
112
|
+
const stripped = normalizedPathname.slice(prefix.length);
|
|
113
|
+
return stripped.startsWith("/") ? stripped : `/${stripped}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return normalizedPathname;
|
|
117
|
+
}
|
|
118
|
+
|
|
79
119
|
function resolveLocalImagePath(urlValue, filePath) {
|
|
80
120
|
const trimmed = urlValue.trim();
|
|
81
121
|
if (!trimmed) return null;
|
|
@@ -91,11 +131,6 @@ function resolveLocalImagePath(urlValue, filePath) {
|
|
|
91
131
|
return null;
|
|
92
132
|
}
|
|
93
133
|
|
|
94
|
-
// Ignore fully-qualified URLs.
|
|
95
|
-
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(decoded)) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
134
|
let parsed;
|
|
100
135
|
try {
|
|
101
136
|
parsed = new URL(decoded, `https://radiant.invalid${htmlBasePathname(filePath)}`);
|
|
@@ -103,13 +138,27 @@ function resolveLocalImagePath(urlValue, filePath) {
|
|
|
103
138
|
return null;
|
|
104
139
|
}
|
|
105
140
|
|
|
141
|
+
const isAbsoluteUrl = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(decoded);
|
|
142
|
+
if (isAbsoluteUrl) {
|
|
143
|
+
if (!CONFIGURED_STATIC_HOST) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (parsed.protocol !== "https:") {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (parsed.hostname.toLowerCase() !== CONFIGURED_STATIC_HOST) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
106
156
|
const extension = path.extname(parsed.pathname).toLowerCase();
|
|
107
157
|
if (!VERSIONED_EXTENSIONS.has(extension)) {
|
|
108
158
|
return null;
|
|
109
159
|
}
|
|
110
160
|
|
|
111
|
-
const normalizedPathname =
|
|
112
|
-
.normalize(parsed.pathname)
|
|
161
|
+
const normalizedPathname = normalizePathForDistLookup(parsed.pathname)
|
|
113
162
|
.replace(/^\/+/, "");
|
|
114
163
|
const absolutePath = path.resolve(DIST_DIR, normalizedPathname);
|
|
115
164
|
|
|
@@ -124,12 +173,15 @@ function resolveLocalImagePath(urlValue, filePath) {
|
|
|
124
173
|
return null;
|
|
125
174
|
}
|
|
126
175
|
|
|
127
|
-
return { parsed, absolutePath };
|
|
176
|
+
return { parsed, absolutePath, isAbsoluteUrl };
|
|
128
177
|
}
|
|
129
178
|
|
|
130
|
-
function formatUpdatedUrl(parsed) {
|
|
179
|
+
function formatUpdatedUrl(parsed, isAbsoluteUrl) {
|
|
131
180
|
const pathnameWithParams = `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
132
|
-
|
|
181
|
+
const fullValue = isAbsoluteUrl
|
|
182
|
+
? `${parsed.origin}${pathnameWithParams}`
|
|
183
|
+
: pathnameWithParams;
|
|
184
|
+
return fullValue.replace(/&/g, "&");
|
|
133
185
|
}
|
|
134
186
|
|
|
135
187
|
function stampSingleUrl(urlValue, filePath) {
|
|
@@ -141,7 +193,7 @@ function stampSingleUrl(urlValue, filePath) {
|
|
|
141
193
|
const version = getHash(resolved.absolutePath);
|
|
142
194
|
resolved.parsed.searchParams.set("v", version);
|
|
143
195
|
|
|
144
|
-
const updated = formatUpdatedUrl(resolved.parsed);
|
|
196
|
+
const updated = formatUpdatedUrl(resolved.parsed, resolved.isAbsoluteUrl);
|
|
145
197
|
return { value: updated, changed: updated !== urlValue };
|
|
146
198
|
}
|
|
147
199
|
|
|
@@ -91,7 +91,7 @@ const socialIcons: Record<string, string> = {
|
|
|
91
91
|
href="https://radiant.io"
|
|
92
92
|
class:list={[
|
|
93
93
|
"group flex items-center gap-1.5 text-[13px] text-neutral-400 dark:text-neutral-500 hover:text-neutral-600 dark:hover:text-neutral-300 transition-colors duration-200",
|
|
94
|
-
askAiEnabled && "sm:pr-
|
|
94
|
+
askAiEnabled && "sm:pr-13",
|
|
95
95
|
]}
|
|
96
96
|
>
|
|
97
97
|
Built with <span
|
|
@@ -15,9 +15,9 @@ const config = await getConfig();
|
|
|
15
15
|
|
|
16
16
|
<header
|
|
17
17
|
class:list={[
|
|
18
|
-
"fixed z-30 top-1 inset-x-1 shadow-[0px_-20px_0px_20px_var(--
|
|
18
|
+
"fixed z-30 top-1 inset-x-1 shadow-[0px_-20px_0px_20px_var(--background-dark)] h-16 border-x border-t border-border rounded-t-2xl overflow-hidden border-b bg-background border-b-border-light",
|
|
19
19
|
config.navbar?.blur &&
|
|
20
|
-
"sm:bg-background/80 sm:backdrop-blur-[18px] sm:backdrop-saturate-50 sm:border-b-neutral-100/85 sm:bg-clip-padding",
|
|
20
|
+
"sm:bg-background/80 sm:backdrop-blur-[18px] sm:backdrop-saturate-50 sm:border-b-neutral-100/85 sm:dark:border-neutral-800/85 sm:bg-clip-padding",
|
|
21
21
|
]}
|
|
22
22
|
data-pagefind-ignore
|
|
23
23
|
data-vaul-scale-chrome
|
|
@@ -34,12 +34,12 @@ const config = await getConfig();
|
|
|
34
34
|
x-bind:class="open ? 'rotate-90': ''"
|
|
35
35
|
>
|
|
36
36
|
<div
|
|
37
|
-
class="w-[14px] h-0.5 bg-neutral-900 rounded-full origin-[20%_50%] transition"
|
|
37
|
+
class="w-[14px] h-0.5 bg-neutral-900 dark:bg-white rounded-full origin-[20%_50%] transition"
|
|
38
38
|
x-bind:class="open ? 'rotate-45': ''"
|
|
39
39
|
>
|
|
40
40
|
</div>
|
|
41
41
|
<div
|
|
42
|
-
class="w-[14px] h-0.5 bg-neutral-900 rounded-full origin-[20%_50%] transition"
|
|
42
|
+
class="w-[14px] h-0.5 bg-neutral-900 dark:bg-white rounded-full origin-[20%_50%] transition"
|
|
43
43
|
x-bind:class="open ? '-rotate-45': ''"
|
|
44
44
|
>
|
|
45
45
|
</div>
|
|
@@ -58,14 +58,14 @@ const config = await getConfig();
|
|
|
58
58
|
showAskAiTrigger ? (
|
|
59
59
|
<button
|
|
60
60
|
type="button"
|
|
61
|
-
class="hidden md:inline-flex items-center gap-
|
|
61
|
+
class="hidden md:inline-flex items-center gap-2 h-8 rounded-lg [corner-shape:superellipse(1.2)] bg-linear-to-b from-neutral-900/85 dark:from-neutral-100 to-neutral-900 dark:to-neutral-200 px-3 text-[13px] font-[350] dark:font-medium text-white shadow-sm dark:bg-white dark:text-neutral-900 cursor-pointer"
|
|
62
62
|
onclick="window.dispatchEvent(new CustomEvent('ask-ai:open'));"
|
|
63
63
|
>
|
|
64
64
|
<img
|
|
65
65
|
src={sparkleIcon}
|
|
66
66
|
alt=""
|
|
67
67
|
aria-hidden="true"
|
|
68
|
-
class="size-3 invert dark:invert-0"
|
|
68
|
+
class="size-3 -ml-px invert dark:invert-0"
|
|
69
69
|
/>
|
|
70
70
|
Ask AI
|
|
71
71
|
</button>
|
|
@@ -87,7 +87,7 @@ const config = await getConfig();
|
|
|
87
87
|
{config.navbar.links.map((l, i, a) => (
|
|
88
88
|
<a
|
|
89
89
|
class:list={[
|
|
90
|
-
"items-center gap-1 text-[13px] font-[450] text-neutral-600/85 hover:text-neutral-600 duration-200 px-1.5 py-[5px] whitespace-nowrap",
|
|
90
|
+
"items-center gap-1 text-[13px] font-[450] dark:font-normal text-neutral-600/85 hover:text-neutral-600 dark:text-neutral-200/90 dark:hover:text-neutral-200 duration-200 px-1.5 py-[5px] whitespace-nowrap",
|
|
91
91
|
showAskAiTrigger && a.length === 3 && i === 2
|
|
92
92
|
? "hidden 2xl:flex"
|
|
93
93
|
: "flex",
|
|
@@ -110,7 +110,7 @@ const config = await getConfig();
|
|
|
110
110
|
{config.navbar.secondary && (
|
|
111
111
|
<a
|
|
112
112
|
class:list={[
|
|
113
|
-
"h-[33px] items-center gap-1.5 px-
|
|
113
|
+
"h-[33px] items-center gap-1.5 px-[11px] text-[13px] bg-white/90 dark:bg-neutral-800/80 text-neutral-600/85 hover:text-neutral-600 dark:text-neutral-200/95 dark:hover:text-neutral-200 rounded-lg [corner-shape:superellipse(1.2)] border border-neutral-200 dark:border-neutral-700/40 shadow-xs transition-all whitespace-nowrap",
|
|
114
114
|
config.navbar.primary ? "hidden lg:flex" : "flex",
|
|
115
115
|
]}
|
|
116
116
|
href={config.navbar.secondary.href}
|
|
@@ -129,7 +129,7 @@ const config = await getConfig();
|
|
|
129
129
|
{config.navbar.primary && (
|
|
130
130
|
<a
|
|
131
131
|
class:list={[
|
|
132
|
-
"h-8 flex items-center gap-2 px-3 text-[13px] rounded-lg [corner-shape:superellipse(1.2)] bg-linear-to-b from-neutral-900/85 to-neutral-900 text-white duration-200 shadow-sm transition-all whitespace-nowrap",
|
|
132
|
+
"font-[350] dark:font-[450] h-8 flex items-center gap-2 px-3 text-[13px] rounded-lg [corner-shape:superellipse(1.2)] bg-linear-to-b from-neutral-900/85 to-neutral-900 dark:from-neutral-100 dark:to-neutral-200 text-white dark:text-neutral-950 duration-200 shadow-sm transition-all whitespace-nowrap",
|
|
133
133
|
]}
|
|
134
134
|
href={config.navbar.primary.href}
|
|
135
135
|
>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getConfig, type LogoVariant } from "../lib/validation";
|
|
3
|
+
import { resolveStaticAssetUrl } from "../lib/static-asset-url";
|
|
3
4
|
|
|
4
5
|
const config = await getConfig();
|
|
5
6
|
|
|
@@ -12,7 +13,7 @@ function getLogoUrl(logoPath: string | undefined): string | null {
|
|
|
12
13
|
: logoPath;
|
|
13
14
|
|
|
14
15
|
// Return public URL - Vite plugin ensures file exists in public
|
|
15
|
-
return `/${normalizedPath}
|
|
16
|
+
return resolveStaticAssetUrl(`/${normalizedPath}`);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function resolveLogoVariant(
|
|
@@ -908,7 +908,7 @@ const formattedBodyDescription = bodyDescription
|
|
|
908
908
|
{
|
|
909
909
|
formattedDescription && (
|
|
910
910
|
<div
|
|
911
|
-
class="mb-6 prose-rules text-neutral-500 **:text-neutral-500"
|
|
911
|
+
class="mb-6 prose-rules text-neutral-500 **:text-neutral-500 dark:text-neutral-400 dark:**:text-neutral-400"
|
|
912
912
|
set:html={formattedDescription}
|
|
913
913
|
/>
|
|
914
914
|
)
|
|
@@ -952,31 +952,31 @@ const formattedBodyDescription = bodyDescription
|
|
|
952
952
|
<h4 class="text-xl font-semibold">{headers[key]}</h4>
|
|
953
953
|
{key === "body" && formattedBodyDescription && (
|
|
954
954
|
<div
|
|
955
|
-
class="mt-2 prose-rules prose-sm! text-neutral-500 **:text-neutral-500"
|
|
955
|
+
class="mt-2 prose-rules prose-sm! text-neutral-500 **:text-neutral-500 dark:text-neutral-400 dark:**:text-neutral-400"
|
|
956
956
|
set:html={formattedBodyDescription}
|
|
957
957
|
/>
|
|
958
958
|
)}
|
|
959
959
|
{hasCommonFields && (
|
|
960
960
|
<div x-data="{ expanded: false }" class="mt-4">
|
|
961
961
|
<div
|
|
962
|
-
class="w-full overflow-hidden rounded-xl border border-neutral-200 bg-white transition-colors duration-200"
|
|
963
|
-
x-bind:class="expanded ? 'border-neutral-300' : 'border-neutral-200'"
|
|
962
|
+
class="w-full overflow-hidden rounded-xl border border-neutral-200 bg-white transition-colors duration-200 dark:border-neutral-800 dark:bg-(--rd-code-surface)"
|
|
963
|
+
x-bind:class="expanded ? 'border-neutral-300 dark:border-neutral-700' : 'border-neutral-200 dark:border-neutral-800'"
|
|
964
964
|
>
|
|
965
965
|
<button
|
|
966
966
|
type="button"
|
|
967
967
|
x-on:click="expanded = !expanded"
|
|
968
968
|
x-bind:aria-expanded="expanded"
|
|
969
|
-
class="group flex w-full items-center justify-between gap-3 px-4 py-2.5 text-left text-sm font-medium text-neutral-700 hover:bg-neutral-50 cursor-pointer transition-colors duration-200"
|
|
969
|
+
class="group flex w-full items-center justify-between gap-3 px-4 py-2.5 text-left text-sm font-medium text-neutral-700 hover:bg-neutral-50 cursor-pointer transition-colors duration-200 dark:text-neutral-300 dark:hover:bg-neutral-800/60"
|
|
970
970
|
>
|
|
971
971
|
<span class="inline-flex items-center gap-2">
|
|
972
|
-
<ListChevronsToggle class="size-4 shrink-0 text-neutral-400 group-hover:text-neutral-600 transition duration-200" />
|
|
972
|
+
<ListChevronsToggle class="size-4 shrink-0 text-neutral-400 group-hover:text-neutral-600 transition duration-200 dark:text-neutral-500 dark:group-hover:text-neutral-300" />
|
|
973
973
|
<span>
|
|
974
974
|
{sectionFields.length} {noun}
|
|
975
975
|
</span>
|
|
976
976
|
</span>
|
|
977
977
|
</button>
|
|
978
978
|
<div x-show="expanded" x-collapse x-cloak>
|
|
979
|
-
<div class="border-t border-neutral-100 p-4">
|
|
979
|
+
<div class="border-t border-neutral-100 p-4 dark:border-neutral-800">
|
|
980
980
|
<ResponseFieldTree fields={sectionFields} />
|
|
981
981
|
</div>
|
|
982
982
|
</div>
|
|
@@ -990,30 +990,30 @@ const formattedBodyDescription = bodyDescription
|
|
|
990
990
|
hasCommonFields ? "mt-3" : "mt-4",
|
|
991
991
|
]}
|
|
992
992
|
>
|
|
993
|
-
<p class="text-xs text-neutral-500">
|
|
993
|
+
<p class="text-xs text-neutral-500 dark:text-neutral-400">
|
|
994
994
|
{sectionVariantData?.variantType === "anyOf"
|
|
995
995
|
? "One or more variants may apply."
|
|
996
996
|
: "One of these variants applies."}
|
|
997
997
|
</p>
|
|
998
998
|
{sectionVariants.map((variant, index) => (
|
|
999
999
|
<>
|
|
1000
|
-
<div class="rounded-lg border border-neutral-200 bg-white p-3">
|
|
1001
|
-
<div class="mb-2 text-xs font-medium text-neutral-600">
|
|
1000
|
+
<div class="rounded-lg border border-neutral-200 bg-white p-3 dark:border-neutral-800 dark:bg-neutral-900/60">
|
|
1001
|
+
<div class="mb-2 text-xs font-medium text-neutral-600 dark:text-neutral-400">
|
|
1002
1002
|
{variant.label}
|
|
1003
1003
|
</div>
|
|
1004
1004
|
<div x-data="{ expanded: false }">
|
|
1005
1005
|
<div
|
|
1006
|
-
class="w-full overflow-hidden rounded-lg border border-neutral-200 bg-white transition-colors duration-200"
|
|
1007
|
-
x-bind:class="expanded ? 'border-neutral-300' : 'border-neutral-200'"
|
|
1006
|
+
class="w-full overflow-hidden rounded-lg border border-neutral-200 bg-white transition-colors duration-200 dark:border-neutral-800 dark:bg-(--rd-code-surface)"
|
|
1007
|
+
x-bind:class="expanded ? 'border-neutral-300 dark:border-neutral-700' : 'border-neutral-200 dark:border-neutral-800'"
|
|
1008
1008
|
>
|
|
1009
1009
|
<button
|
|
1010
1010
|
type="button"
|
|
1011
1011
|
x-on:click="expanded = !expanded"
|
|
1012
1012
|
x-bind:aria-expanded="expanded"
|
|
1013
|
-
class="group flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-xs font-medium text-neutral-700 hover:bg-neutral-50 cursor-pointer transition-colors duration-200"
|
|
1013
|
+
class="group flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-xs font-medium text-neutral-700 hover:bg-neutral-50 cursor-pointer transition-colors duration-200 dark:text-neutral-300 dark:hover:bg-neutral-800/60"
|
|
1014
1014
|
>
|
|
1015
1015
|
<span class="inline-flex items-center gap-2">
|
|
1016
|
-
<ListChevronsToggle class="size-4 shrink-0 text-neutral-400 group-hover:text-neutral-600 transition duration-200" />
|
|
1016
|
+
<ListChevronsToggle class="size-4 shrink-0 text-neutral-400 group-hover:text-neutral-600 transition duration-200 dark:text-neutral-500 dark:group-hover:text-neutral-300" />
|
|
1017
1017
|
<span>
|
|
1018
1018
|
{variant.fields.length}{" "}
|
|
1019
1019
|
{variant.fields.length === 1
|
|
@@ -1023,7 +1023,7 @@ const formattedBodyDescription = bodyDescription
|
|
|
1023
1023
|
</span>
|
|
1024
1024
|
</button>
|
|
1025
1025
|
<div x-show="expanded" x-collapse x-cloak>
|
|
1026
|
-
<div class="border-t border-neutral-100 px-3 py-3">
|
|
1026
|
+
<div class="border-t border-neutral-100 px-3 py-3 dark:border-neutral-800">
|
|
1027
1027
|
<ResponseFieldTree
|
|
1028
1028
|
fields={variant.fields}
|
|
1029
1029
|
/>
|
|
@@ -1035,11 +1035,11 @@ const formattedBodyDescription = bodyDescription
|
|
|
1035
1035
|
{sectionVariantData?.variantType === "oneOf" &&
|
|
1036
1036
|
index < sectionVariants.length - 1 && (
|
|
1037
1037
|
<div class="flex items-center gap-2 py-0">
|
|
1038
|
-
<div class="h-px flex-1 bg-neutral-200" />
|
|
1039
|
-
<span class="px-1 text-[10px] uppercase tracking-wide text-neutral-500">
|
|
1038
|
+
<div class="h-px flex-1 bg-neutral-200 dark:bg-neutral-800" />
|
|
1039
|
+
<span class="px-1 text-[10px] uppercase tracking-wide text-neutral-500 dark:text-neutral-400">
|
|
1040
1040
|
OR
|
|
1041
1041
|
</span>
|
|
1042
|
-
<div class="h-px flex-1 bg-neutral-200" />
|
|
1042
|
+
<div class="h-px flex-1 bg-neutral-200 dark:bg-neutral-800" />
|
|
1043
1043
|
</div>
|
|
1044
1044
|
)}
|
|
1045
1045
|
</>
|