radiant-docs 0.1.38 → 0.1.40
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 +38 -7
- package/template/package-lock.json +19 -7
- package/template/package.json +1 -1
- package/template/scripts/generate-robots-txt.mjs +29 -1
- package/template/scripts/stamp-image-versions.mjs +59 -33
- package/template/src/components/Footer.astro +2 -1
- package/template/src/components/Header.astro +8 -6
- package/template/src/components/LogoLink.astro +2 -1
- package/template/src/components/MdxPage.astro +15 -4
- package/template/src/components/PagePagination.astro +61 -0
- package/template/src/components/SidebarDropdown.astro +12 -8
- package/template/src/components/SidebarGroup.astro +1 -1
- package/template/src/components/SidebarMenu.astro +1 -1
- package/template/src/components/SidebarSegmented.astro +6 -5
- package/template/src/components/TableOfContents.astro +4 -13
- package/template/src/components/chat/AskAiWidget.tsx +274 -39
- package/template/src/components/endpoint/PlaygroundForm.astro +2 -1
- package/template/src/components/user/CodeBlock.astro +8 -5
- package/template/src/components/user/CodeGroup.astro +262 -14
- package/template/src/components/user/ComponentPreviewBlock.astro +4 -3
- package/template/src/components/user/Image.astro +43 -53
- package/template/src/components/user/Tabs.astro +128 -23
- package/template/src/layouts/Layout.astro +217 -7
- package/template/src/lib/base-path.ts +98 -0
- package/template/src/lib/component-error.ts +49 -10
- package/template/src/lib/mdx/remark-resolve-internal-links.ts +128 -18
- package/template/src/lib/pagefind.ts +62 -14
- package/template/src/lib/routes.ts +49 -1
- package/template/src/lib/static-asset-url.ts +3 -1
- package/template/src/lib/utils.ts +12 -4
- package/template/src/lib/validation.ts +376 -36
- package/template/src/pages/404.astro +2 -1
- package/template/src/pages/[...slug].astro +68 -6
- package/template/src/styles/global.css +85 -1
|
@@ -167,22 +167,58 @@ export type NavbarItem = {
|
|
|
167
167
|
href: string;
|
|
168
168
|
icon?: string | null;
|
|
169
169
|
};
|
|
170
|
-
export type
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
top?: number;
|
|
174
|
-
bottom?: number;
|
|
175
|
-
};
|
|
170
|
+
export type HiddenPageRoute = {
|
|
171
|
+
filePath: string;
|
|
172
|
+
href: string;
|
|
176
173
|
};
|
|
174
|
+
type InternalPageHrefResolution = HiddenPageRoute & {
|
|
175
|
+
linkHref: string;
|
|
176
|
+
};
|
|
177
|
+
export type LogoVariant =
|
|
178
|
+
| string
|
|
179
|
+
| {
|
|
180
|
+
image: string;
|
|
181
|
+
padding?: {
|
|
182
|
+
top?: number;
|
|
183
|
+
bottom?: number;
|
|
184
|
+
};
|
|
185
|
+
};
|
|
177
186
|
export type Logo = {
|
|
178
187
|
light?: LogoVariant;
|
|
179
188
|
dark?: LogoVariant;
|
|
180
189
|
href?: string;
|
|
181
190
|
pill?: string | false;
|
|
182
191
|
};
|
|
192
|
+
export const BASE_COLOR_OPTIONS = [
|
|
193
|
+
"slate",
|
|
194
|
+
"gray",
|
|
195
|
+
"zinc",
|
|
196
|
+
"neutral",
|
|
197
|
+
"stone",
|
|
198
|
+
"taupe",
|
|
199
|
+
"mauve",
|
|
200
|
+
"mist",
|
|
201
|
+
"olive",
|
|
202
|
+
] as const;
|
|
203
|
+
export type BaseColorOption = (typeof BASE_COLOR_OPTIONS)[number];
|
|
204
|
+
export type BaseColorByMode = {
|
|
205
|
+
light: BaseColorOption;
|
|
206
|
+
dark: BaseColorOption;
|
|
207
|
+
};
|
|
208
|
+
export const DEFAULT_THEME_COLOR_LIGHT = "#171717";
|
|
209
|
+
export const DEFAULT_THEME_COLOR_DARK = "#f5f5f5";
|
|
210
|
+
export type ThemeColorByMode = {
|
|
211
|
+
light: string;
|
|
212
|
+
dark: string;
|
|
213
|
+
};
|
|
214
|
+
export type DocsTheme = {
|
|
215
|
+
baseColor?: BaseColorOption | BaseColorByMode;
|
|
216
|
+
themeColor?: string | ThemeColorByMode;
|
|
217
|
+
};
|
|
183
218
|
export type DocsConfig = {
|
|
184
219
|
title: string;
|
|
185
220
|
logo?: Logo;
|
|
221
|
+
theme?: DocsTheme;
|
|
186
222
|
home?: string;
|
|
187
223
|
navigation: NavigationItem;
|
|
188
224
|
navbar?: {
|
|
@@ -195,6 +231,7 @@ export type DocsConfig = {
|
|
|
195
231
|
proxy?: boolean;
|
|
196
232
|
};
|
|
197
233
|
footer?: Footer;
|
|
234
|
+
hiddenPageRoutes?: HiddenPageRoute[];
|
|
198
235
|
};
|
|
199
236
|
|
|
200
237
|
export type SocialPlatform =
|
|
@@ -295,6 +332,54 @@ function normalizeDocsPagePath(
|
|
|
295
332
|
return normalizedPath;
|
|
296
333
|
}
|
|
297
334
|
|
|
335
|
+
function splitHrefPathAndSuffix(href: string): {
|
|
336
|
+
pathname: string;
|
|
337
|
+
suffix: string;
|
|
338
|
+
} {
|
|
339
|
+
const match = href.match(/^([^?#]*)(.*)$/);
|
|
340
|
+
return {
|
|
341
|
+
pathname: match?.[1] ?? href,
|
|
342
|
+
suffix: match?.[2] ?? "",
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function normalizeInternalPageHref(
|
|
347
|
+
href: string,
|
|
348
|
+
currentPath: Path,
|
|
349
|
+
label: string,
|
|
350
|
+
): InternalPageHrefResolution | null {
|
|
351
|
+
const trimmedHref = href.trim();
|
|
352
|
+
if (trimmedHref === "") {
|
|
353
|
+
throwConfigError(`${label} cannot be an empty string`, currentPath);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (isUrl(trimmedHref)) {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (!trimmedHref.startsWith("/")) {
|
|
361
|
+
throwConfigError(
|
|
362
|
+
`${label} must be either a valid URL (http:// or https://) or an internal path (starting with /)`,
|
|
363
|
+
currentPath,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const { pathname, suffix } = splitHrefPathAndSuffix(trimmedHref);
|
|
368
|
+
const normalizedPathname = pathname.replace(/\/{2,}/g, "/");
|
|
369
|
+
if (normalizedPathname === "/" || normalizedPathname === "") {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const filePath = normalizeDocsPagePath(normalizedPathname, currentPath, label);
|
|
374
|
+
validateFileExistence(filePath, currentPath);
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
filePath,
|
|
378
|
+
href: `/${filePath}`,
|
|
379
|
+
linkHref: `/${filePath}${suffix}`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
298
383
|
// Cache for OpenAPI specs (key: filePathOrUrl, value: parsed spec)
|
|
299
384
|
const openApiSpecCache = new Map<string, any>();
|
|
300
385
|
|
|
@@ -662,7 +747,11 @@ async function validateNavigationNode(
|
|
|
662
747
|
continue;
|
|
663
748
|
}
|
|
664
749
|
|
|
665
|
-
await validateNavigationNode(
|
|
750
|
+
await validateNavigationNode(
|
|
751
|
+
child,
|
|
752
|
+
[...path, "pages", i],
|
|
753
|
+
groupDepth + 1,
|
|
754
|
+
);
|
|
666
755
|
}
|
|
667
756
|
return;
|
|
668
757
|
}
|
|
@@ -1049,9 +1138,12 @@ async function validateNavMenu(menu: any, currentPath: Path) {
|
|
|
1049
1138
|
}
|
|
1050
1139
|
}
|
|
1051
1140
|
|
|
1052
|
-
function validateNavbarItem(
|
|
1141
|
+
function validateNavbarItem(
|
|
1142
|
+
item: any,
|
|
1143
|
+
currentPath: Path,
|
|
1144
|
+
): HiddenPageRoute | null {
|
|
1053
1145
|
// Check if object exists, otherwise we skip (it's optional)
|
|
1054
|
-
if (item === undefined) return;
|
|
1146
|
+
if (item === undefined) return null;
|
|
1055
1147
|
|
|
1056
1148
|
checkType(item, "object", currentPath, "Navbar item");
|
|
1057
1149
|
|
|
@@ -1069,8 +1161,18 @@ function validateNavbarItem(item: any, currentPath: Path): void {
|
|
|
1069
1161
|
]);
|
|
1070
1162
|
}
|
|
1071
1163
|
|
|
1164
|
+
const hiddenPageRoute = normalizeInternalPageHref(
|
|
1165
|
+
item.href,
|
|
1166
|
+
[...currentPath, "href"],
|
|
1167
|
+
"Navbar item href",
|
|
1168
|
+
);
|
|
1169
|
+
if (hiddenPageRoute) {
|
|
1170
|
+
item.href = hiddenPageRoute.linkHref;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1072
1173
|
// Optional property
|
|
1073
1174
|
validateIcon(item.icon, [...currentPath, "icon"]);
|
|
1175
|
+
return hiddenPageRoute;
|
|
1074
1176
|
}
|
|
1075
1177
|
|
|
1076
1178
|
// --- Top-Level Validation Functions (Your Clean API) ---
|
|
@@ -1080,7 +1182,11 @@ function validateTitle(title: DocsConfig["title"]) {
|
|
|
1080
1182
|
if (!title) throwConfigError("Title is missing.", ["title"]);
|
|
1081
1183
|
}
|
|
1082
1184
|
|
|
1083
|
-
function validateLogoPaddingValue(
|
|
1185
|
+
function validateLogoPaddingValue(
|
|
1186
|
+
value: unknown,
|
|
1187
|
+
currentPath: Path,
|
|
1188
|
+
label: string,
|
|
1189
|
+
): void {
|
|
1084
1190
|
if (value === undefined) return;
|
|
1085
1191
|
|
|
1086
1192
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
@@ -1134,7 +1240,11 @@ function validateLogoVariant(
|
|
|
1134
1240
|
return;
|
|
1135
1241
|
}
|
|
1136
1242
|
|
|
1137
|
-
if (
|
|
1243
|
+
if (
|
|
1244
|
+
typeof variant !== "object" ||
|
|
1245
|
+
variant === null ||
|
|
1246
|
+
Array.isArray(variant)
|
|
1247
|
+
) {
|
|
1138
1248
|
throwConfigError(
|
|
1139
1249
|
`Logo ${mode} must be a string path or an object with 'image' and optional 'padding'.`,
|
|
1140
1250
|
currentPath,
|
|
@@ -1152,13 +1262,17 @@ function validateLogoVariant(
|
|
|
1152
1262
|
}
|
|
1153
1263
|
|
|
1154
1264
|
if (typeof variant.image !== "string") {
|
|
1155
|
-
throwConfigError(
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
);
|
|
1265
|
+
throwConfigError(`Logo ${mode} object must include an 'image' string.`, [
|
|
1266
|
+
...currentPath,
|
|
1267
|
+
"image",
|
|
1268
|
+
]);
|
|
1159
1269
|
}
|
|
1160
1270
|
|
|
1161
|
-
validateLogoImagePath(
|
|
1271
|
+
validateLogoImagePath(
|
|
1272
|
+
variant.image,
|
|
1273
|
+
[...currentPath, "image"],
|
|
1274
|
+
`Logo ${mode} image`,
|
|
1275
|
+
);
|
|
1162
1276
|
|
|
1163
1277
|
if (variant.padding === undefined) return;
|
|
1164
1278
|
|
|
@@ -1242,10 +1356,214 @@ function validateLogo(logo: DocsConfig["logo"]) {
|
|
|
1242
1356
|
);
|
|
1243
1357
|
}
|
|
1244
1358
|
} else if (logo.pill !== false) {
|
|
1245
|
-
throwConfigError("Logo pill must be a string or false.", [
|
|
1359
|
+
throwConfigError("Logo pill must be a string or false.", [
|
|
1360
|
+
"logo",
|
|
1361
|
+
"pill",
|
|
1362
|
+
]);
|
|
1246
1363
|
}
|
|
1247
1364
|
}
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
function validateTheme(theme: DocsConfig["theme"]): void {
|
|
1368
|
+
if (theme === undefined) return;
|
|
1369
|
+
|
|
1370
|
+
checkType(theme, "object", ["theme"], "Theme configuration");
|
|
1371
|
+
if (typeof theme !== "object" || theme === null || Array.isArray(theme)) {
|
|
1372
|
+
throwConfigError("Theme configuration must be an object.", ["theme"]);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
const normalizeBaseColor = (
|
|
1376
|
+
value: unknown,
|
|
1377
|
+
currentPath: Path,
|
|
1378
|
+
label: string,
|
|
1379
|
+
): BaseColorOption => {
|
|
1380
|
+
checkType(value, "string", currentPath, label);
|
|
1381
|
+
if (typeof value !== "string") {
|
|
1382
|
+
throwConfigError(`${label} must be a string.`, currentPath);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const normalizedBaseColor = (value as string).trim().toLowerCase();
|
|
1386
|
+
if (normalizedBaseColor.length === 0) {
|
|
1387
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
if (!BASE_COLOR_OPTIONS.includes(normalizedBaseColor as BaseColorOption)) {
|
|
1391
|
+
throwConfigError(
|
|
1392
|
+
`${label} must be one of: ${BASE_COLOR_OPTIONS.join(", ")}.`,
|
|
1393
|
+
currentPath,
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
return normalizedBaseColor as BaseColorOption;
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
const normalizeThemeColor = (
|
|
1401
|
+
value: unknown,
|
|
1402
|
+
currentPath: Path,
|
|
1403
|
+
label: string,
|
|
1404
|
+
): string => {
|
|
1405
|
+
checkType(value, "string", currentPath, label);
|
|
1406
|
+
if (typeof value !== "string") {
|
|
1407
|
+
throwConfigError(`${label} must be a string.`, currentPath);
|
|
1408
|
+
}
|
|
1248
1409
|
|
|
1410
|
+
const trimmedValue = (value as string).trim();
|
|
1411
|
+
if (trimmedValue.length === 0) {
|
|
1412
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
const normalizedValue = trimmedValue.startsWith("#")
|
|
1416
|
+
? trimmedValue
|
|
1417
|
+
: `#${trimmedValue}`;
|
|
1418
|
+
if (
|
|
1419
|
+
!/^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/.test(
|
|
1420
|
+
normalizedValue,
|
|
1421
|
+
)
|
|
1422
|
+
) {
|
|
1423
|
+
throwConfigError(
|
|
1424
|
+
`${label} must be a valid hex color (for example: #1d4ed8).`,
|
|
1425
|
+
currentPath,
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
return normalizedValue.toLowerCase();
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
if (theme.baseColor !== undefined) {
|
|
1433
|
+
if (typeof theme.baseColor === "string") {
|
|
1434
|
+
theme.baseColor = normalizeBaseColor(
|
|
1435
|
+
theme.baseColor,
|
|
1436
|
+
["theme", "baseColor"],
|
|
1437
|
+
"Theme base color",
|
|
1438
|
+
);
|
|
1439
|
+
} else {
|
|
1440
|
+
checkType(
|
|
1441
|
+
theme.baseColor,
|
|
1442
|
+
"object",
|
|
1443
|
+
["theme", "baseColor"],
|
|
1444
|
+
"Theme base color",
|
|
1445
|
+
);
|
|
1446
|
+
if (
|
|
1447
|
+
typeof theme.baseColor !== "object" ||
|
|
1448
|
+
theme.baseColor === null ||
|
|
1449
|
+
Array.isArray(theme.baseColor)
|
|
1450
|
+
) {
|
|
1451
|
+
throwConfigError(
|
|
1452
|
+
"Theme base color must be a string or an object with light/dark values.",
|
|
1453
|
+
["theme", "baseColor"],
|
|
1454
|
+
);
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
const baseColorByMode = theme.baseColor as Record<string, unknown>;
|
|
1458
|
+
const allowedKeys = new Set(["light", "dark"]);
|
|
1459
|
+
for (const key of Object.keys(baseColorByMode)) {
|
|
1460
|
+
if (!allowedKeys.has(key)) {
|
|
1461
|
+
throwConfigError(
|
|
1462
|
+
"Theme base color object only supports 'light' and 'dark'.",
|
|
1463
|
+
["theme", "baseColor", key],
|
|
1464
|
+
);
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
const light =
|
|
1469
|
+
baseColorByMode.light !== undefined
|
|
1470
|
+
? normalizeBaseColor(
|
|
1471
|
+
baseColorByMode.light,
|
|
1472
|
+
["theme", "baseColor", "light"],
|
|
1473
|
+
"Theme base color light",
|
|
1474
|
+
)
|
|
1475
|
+
: undefined;
|
|
1476
|
+
const dark =
|
|
1477
|
+
baseColorByMode.dark !== undefined
|
|
1478
|
+
? normalizeBaseColor(
|
|
1479
|
+
baseColorByMode.dark,
|
|
1480
|
+
["theme", "baseColor", "dark"],
|
|
1481
|
+
"Theme base color dark",
|
|
1482
|
+
)
|
|
1483
|
+
: undefined;
|
|
1484
|
+
|
|
1485
|
+
if (!light && !dark) {
|
|
1486
|
+
throwConfigError(
|
|
1487
|
+
"Theme base color object must include 'light', 'dark', or both.",
|
|
1488
|
+
["theme", "baseColor"],
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
const resolvedLight: BaseColorOption = light ?? "neutral";
|
|
1493
|
+
const resolvedDark: BaseColorOption = dark ?? "neutral";
|
|
1494
|
+
|
|
1495
|
+
theme.baseColor = {
|
|
1496
|
+
light: resolvedLight,
|
|
1497
|
+
dark: resolvedDark,
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
if (theme.themeColor === undefined) {
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
if (typeof theme.themeColor === "string") {
|
|
1507
|
+
theme.themeColor = normalizeThemeColor(
|
|
1508
|
+
theme.themeColor,
|
|
1509
|
+
["theme", "themeColor"],
|
|
1510
|
+
"Theme color",
|
|
1511
|
+
);
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
checkType(theme.themeColor, "object", ["theme", "themeColor"], "Theme color");
|
|
1516
|
+
if (
|
|
1517
|
+
typeof theme.themeColor !== "object" ||
|
|
1518
|
+
theme.themeColor === null ||
|
|
1519
|
+
Array.isArray(theme.themeColor)
|
|
1520
|
+
) {
|
|
1521
|
+
throwConfigError(
|
|
1522
|
+
"Theme color must be a string or an object with light/dark values.",
|
|
1523
|
+
["theme", "themeColor"],
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const themeColorByMode = theme.themeColor as Record<string, unknown>;
|
|
1528
|
+
const allowedKeys = new Set(["light", "dark"]);
|
|
1529
|
+
for (const key of Object.keys(themeColorByMode)) {
|
|
1530
|
+
if (!allowedKeys.has(key)) {
|
|
1531
|
+
throwConfigError("Theme color object only supports 'light' and 'dark'.", [
|
|
1532
|
+
"theme",
|
|
1533
|
+
"themeColor",
|
|
1534
|
+
key,
|
|
1535
|
+
]);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
const light =
|
|
1540
|
+
themeColorByMode.light !== undefined
|
|
1541
|
+
? normalizeThemeColor(
|
|
1542
|
+
themeColorByMode.light,
|
|
1543
|
+
["theme", "themeColor", "light"],
|
|
1544
|
+
"Theme color light",
|
|
1545
|
+
)
|
|
1546
|
+
: undefined;
|
|
1547
|
+
const dark =
|
|
1548
|
+
themeColorByMode.dark !== undefined
|
|
1549
|
+
? normalizeThemeColor(
|
|
1550
|
+
themeColorByMode.dark,
|
|
1551
|
+
["theme", "themeColor", "dark"],
|
|
1552
|
+
"Theme color dark",
|
|
1553
|
+
)
|
|
1554
|
+
: undefined;
|
|
1555
|
+
|
|
1556
|
+
if (!light && !dark) {
|
|
1557
|
+
throwConfigError(
|
|
1558
|
+
"Theme color object must include 'light', 'dark', or both.",
|
|
1559
|
+
["theme", "themeColor"],
|
|
1560
|
+
);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
theme.themeColor = {
|
|
1564
|
+
light: light ?? DEFAULT_THEME_COLOR_LIGHT,
|
|
1565
|
+
dark: dark ?? DEFAULT_THEME_COLOR_DARK,
|
|
1566
|
+
};
|
|
1249
1567
|
}
|
|
1250
1568
|
|
|
1251
1569
|
function validateHome(home: DocsConfig["home"]): string | undefined {
|
|
@@ -1256,8 +1574,9 @@ function validateHome(home: DocsConfig["home"]): string | undefined {
|
|
|
1256
1574
|
return normalizedHome;
|
|
1257
1575
|
}
|
|
1258
1576
|
|
|
1259
|
-
function validateNavbar(navbar: DocsConfig["navbar"]) {
|
|
1260
|
-
|
|
1577
|
+
function validateNavbar(navbar: DocsConfig["navbar"]): HiddenPageRoute[] {
|
|
1578
|
+
const hiddenPageRoutes: HiddenPageRoute[] = [];
|
|
1579
|
+
if (navbar === undefined) return hiddenPageRoutes; // Navbar itself is optional
|
|
1261
1580
|
|
|
1262
1581
|
checkType(navbar, "object", ["navbar"], "Navbar configuration");
|
|
1263
1582
|
|
|
@@ -1265,10 +1584,18 @@ function validateNavbar(navbar: DocsConfig["navbar"]) {
|
|
|
1265
1584
|
checkType(navbar.blur, "boolean", ["navbar", "blur"], "Navbar blur setting");
|
|
1266
1585
|
|
|
1267
1586
|
// Validate 'primary' item
|
|
1268
|
-
validateNavbarItem(navbar.primary, [
|
|
1587
|
+
const primaryPageRoute = validateNavbarItem(navbar.primary, [
|
|
1588
|
+
"navbar",
|
|
1589
|
+
"primary",
|
|
1590
|
+
]);
|
|
1591
|
+
if (primaryPageRoute) hiddenPageRoutes.push(primaryPageRoute);
|
|
1269
1592
|
|
|
1270
1593
|
// Validate 'secondary' item
|
|
1271
|
-
validateNavbarItem(navbar.secondary, [
|
|
1594
|
+
const secondaryPageRoute = validateNavbarItem(navbar.secondary, [
|
|
1595
|
+
"navbar",
|
|
1596
|
+
"secondary",
|
|
1597
|
+
]);
|
|
1598
|
+
if (secondaryPageRoute) hiddenPageRoutes.push(secondaryPageRoute);
|
|
1272
1599
|
|
|
1273
1600
|
// Validate 'links' array
|
|
1274
1601
|
if (navbar.links !== undefined) {
|
|
@@ -1282,13 +1609,17 @@ function validateNavbar(navbar: DocsConfig["navbar"]) {
|
|
|
1282
1609
|
}
|
|
1283
1610
|
|
|
1284
1611
|
navbar.links.forEach((link: any, i: number) => {
|
|
1285
|
-
validateNavbarItem(link, ["navbar", "links", i]);
|
|
1612
|
+
const hiddenPageRoute = validateNavbarItem(link, ["navbar", "links", i]);
|
|
1613
|
+
if (hiddenPageRoute) hiddenPageRoutes.push(hiddenPageRoute);
|
|
1286
1614
|
});
|
|
1287
1615
|
}
|
|
1616
|
+
|
|
1617
|
+
return hiddenPageRoutes;
|
|
1288
1618
|
}
|
|
1289
1619
|
|
|
1290
|
-
function validateFooter(footer: DocsConfig["footer"]) {
|
|
1291
|
-
|
|
1620
|
+
function validateFooter(footer: DocsConfig["footer"]): HiddenPageRoute[] {
|
|
1621
|
+
const hiddenPageRoutes: HiddenPageRoute[] = [];
|
|
1622
|
+
if (footer === undefined) return hiddenPageRoutes;
|
|
1292
1623
|
|
|
1293
1624
|
checkType(footer, "object", ["footer"], "Footer configuration");
|
|
1294
1625
|
|
|
@@ -1367,19 +1698,19 @@ function validateFooter(footer: DocsConfig["footer"]) {
|
|
|
1367
1698
|
]);
|
|
1368
1699
|
}
|
|
1369
1700
|
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
if (
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
["footer", "links", i, "href"],
|
|
1379
|
-
);
|
|
1701
|
+
const hiddenPageRoute = normalizeInternalPageHref(
|
|
1702
|
+
link.href,
|
|
1703
|
+
["footer", "links", i, "href"],
|
|
1704
|
+
"Footer link href",
|
|
1705
|
+
);
|
|
1706
|
+
if (hiddenPageRoute) {
|
|
1707
|
+
link.href = hiddenPageRoute.linkHref;
|
|
1708
|
+
hiddenPageRoutes.push(hiddenPageRoute);
|
|
1380
1709
|
}
|
|
1381
1710
|
});
|
|
1382
1711
|
}
|
|
1712
|
+
|
|
1713
|
+
return hiddenPageRoutes;
|
|
1383
1714
|
}
|
|
1384
1715
|
|
|
1385
1716
|
async function validateNavigation(navigation: DocsConfig["navigation"]) {
|
|
@@ -1433,8 +1764,7 @@ async function validateConfig(config: any): Promise<DocsConfig> {
|
|
|
1433
1764
|
// Execute top-level checks sequentially
|
|
1434
1765
|
validateTitle(config.title);
|
|
1435
1766
|
validateLogo(config.logo);
|
|
1436
|
-
|
|
1437
|
-
validateFooter(config.footer);
|
|
1767
|
+
validateTheme(config.theme);
|
|
1438
1768
|
await validateNavigation(config.navigation);
|
|
1439
1769
|
config.home = validateHome(config.home);
|
|
1440
1770
|
|
|
@@ -1449,6 +1779,16 @@ async function validateConfig(config: any): Promise<DocsConfig> {
|
|
|
1449
1779
|
config.home = fallbackHome;
|
|
1450
1780
|
}
|
|
1451
1781
|
|
|
1782
|
+
const hiddenPageRoutes = [
|
|
1783
|
+
...validateNavbar(config.navbar),
|
|
1784
|
+
...validateFooter(config.footer),
|
|
1785
|
+
];
|
|
1786
|
+
const dedupedHiddenPageRoutes = new Map<string, HiddenPageRoute>();
|
|
1787
|
+
for (const route of hiddenPageRoutes) {
|
|
1788
|
+
dedupedHiddenPageRoutes.set(route.href, route);
|
|
1789
|
+
}
|
|
1790
|
+
config.hiddenPageRoutes = Array.from(dedupedHiddenPageRoutes.values());
|
|
1791
|
+
|
|
1452
1792
|
// --- 4. Validate Playground ---
|
|
1453
1793
|
if (config.playground !== undefined) {
|
|
1454
1794
|
checkType(config.playground, "object", ["playground"], "Playground");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { Icon } from "astro-icon/components";
|
|
3
|
+
import { withBasePath } from "../lib/base-path";
|
|
3
4
|
import Layout from "../layouts/Layout.astro";
|
|
4
5
|
---
|
|
5
6
|
|
|
@@ -33,7 +34,7 @@ import Layout from "../layouts/Layout.astro";
|
|
|
33
34
|
Go back
|
|
34
35
|
</button>
|
|
35
36
|
<a
|
|
36
|
-
href="/"
|
|
37
|
+
href={withBasePath("/")}
|
|
37
38
|
class="inline-flex items-center justify-center gap-2 rounded-lg [corner-shape:superellipse(1.2)] border border-border px-4 py-2 text-sm font-[350] dark:font-[450] text-white bg-linear-to-b from-neutral-900/85 to-neutral-900 dark:from-neutral-100 dark:to-neutral-200 shadow-sm"
|
|
38
39
|
>
|
|
39
40
|
<Icon name="lucide:house" class="size-4" />
|
|
@@ -1,16 +1,48 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from "astro:content";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getAllRoutes,
|
|
5
|
+
resolveMdxPageTitle,
|
|
6
|
+
type Route,
|
|
7
|
+
} from "../lib/routes";
|
|
4
8
|
import { getConfig, validateMdxContent } from "../lib/validation";
|
|
5
9
|
import MdxPage from "../components/MdxPage.astro";
|
|
6
10
|
import OpenApiPage from "../components/OpenApiPage.astro";
|
|
7
11
|
import type { GetStaticPathsResult } from "astro";
|
|
8
12
|
|
|
9
13
|
export async function getStaticPaths(): Promise<GetStaticPathsResult> {
|
|
14
|
+
const buildAdjacentRouteMap = (routes: Route[]): Map<
|
|
15
|
+
string,
|
|
16
|
+
{
|
|
17
|
+
previousRoute?: Route;
|
|
18
|
+
nextRoute?: Route;
|
|
19
|
+
}
|
|
20
|
+
> => {
|
|
21
|
+
const adjacentBySlug = new Map<
|
|
22
|
+
string,
|
|
23
|
+
{
|
|
24
|
+
previousRoute?: Route;
|
|
25
|
+
nextRoute?: Route;
|
|
26
|
+
}
|
|
27
|
+
>();
|
|
28
|
+
|
|
29
|
+
for (let index = 0; index < routes.length; index++) {
|
|
30
|
+
const currentRoute = routes[index];
|
|
31
|
+
adjacentBySlug.set(currentRoute.slug, {
|
|
32
|
+
previousRoute: routes[index - 1],
|
|
33
|
+
nextRoute: routes[index + 1],
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return adjacentBySlug;
|
|
38
|
+
};
|
|
39
|
+
|
|
10
40
|
await validateMdxContent();
|
|
11
41
|
const routes = await getAllRoutes();
|
|
42
|
+
const visibleRoutes = routes.filter((route) => !route.hidden);
|
|
12
43
|
const docs = await getCollection("docs");
|
|
13
44
|
const config = await getConfig();
|
|
45
|
+
const adjacentRoutesBySlug = buildAdjacentRouteMap(visibleRoutes);
|
|
14
46
|
|
|
15
47
|
// console.log("routes", routes);
|
|
16
48
|
|
|
@@ -31,9 +63,16 @@ export async function getStaticPaths(): Promise<GetStaticPathsResult> {
|
|
|
31
63
|
`Could not find content collection entry for path: ${route.filePath}`,
|
|
32
64
|
);
|
|
33
65
|
}
|
|
66
|
+
const adjacentRoutes = adjacentRoutesBySlug.get(route.slug);
|
|
34
67
|
return {
|
|
35
68
|
params: { slug: route.slug },
|
|
36
|
-
props: {
|
|
69
|
+
props: {
|
|
70
|
+
route,
|
|
71
|
+
entry,
|
|
72
|
+
previousRoute: adjacentRoutes?.previousRoute,
|
|
73
|
+
nextRoute: adjacentRoutes?.nextRoute,
|
|
74
|
+
homePath: config.home,
|
|
75
|
+
},
|
|
37
76
|
};
|
|
38
77
|
} else {
|
|
39
78
|
return {
|
|
@@ -68,22 +107,45 @@ export async function getStaticPaths(): Promise<GetStaticPathsResult> {
|
|
|
68
107
|
filePath: config.home,
|
|
69
108
|
}),
|
|
70
109
|
};
|
|
110
|
+
|
|
111
|
+
const homeAdjacentRoutes = existingHomeRoute
|
|
112
|
+
? adjacentRoutesBySlug.get(existingHomeRoute.slug)
|
|
113
|
+
: undefined;
|
|
114
|
+
|
|
71
115
|
paths.push({
|
|
72
116
|
params: { slug: "/" },
|
|
73
|
-
props: {
|
|
117
|
+
props: {
|
|
118
|
+
route: homeRoute,
|
|
119
|
+
entry: homeEntry,
|
|
120
|
+
previousRoute: homeAdjacentRoutes?.previousRoute,
|
|
121
|
+
nextRoute: homeAdjacentRoutes?.nextRoute,
|
|
122
|
+
homePath: config.home,
|
|
123
|
+
},
|
|
74
124
|
});
|
|
75
125
|
}
|
|
76
126
|
|
|
77
127
|
return paths;
|
|
78
128
|
}
|
|
79
129
|
|
|
80
|
-
const props = Astro.props as {
|
|
81
|
-
|
|
130
|
+
const props = Astro.props as {
|
|
131
|
+
route: Route;
|
|
132
|
+
entry?: any;
|
|
133
|
+
previousRoute?: Route;
|
|
134
|
+
nextRoute?: Route;
|
|
135
|
+
homePath?: string;
|
|
136
|
+
};
|
|
137
|
+
const { route, entry, previousRoute, nextRoute, homePath } = props;
|
|
82
138
|
---
|
|
83
139
|
|
|
84
140
|
{
|
|
85
141
|
route.type === "mdx" ? (
|
|
86
|
-
<MdxPage
|
|
142
|
+
<MdxPage
|
|
143
|
+
entry={entry!}
|
|
144
|
+
route={route}
|
|
145
|
+
previousRoute={previousRoute}
|
|
146
|
+
nextRoute={nextRoute}
|
|
147
|
+
homePath={homePath}
|
|
148
|
+
/>
|
|
87
149
|
) : (
|
|
88
150
|
<OpenApiPage route={route} />
|
|
89
151
|
)
|