radiant-docs 0.1.7 → 0.1.8
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/index.js +28 -5
- package/package.json +3 -3
- package/template/astro.config.mjs +76 -3
- package/template/package-lock.json +924 -737
- package/template/package.json +7 -5
- package/template/scripts/generate-og-images.mjs +335 -0
- package/template/scripts/generate-og-metadata.mjs +173 -0
- package/template/scripts/rewrite-static-asset-host.mjs +408 -0
- package/template/scripts/stamp-image-versions.mjs +277 -0
- package/template/scripts/stamp-og-image-versions.mjs +199 -0
- package/template/scripts/stamp-pagefind-runtime-version.mjs +140 -0
- package/template/src/assets/fonts/geist-mono/cyrillic.woff2 +0 -0
- package/template/src/assets/fonts/geist-mono/latin-ext.woff2 +0 -0
- package/template/src/assets/fonts/geist-mono/latin.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/canadian-aboriginal.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/cherokee.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/latin-ext.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/latin.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/math.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/nushu.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/symbols.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/syriac.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/tifinagh.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/vietnamese.woff2 +0 -0
- package/template/src/components/Footer.astro +94 -0
- package/template/src/components/Header.astro +11 -66
- package/template/src/components/LogoLink.astro +103 -0
- package/template/src/components/MdxPage.astro +126 -11
- package/template/src/components/OpenApiPage.astro +1036 -69
- package/template/src/components/Search.astro +0 -2
- package/template/src/components/SidebarDropdown.astro +34 -14
- package/template/src/components/SidebarGroup.astro +3 -6
- package/template/src/components/SidebarLink.astro +22 -12
- package/template/src/components/SidebarMenu.astro +19 -16
- package/template/src/components/SidebarSegmented.astro +99 -0
- package/template/src/components/SidebarSubgroup.astro +12 -12
- package/template/src/components/ThemeSwitcher.astro +30 -7
- package/template/src/components/endpoint/PlaygroundBar.astro +32 -36
- package/template/src/components/endpoint/PlaygroundButton.astro +40 -4
- package/template/src/components/endpoint/PlaygroundField.astro +1068 -22
- package/template/src/components/endpoint/PlaygroundForm.astro +559 -61
- package/template/src/components/endpoint/RequestSnippets.astro +342 -193
- package/template/src/components/endpoint/ResponseDisplay.astro +161 -147
- package/template/src/components/endpoint/ResponseFieldTree.astro +134 -0
- package/template/src/components/endpoint/ResponseFields.astro +711 -68
- package/template/src/components/endpoint/ResponseSnippets.astro +299 -173
- package/template/src/components/sidebar/SidebarEndpointLink.astro +1 -1
- package/template/src/components/ui/CodeLanguageIcon.astro +19 -0
- package/template/src/components/ui/CodeTabEdge.astro +79 -0
- package/template/src/components/ui/Field.astro +103 -20
- package/template/src/components/ui/Icon.astro +32 -0
- package/template/src/components/ui/ListChevronsToggle.astro +31 -0
- package/template/src/components/ui/Tag.astro +1 -1
- package/template/src/components/user/{Accordian.astro → Accordion.astro} +6 -6
- package/template/src/components/user/Callout.astro +5 -9
- package/template/src/components/user/CodeBlock.astro +400 -0
- package/template/src/components/user/CodeGroup.astro +225 -0
- package/template/src/components/user/ComponentPreview.astro +1 -0
- package/template/src/components/user/ComponentPreviewBlock.astro +181 -0
- package/template/src/components/user/Image.astro +132 -0
- package/template/src/components/user/Steps.astro +1 -3
- package/template/src/components/user/Tabs.astro +2 -2
- package/template/src/content.config.ts +1 -0
- package/template/src/layouts/Layout.astro +109 -8
- package/template/src/lib/code/code-block.ts +546 -0
- package/template/src/lib/frontmatter-schema.ts +8 -7
- package/template/src/lib/mdx/remark-code-block-component.ts +342 -0
- package/template/src/lib/mdx/remark-demote-h1.ts +16 -0
- package/template/src/lib/pagefind.ts +19 -5
- package/template/src/lib/routes.ts +49 -31
- package/template/src/lib/utils.ts +20 -0
- package/template/src/lib/validation.ts +638 -200
- package/template/src/pages/[...slug].astro +18 -5
- package/template/src/styles/geist-mono.css +33 -0
- package/template/src/styles/global.css +89 -84
- package/template/src/styles/google-sans-flex.css +143 -0
- package/template/ec.config.mjs +0 -51
- /package/template/src/components/user/{AccordianGroup.astro → AccordionGroup.astro} +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const CWD = process.cwd();
|
|
6
|
+
const DIST_DIR = path.join(CWD, "dist");
|
|
7
|
+
const OG_IMAGES_DIR = path.join(DIST_DIR, "_og/images");
|
|
8
|
+
const VERSION_LENGTH = 12;
|
|
9
|
+
|
|
10
|
+
function findHtmlFiles(dir, files = []) {
|
|
11
|
+
if (!fs.existsSync(dir)) return files;
|
|
12
|
+
|
|
13
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const fullPath = path.join(dir, entry.name);
|
|
16
|
+
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
findHtmlFiles(fullPath, files);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
23
|
+
files.push(fullPath);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return files;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeRoutePath(routePath) {
|
|
31
|
+
if (!routePath || routePath === "/") return "/";
|
|
32
|
+
if (!routePath.startsWith("/")) routePath = `/${routePath}`;
|
|
33
|
+
return routePath.endsWith("/") ? routePath : `${routePath}/`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function routePathFromHtmlPath(filePath) {
|
|
37
|
+
const relative = path.relative(DIST_DIR, filePath).replace(/\\/g, "/");
|
|
38
|
+
|
|
39
|
+
if (relative === "index.html") return "/";
|
|
40
|
+
if (relative.endsWith("/index.html")) {
|
|
41
|
+
const base = relative.slice(0, -"/index.html".length);
|
|
42
|
+
return normalizeRoutePath(base);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (relative.endsWith(".html")) {
|
|
46
|
+
return normalizeRoutePath(relative.slice(0, -".html".length));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return "/";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function routePathToOgImageRelativePath(routePath) {
|
|
53
|
+
const normalizedRoutePath = normalizeRoutePath(routePath);
|
|
54
|
+
if (normalizedRoutePath === "/") return "index.png";
|
|
55
|
+
return `${normalizedRoutePath.slice(1, -1)}.png`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hashFile(filePath) {
|
|
59
|
+
const buffer = fs.readFileSync(filePath);
|
|
60
|
+
return crypto
|
|
61
|
+
.createHash("sha256")
|
|
62
|
+
.update(buffer)
|
|
63
|
+
.digest("hex")
|
|
64
|
+
.slice(0, VERSION_LENGTH);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function updateVersionInUrl(urlValue, version) {
|
|
68
|
+
if (typeof urlValue !== "string" || urlValue.trim().length === 0) {
|
|
69
|
+
return urlValue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const decoded = urlValue.trim().replace(/&/g, "&");
|
|
73
|
+
const hasScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(decoded);
|
|
74
|
+
const isProtocolRelative = decoded.startsWith("//");
|
|
75
|
+
const isAbsolutePath = decoded.startsWith("/");
|
|
76
|
+
|
|
77
|
+
let parsed;
|
|
78
|
+
try {
|
|
79
|
+
if (hasScheme) {
|
|
80
|
+
parsed = new URL(decoded);
|
|
81
|
+
} else if (isProtocolRelative) {
|
|
82
|
+
parsed = new URL(`https:${decoded}`);
|
|
83
|
+
} else {
|
|
84
|
+
parsed = new URL(decoded, "https://radiant.invalid/");
|
|
85
|
+
}
|
|
86
|
+
} catch {
|
|
87
|
+
return urlValue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
parsed.searchParams.set("v", version);
|
|
91
|
+
|
|
92
|
+
let encoded;
|
|
93
|
+
if (hasScheme) {
|
|
94
|
+
encoded = parsed.toString();
|
|
95
|
+
} else if (isProtocolRelative) {
|
|
96
|
+
encoded = `//${parsed.host}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
97
|
+
} else if (isAbsolutePath) {
|
|
98
|
+
encoded = `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
99
|
+
} else {
|
|
100
|
+
encoded = `${parsed.pathname.replace(/^\//, "")}${parsed.search}${parsed.hash}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return encoded.replace(/&/g, "&");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function replaceMetaImageUrl(html, attrName, attrValue, version) {
|
|
107
|
+
const patterns = [
|
|
108
|
+
new RegExp(
|
|
109
|
+
`(<meta\\s+[^>]*${attrName}\\s*=\\s*["']${attrValue}["'][^>]*\\bcontent\\s*=\\s*["'])([^"']*)(["'][^>]*>)`,
|
|
110
|
+
"gi",
|
|
111
|
+
),
|
|
112
|
+
new RegExp(
|
|
113
|
+
`(<meta\\s+[^>]*\\bcontent\\s*=\\s*["'])([^"']*)(["'][^>]*${attrName}\\s*=\\s*["']${attrValue}["'][^>]*>)`,
|
|
114
|
+
"gi",
|
|
115
|
+
),
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
let found = false;
|
|
119
|
+
let changed = false;
|
|
120
|
+
let nextHtml = html;
|
|
121
|
+
|
|
122
|
+
for (const pattern of patterns) {
|
|
123
|
+
nextHtml = nextHtml.replace(pattern, (_, prefix, currentUrl, suffix) => {
|
|
124
|
+
found = true;
|
|
125
|
+
const updatedUrl = updateVersionInUrl(currentUrl, version);
|
|
126
|
+
if (updatedUrl !== currentUrl) {
|
|
127
|
+
changed = true;
|
|
128
|
+
}
|
|
129
|
+
return `${prefix}${updatedUrl}${suffix}`;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { html: nextHtml, found, changed };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function main() {
|
|
137
|
+
if (!fs.existsSync(DIST_DIR)) {
|
|
138
|
+
console.warn("Skipping OG version stamping: dist directory not found.");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!fs.existsSync(OG_IMAGES_DIR)) {
|
|
143
|
+
console.warn("Skipping OG version stamping: OG images directory not found.");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const htmlFiles = findHtmlFiles(DIST_DIR).sort();
|
|
148
|
+
if (htmlFiles.length === 0) {
|
|
149
|
+
console.warn("Skipping OG version stamping: no HTML files found in dist.");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let updatedCount = 0;
|
|
154
|
+
let missingOgCount = 0;
|
|
155
|
+
let missingMetaCount = 0;
|
|
156
|
+
|
|
157
|
+
for (const htmlPath of htmlFiles) {
|
|
158
|
+
const routePath = routePathFromHtmlPath(htmlPath);
|
|
159
|
+
const ogImageRelativePath = routePathToOgImageRelativePath(routePath);
|
|
160
|
+
const ogImagePath = path.join(OG_IMAGES_DIR, ...ogImageRelativePath.split("/"));
|
|
161
|
+
|
|
162
|
+
if (!fs.existsSync(ogImagePath)) {
|
|
163
|
+
missingOgCount += 1;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const version = hashFile(ogImagePath);
|
|
168
|
+
const sourceHtml = fs.readFileSync(htmlPath, "utf8");
|
|
169
|
+
|
|
170
|
+
const ogResult = replaceMetaImageUrl(
|
|
171
|
+
sourceHtml,
|
|
172
|
+
"property",
|
|
173
|
+
"og:image",
|
|
174
|
+
version,
|
|
175
|
+
);
|
|
176
|
+
const twitterResult = replaceMetaImageUrl(
|
|
177
|
+
ogResult.html,
|
|
178
|
+
"name",
|
|
179
|
+
"twitter:image",
|
|
180
|
+
version,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (!ogResult.found && !twitterResult.found) {
|
|
184
|
+
missingMetaCount += 1;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (twitterResult.html !== sourceHtml) {
|
|
189
|
+
fs.writeFileSync(htmlPath, twitterResult.html, "utf8");
|
|
190
|
+
updatedCount += 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.log(
|
|
195
|
+
`✅ OG version stamping complete. Updated ${updatedCount}/${htmlFiles.length} HTML files (missing OG image: ${missingOgCount}, missing meta tags: ${missingMetaCount}).`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
main();
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const CWD = process.cwd();
|
|
6
|
+
const DIST_DIR = path.join(CWD, "dist");
|
|
7
|
+
const PAGEFIND_DIR = path.join(DIST_DIR, "pagefind");
|
|
8
|
+
const PAGEFIND_RUNTIME_PATH = path.join(PAGEFIND_DIR, "pagefind.js");
|
|
9
|
+
const VERSION_LENGTH = 12;
|
|
10
|
+
|
|
11
|
+
function findFilesRecursively(dir, files = []) {
|
|
12
|
+
if (!fs.existsSync(dir)) return files;
|
|
13
|
+
|
|
14
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
15
|
+
for (const entry of entries) {
|
|
16
|
+
const fullPath = path.join(dir, entry.name);
|
|
17
|
+
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
findFilesRecursively(fullPath, files);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (entry.isFile()) {
|
|
24
|
+
files.push(fullPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return files;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function normalizeRuntimeSourceForHash(source) {
|
|
32
|
+
return source
|
|
33
|
+
.replaceAll(
|
|
34
|
+
/pagefind-entry\.json\?(?:ts=\$\{Date\.now\(\)\}|v=[^"'`)]*)/g,
|
|
35
|
+
"pagefind-entry.json?__VERSION__",
|
|
36
|
+
)
|
|
37
|
+
.replaceAll(
|
|
38
|
+
/wasm\.\$\{language\}\.pagefind(?:\?v=[^"'`)]*)?/g,
|
|
39
|
+
"wasm.${language}.pagefind?__VERSION__",
|
|
40
|
+
)
|
|
41
|
+
.replaceAll(
|
|
42
|
+
/wasm\.unknown\.pagefind(?:\?v=[^"'`)]*)?/g,
|
|
43
|
+
"wasm.unknown.pagefind?__VERSION__",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function deriveContentVersion(runtimeSource) {
|
|
48
|
+
const hash = crypto.createHash("sha256");
|
|
49
|
+
const normalizedRuntimeSource = normalizeRuntimeSourceForHash(runtimeSource);
|
|
50
|
+
|
|
51
|
+
const pagefindFiles = findFilesRecursively(PAGEFIND_DIR).sort();
|
|
52
|
+
for (const filePath of pagefindFiles) {
|
|
53
|
+
const relativePath = path.relative(PAGEFIND_DIR, filePath).replace(/\\/g, "/");
|
|
54
|
+
hash.update(relativePath);
|
|
55
|
+
hash.update("\0");
|
|
56
|
+
|
|
57
|
+
if (filePath === PAGEFIND_RUNTIME_PATH) {
|
|
58
|
+
hash.update(normalizedRuntimeSource);
|
|
59
|
+
} else {
|
|
60
|
+
hash.update(fs.readFileSync(filePath));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
hash.update("\0");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return hash.digest("hex").slice(0, VERSION_LENGTH);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function deriveVersion(runtimeSource) {
|
|
70
|
+
const commitSha = process.env.COMMIT_SHA?.trim().toLowerCase();
|
|
71
|
+
if (commitSha && /^[a-f0-9]{7,40}$/.test(commitSha)) {
|
|
72
|
+
return {
|
|
73
|
+
version: commitSha.slice(0, VERSION_LENGTH),
|
|
74
|
+
source: "commit-sha",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
version: deriveContentVersion(runtimeSource),
|
|
80
|
+
source: "content-hash",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function stampRuntimeScript(runtimeSource, encodedVersion) {
|
|
85
|
+
let replacements = 0;
|
|
86
|
+
|
|
87
|
+
let next = runtimeSource.replaceAll(
|
|
88
|
+
/pagefind-entry\.json\?(?:ts=\$\{Date\.now\(\)\}|v=[^"'`)]*)/g,
|
|
89
|
+
() => {
|
|
90
|
+
replacements += 1;
|
|
91
|
+
return `pagefind-entry.json?v=${encodedVersion}`;
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
next = next.replaceAll(
|
|
96
|
+
/wasm\.\$\{language\}\.pagefind(?:\?v=[^"'`)]*)?/g,
|
|
97
|
+
() => {
|
|
98
|
+
replacements += 1;
|
|
99
|
+
return `wasm.\${language}.pagefind?v=${encodedVersion}`;
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
next = next.replaceAll(
|
|
104
|
+
/wasm\.unknown\.pagefind(?:\?v=[^"'`)]*)?/g,
|
|
105
|
+
() => {
|
|
106
|
+
replacements += 1;
|
|
107
|
+
return `wasm.unknown.pagefind?v=${encodedVersion}`;
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
return { next, replacements };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function main() {
|
|
115
|
+
if (!fs.existsSync(PAGEFIND_RUNTIME_PATH)) {
|
|
116
|
+
console.warn(
|
|
117
|
+
"Skipping Pagefind runtime version stamping: dist/pagefind/pagefind.js not found.",
|
|
118
|
+
);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const runtimeSource = fs.readFileSync(PAGEFIND_RUNTIME_PATH, "utf8");
|
|
123
|
+
const { version, source } = deriveVersion(runtimeSource);
|
|
124
|
+
const encodedVersion = encodeURIComponent(version);
|
|
125
|
+
|
|
126
|
+
const runtimeResult = stampRuntimeScript(runtimeSource, encodedVersion);
|
|
127
|
+
if (runtimeResult.next !== runtimeSource) {
|
|
128
|
+
fs.writeFileSync(PAGEFIND_RUNTIME_PATH, runtimeResult.next, "utf8");
|
|
129
|
+
} else if (runtimeResult.replacements === 0) {
|
|
130
|
+
console.warn(
|
|
131
|
+
"Pagefind runtime version stamping made no runtime changes. Runtime format may have changed.",
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(
|
|
136
|
+
`✅ Pagefind runtime version stamping complete (version=${version}, source=${source}, runtimeReplacements=${runtimeResult.replacements}).`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Icon from "./ui/Icon.astro";
|
|
3
|
+
import { getConfig } from "../lib/validation";
|
|
4
|
+
import LogoLink from "./LogoLink.astro";
|
|
5
|
+
|
|
6
|
+
const config = await getConfig();
|
|
7
|
+
const { footer } = config;
|
|
8
|
+
|
|
9
|
+
if (!footer) return null;
|
|
10
|
+
|
|
11
|
+
const socialIcons: Record<string, string> = {
|
|
12
|
+
x: "simple-icons:x",
|
|
13
|
+
website: "lucide:globe",
|
|
14
|
+
facebook: "simple-icons:facebook",
|
|
15
|
+
youtube: "simple-icons:youtube",
|
|
16
|
+
discord: "simple-icons:discord",
|
|
17
|
+
slack: "simple-icons:slack",
|
|
18
|
+
github: "simple-icons:github",
|
|
19
|
+
linkedin: "simple-icons:linkedin",
|
|
20
|
+
instagram: "simple-icons:instagram",
|
|
21
|
+
"hacker-news": "simple-icons:ycombinator",
|
|
22
|
+
medium: "simple-icons:medium",
|
|
23
|
+
telegram: "simple-icons:telegram",
|
|
24
|
+
bluesky: "simple-icons:bluesky",
|
|
25
|
+
threads: "simple-icons:threads",
|
|
26
|
+
reddit: "simple-icons:reddit",
|
|
27
|
+
podcast: "lucide:podcast",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<footer class="border-t border-border-light pt-16" data-pagefind-ignore>
|
|
33
|
+
<div class="flex flex-col items-center gap-6">
|
|
34
|
+
<!-- Branding -->
|
|
35
|
+
<LogoLink
|
|
36
|
+
class="flex items-center gap-2.5 text-xl font-bold text-neutral-800 dark:text-neutral-100"
|
|
37
|
+
/>
|
|
38
|
+
<!-- Socials -->
|
|
39
|
+
{
|
|
40
|
+
footer.socials && (
|
|
41
|
+
<div class="flex items-center justify-center gap-6">
|
|
42
|
+
{Object.entries(footer.socials).map(([platform, url]) => (
|
|
43
|
+
<a
|
|
44
|
+
href={url}
|
|
45
|
+
class="text-neutral-400 hover:text-neutral-600 dark:text-neutral-500 dark:hover:text-neutral-300 transition-all duration-300 transform hover:scale-105"
|
|
46
|
+
target="_blank"
|
|
47
|
+
rel="noopener noreferrer"
|
|
48
|
+
title={platform}
|
|
49
|
+
>
|
|
50
|
+
<Icon
|
|
51
|
+
name={socialIcons[platform] || "lucide:external-link"}
|
|
52
|
+
class="w-[18px] h-[18px]"
|
|
53
|
+
/>
|
|
54
|
+
</a>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
{
|
|
60
|
+
footer.links && (
|
|
61
|
+
<div class="flex flex-wrap justify-center gap-x-8 gap-y-3">
|
|
62
|
+
{footer.links.map((link) => (
|
|
63
|
+
<a
|
|
64
|
+
href={link.href}
|
|
65
|
+
class="text-sm text-neutral-500 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100 transition-colors duration-300"
|
|
66
|
+
>
|
|
67
|
+
{link.text}
|
|
68
|
+
</a>
|
|
69
|
+
))}
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<!-- Links -->
|
|
76
|
+
</footer>
|
|
77
|
+
|
|
78
|
+
<div
|
|
79
|
+
class="mt-20 py-8 border-t border-neutral-100 dark:border-neutral-800/60 flex flex-col sm:flex-row justify-between items-center gap-4"
|
|
80
|
+
>
|
|
81
|
+
<p class="text-[13px] text-neutral-400 dark:text-neutral-500">
|
|
82
|
+
© {new Date().getFullYear()}
|
|
83
|
+
{config.title}. All rights reserved.
|
|
84
|
+
</p>
|
|
85
|
+
<a
|
|
86
|
+
href="https://radiant.io"
|
|
87
|
+
class="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"
|
|
88
|
+
>
|
|
89
|
+
Built with <span
|
|
90
|
+
class="group-hover:text-neutral-900 dark:group-hover:text-neutral-100 font-medium duration-200"
|
|
91
|
+
>Radiant Docs</span
|
|
92
|
+
>
|
|
93
|
+
</a>
|
|
94
|
+
</div>
|
|
@@ -1,40 +1,22 @@
|
|
|
1
1
|
---
|
|
2
|
-
import
|
|
2
|
+
import Icon from "./ui/Icon.astro";
|
|
3
3
|
import { getConfig } from "../lib/validation";
|
|
4
4
|
import Search from "./Search.astro";
|
|
5
|
+
import LogoLink from "./LogoLink.astro";
|
|
5
6
|
|
|
6
7
|
const config = await getConfig();
|
|
7
|
-
|
|
8
|
-
function getLogoUrl(logoPath: string | undefined): string | null {
|
|
9
|
-
if (!logoPath) return null;
|
|
10
|
-
|
|
11
|
-
// Normalize path: remove leading slash if present
|
|
12
|
-
const normalizedPath = logoPath.startsWith("/")
|
|
13
|
-
? logoPath.slice(1)
|
|
14
|
-
: logoPath;
|
|
15
|
-
|
|
16
|
-
// Return public URL - Vite plugin ensures file exists in public
|
|
17
|
-
return `/${normalizedPath}`;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Load light and dark logos
|
|
21
|
-
const lightLogoUrl = getLogoUrl(config.logo?.light);
|
|
22
|
-
const darkLogoUrl = getLogoUrl(config.logo?.dark);
|
|
23
|
-
|
|
24
|
-
// Get the href for the logo link (defaults to "/")
|
|
25
|
-
const logoHref = config.logo?.href || "/";
|
|
26
8
|
---
|
|
27
9
|
|
|
28
10
|
<header
|
|
29
11
|
class:list={[
|
|
30
|
-
"fixed z-30 inset-x-1 top-0 h-16 mt-1 border-x border-t border-border rounded-t-xl overflow-hidden",
|
|
12
|
+
"fixed z-30 inset-x-1 top-0 h-16 mt-1 border-x border-t border-border rounded-t-xl overflow-hidden border-b border-b-border-light",
|
|
31
13
|
config.navbar?.blur
|
|
32
|
-
? "bg-background/80 backdrop-blur-[18px] backdrop-saturate-
|
|
14
|
+
? "bg-background/80 backdrop-blur-[18px] backdrop-saturate-50 border-b-neutral-100/85 bg-clip-padding"
|
|
33
15
|
: "bg-background",
|
|
34
16
|
]}
|
|
35
17
|
data-pagefind-ignore
|
|
36
18
|
>
|
|
37
|
-
<div class="flex h-full
|
|
19
|
+
<div class="flex h-full">
|
|
38
20
|
<div class="lg:w-[283px] h-full">
|
|
39
21
|
<div class="flex gap-3 lg:block h-full lg:border-r border-border-light">
|
|
40
22
|
<button
|
|
@@ -57,44 +39,7 @@ const logoHref = config.logo?.href || "/";
|
|
|
57
39
|
</div>
|
|
58
40
|
</div>
|
|
59
41
|
</button>
|
|
60
|
-
<
|
|
61
|
-
href={logoHref}
|
|
62
|
-
class="h-full flex items-center justify-center gap-2 lg:gap-3 text-xl font-bold text-neutral-800 dark:text-neutral-100"
|
|
63
|
-
>
|
|
64
|
-
{
|
|
65
|
-
lightLogoUrl || darkLogoUrl ? (
|
|
66
|
-
<>
|
|
67
|
-
{/* Light mode: show light logo if available, otherwise show title */}
|
|
68
|
-
{lightLogoUrl ? (
|
|
69
|
-
<img
|
|
70
|
-
src={lightLogoUrl}
|
|
71
|
-
class="max-h-[40px] w-auto max-w-[200px] object-contain dark:hidden"
|
|
72
|
-
alt="Logo"
|
|
73
|
-
/>
|
|
74
|
-
) : (
|
|
75
|
-
<span class="dark:hidden">{config.title}</span>
|
|
76
|
-
)}
|
|
77
|
-
|
|
78
|
-
{/* Dark mode: show dark logo if available, otherwise show title */}
|
|
79
|
-
{darkLogoUrl ? (
|
|
80
|
-
<img
|
|
81
|
-
src={darkLogoUrl}
|
|
82
|
-
class="max-h-[40px] w-auto max-w-[200px] object-contain hidden dark:block"
|
|
83
|
-
alt="Logo"
|
|
84
|
-
/>
|
|
85
|
-
) : (
|
|
86
|
-
<span class="hidden dark:block">{config.title}</span>
|
|
87
|
-
)}
|
|
88
|
-
</>
|
|
89
|
-
) : (
|
|
90
|
-
config.title
|
|
91
|
-
)
|
|
92
|
-
}
|
|
93
|
-
<span
|
|
94
|
-
class="text-[10px] text-neutral-500 font-semibold bg-neutral-100 dark:bg-neutral-800 px-2 py-px mt-0.5 rounded-full border border-neutral-200 dark:border-neutral-700/70 shadow-xs"
|
|
95
|
-
>Docs</span
|
|
96
|
-
>
|
|
97
|
-
</a>
|
|
42
|
+
<LogoLink />
|
|
98
43
|
</div>
|
|
99
44
|
</div>
|
|
100
45
|
<div class="flex-1 flex">
|
|
@@ -122,7 +67,7 @@ const logoHref = config.logo?.href || "/";
|
|
|
122
67
|
{l.icon && (
|
|
123
68
|
<Icon
|
|
124
69
|
class="ml-0.5"
|
|
125
|
-
name={
|
|
70
|
+
name={l.icon}
|
|
126
71
|
width="16"
|
|
127
72
|
height="16"
|
|
128
73
|
/>
|
|
@@ -135,14 +80,14 @@ const logoHref = config.logo?.href || "/";
|
|
|
135
80
|
{config.navbar.secondary && (
|
|
136
81
|
<a
|
|
137
82
|
class:list={[
|
|
138
|
-
"items-center gap-1.5 px-3 py-[5px] text-sm
|
|
83
|
+
"items-center gap-1.5 px-3 py-[5px] text-sm bg-white text-neutral-600 hover:text-neutral-700 rounded-md border border-neutral-200 shadow-xs hover:shadow-sm transition-all whitespace-nowrap",
|
|
139
84
|
config.navbar.primary ? "hidden lg:flex" : "flex",
|
|
140
85
|
]}
|
|
141
86
|
href={config.navbar.secondary.href}
|
|
142
87
|
>
|
|
143
88
|
{config.navbar.secondary.icon && (
|
|
144
89
|
<Icon
|
|
145
|
-
name={
|
|
90
|
+
name={config.navbar.secondary.icon}
|
|
146
91
|
width="16"
|
|
147
92
|
height="16"
|
|
148
93
|
/>
|
|
@@ -153,13 +98,13 @@ const logoHref = config.logo?.href || "/";
|
|
|
153
98
|
{config.navbar.primary && (
|
|
154
99
|
<a
|
|
155
100
|
class:list={[
|
|
156
|
-
"flex items-center gap-1.5 px-5 py-[5px] text-sm
|
|
101
|
+
"flex items-center gap-1.5 px-5 py-[5px] text-sm rounded-md bg-neutral-900 text-white/95 hover:text-white duration-200 shadow-[inset_0_1px_0_rgb(255,255,255,0.3),0_0_0_1px_var(--color-neutral-800)] transition-all whitespace-nowrap",
|
|
157
102
|
]}
|
|
158
103
|
href={config.navbar.primary.href}
|
|
159
104
|
>
|
|
160
105
|
{config.navbar.primary.icon && (
|
|
161
106
|
<Icon
|
|
162
|
-
name={
|
|
107
|
+
name={config.navbar.primary.icon}
|
|
163
108
|
width="16"
|
|
164
109
|
height="16"
|
|
165
110
|
/>
|