radiant-docs 0.1.39 → 0.1.41
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 +3 -3
- package/template/public/favicon.svg +16 -8
- package/template/scripts/generate-robots-txt.mjs +29 -1
- package/template/scripts/remove-assistant-for-non-pro.mjs +28 -0
- package/template/scripts/stamp-image-versions.mjs +59 -33
- package/template/src/components/Footer.astro +2 -1
- package/template/src/components/Header.astro +10 -8
- 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/chat/AssistantDocsWidget.astro +16 -0
- package/template/src/components/chat/AssistantDocsWidget.tsx +402 -0
- package/template/src/components/chat/AssistantEmbedPanel.tsx +1693 -0
- package/template/src/components/chat/AssistantEmbedPanelPage.astro +95 -0
- package/template/src/components/endpoint/PlaygroundForm.astro +2 -1
- package/template/src/components/user/Callout.astro +10 -4
- package/template/src/components/user/CodeBlock.astro +1 -1
- package/template/src/components/user/CodeGroup.astro +16 -1
- package/template/src/components/user/ComponentPreviewBlock.astro +1 -0
- package/template/src/components/user/Image.astro +43 -53
- package/template/src/layouts/Layout.astro +104 -35
- package/template/src/lib/assistant-chrome-defaults.ts +74 -0
- package/template/src/lib/assistant-chrome.ts +39 -0
- package/template/src/lib/assistant-embed-script.ts +897 -0
- package/template/src/lib/assistant-panel-config.ts +80 -0
- package/template/src/lib/base-path.ts +98 -0
- package/template/src/lib/component-error.ts +49 -10
- package/template/src/lib/favicon.ts +31 -0
- 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/theme-css.ts +176 -0
- package/template/src/lib/utils.ts +12 -4
- package/template/src/lib/validation.ts +754 -37
- package/template/src/pages/-/assistant/embed.js.ts +15 -0
- package/template/src/pages/-/assistant/panel.astro +5 -0
- package/template/src/pages/404.astro +6 -5
- package/template/src/pages/[...slug].astro +68 -6
- package/template/src/styles/global.css +62 -1
|
@@ -167,22 +167,74 @@ export type NavbarItem = {
|
|
|
167
167
|
href: string;
|
|
168
168
|
icon?: string | null;
|
|
169
169
|
};
|
|
170
|
-
export type
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
export type HiddenPageRoute = {
|
|
171
|
+
filePath: string;
|
|
172
|
+
href: string;
|
|
173
|
+
};
|
|
174
|
+
type InternalPageHrefResolution = HiddenPageRoute & {
|
|
175
|
+
linkHref: string;
|
|
176
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
|
+
};
|
|
218
|
+
export type AssistantIcon = {
|
|
219
|
+
src?: string;
|
|
220
|
+
color?: string;
|
|
221
|
+
};
|
|
222
|
+
export type AssistantButtonSize = "small" | "default";
|
|
223
|
+
export type AssistantButtonConfig = {
|
|
224
|
+
size?: AssistantButtonSize;
|
|
225
|
+
};
|
|
226
|
+
export type AssistantConfig = {
|
|
227
|
+
button?: AssistantButtonConfig;
|
|
228
|
+
heading?: string;
|
|
229
|
+
questions?: string[];
|
|
230
|
+
themeColor?: string | ThemeColorByMode;
|
|
231
|
+
icon?: AssistantIcon;
|
|
232
|
+
};
|
|
183
233
|
export type DocsConfig = {
|
|
184
234
|
title: string;
|
|
185
235
|
logo?: Logo;
|
|
236
|
+
theme?: DocsTheme;
|
|
237
|
+
assistant?: AssistantConfig;
|
|
186
238
|
home?: string;
|
|
187
239
|
navigation: NavigationItem;
|
|
188
240
|
navbar?: {
|
|
@@ -195,6 +247,7 @@ export type DocsConfig = {
|
|
|
195
247
|
proxy?: boolean;
|
|
196
248
|
};
|
|
197
249
|
footer?: Footer;
|
|
250
|
+
hiddenPageRoutes?: HiddenPageRoute[];
|
|
198
251
|
};
|
|
199
252
|
|
|
200
253
|
export type SocialPlatform =
|
|
@@ -255,6 +308,38 @@ function checkType(
|
|
|
255
308
|
}
|
|
256
309
|
}
|
|
257
310
|
|
|
311
|
+
function normalizeHexColor(
|
|
312
|
+
value: unknown,
|
|
313
|
+
currentPath: Path,
|
|
314
|
+
label: string,
|
|
315
|
+
): string {
|
|
316
|
+
checkType(value, "string", currentPath, label);
|
|
317
|
+
if (typeof value !== "string") {
|
|
318
|
+
throwConfigError(`${label} must be a string.`, currentPath);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const trimmedValue = value.trim();
|
|
322
|
+
if (trimmedValue.length === 0) {
|
|
323
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const normalizedValue = trimmedValue.startsWith("#")
|
|
327
|
+
? trimmedValue
|
|
328
|
+
: `#${trimmedValue}`;
|
|
329
|
+
if (
|
|
330
|
+
!/^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/.test(
|
|
331
|
+
normalizedValue,
|
|
332
|
+
)
|
|
333
|
+
) {
|
|
334
|
+
throwConfigError(
|
|
335
|
+
`${label} must be a valid hex color (for example: #1d4ed8).`,
|
|
336
|
+
currentPath,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return normalizedValue.toLowerCase();
|
|
341
|
+
}
|
|
342
|
+
|
|
258
343
|
function validateFileExistence(filePath: string, currentPath: Path): void {
|
|
259
344
|
// Assuming relative path from DOCS_DIR and .mdx extension
|
|
260
345
|
const fullPath = path.join(DOCS_DIR, `${filePath}.mdx`);
|
|
@@ -295,6 +380,58 @@ function normalizeDocsPagePath(
|
|
|
295
380
|
return normalizedPath;
|
|
296
381
|
}
|
|
297
382
|
|
|
383
|
+
function splitHrefPathAndSuffix(href: string): {
|
|
384
|
+
pathname: string;
|
|
385
|
+
suffix: string;
|
|
386
|
+
} {
|
|
387
|
+
const match = href.match(/^([^?#]*)(.*)$/);
|
|
388
|
+
return {
|
|
389
|
+
pathname: match?.[1] ?? href,
|
|
390
|
+
suffix: match?.[2] ?? "",
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function normalizeInternalPageHref(
|
|
395
|
+
href: string,
|
|
396
|
+
currentPath: Path,
|
|
397
|
+
label: string,
|
|
398
|
+
): InternalPageHrefResolution | null {
|
|
399
|
+
const trimmedHref = href.trim();
|
|
400
|
+
if (trimmedHref === "") {
|
|
401
|
+
throwConfigError(`${label} cannot be an empty string`, currentPath);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (isUrl(trimmedHref)) {
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (!trimmedHref.startsWith("/")) {
|
|
409
|
+
throwConfigError(
|
|
410
|
+
`${label} must be either a valid URL (http:// or https://) or an internal path (starting with /)`,
|
|
411
|
+
currentPath,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const { pathname, suffix } = splitHrefPathAndSuffix(trimmedHref);
|
|
416
|
+
const normalizedPathname = pathname.replace(/\/{2,}/g, "/");
|
|
417
|
+
if (normalizedPathname === "/" || normalizedPathname === "") {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const filePath = normalizeDocsPagePath(
|
|
422
|
+
normalizedPathname,
|
|
423
|
+
currentPath,
|
|
424
|
+
label,
|
|
425
|
+
);
|
|
426
|
+
validateFileExistence(filePath, currentPath);
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
filePath,
|
|
430
|
+
href: `/${filePath}`,
|
|
431
|
+
linkHref: `/${filePath}${suffix}`,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
298
435
|
// Cache for OpenAPI specs (key: filePathOrUrl, value: parsed spec)
|
|
299
436
|
const openApiSpecCache = new Map<string, any>();
|
|
300
437
|
|
|
@@ -662,7 +799,11 @@ async function validateNavigationNode(
|
|
|
662
799
|
continue;
|
|
663
800
|
}
|
|
664
801
|
|
|
665
|
-
await validateNavigationNode(
|
|
802
|
+
await validateNavigationNode(
|
|
803
|
+
child,
|
|
804
|
+
[...path, "pages", i],
|
|
805
|
+
groupDepth + 1,
|
|
806
|
+
);
|
|
666
807
|
}
|
|
667
808
|
return;
|
|
668
809
|
}
|
|
@@ -1049,9 +1190,12 @@ async function validateNavMenu(menu: any, currentPath: Path) {
|
|
|
1049
1190
|
}
|
|
1050
1191
|
}
|
|
1051
1192
|
|
|
1052
|
-
function validateNavbarItem(
|
|
1193
|
+
function validateNavbarItem(
|
|
1194
|
+
item: any,
|
|
1195
|
+
currentPath: Path,
|
|
1196
|
+
): HiddenPageRoute | null {
|
|
1053
1197
|
// Check if object exists, otherwise we skip (it's optional)
|
|
1054
|
-
if (item === undefined) return;
|
|
1198
|
+
if (item === undefined) return null;
|
|
1055
1199
|
|
|
1056
1200
|
checkType(item, "object", currentPath, "Navbar item");
|
|
1057
1201
|
|
|
@@ -1069,8 +1213,18 @@ function validateNavbarItem(item: any, currentPath: Path): void {
|
|
|
1069
1213
|
]);
|
|
1070
1214
|
}
|
|
1071
1215
|
|
|
1216
|
+
const hiddenPageRoute = normalizeInternalPageHref(
|
|
1217
|
+
item.href,
|
|
1218
|
+
[...currentPath, "href"],
|
|
1219
|
+
"Navbar item href",
|
|
1220
|
+
);
|
|
1221
|
+
if (hiddenPageRoute) {
|
|
1222
|
+
item.href = hiddenPageRoute.linkHref;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1072
1225
|
// Optional property
|
|
1073
1226
|
validateIcon(item.icon, [...currentPath, "icon"]);
|
|
1227
|
+
return hiddenPageRoute;
|
|
1074
1228
|
}
|
|
1075
1229
|
|
|
1076
1230
|
// --- Top-Level Validation Functions (Your Clean API) ---
|
|
@@ -1080,7 +1234,11 @@ function validateTitle(title: DocsConfig["title"]) {
|
|
|
1080
1234
|
if (!title) throwConfigError("Title is missing.", ["title"]);
|
|
1081
1235
|
}
|
|
1082
1236
|
|
|
1083
|
-
function validateLogoPaddingValue(
|
|
1237
|
+
function validateLogoPaddingValue(
|
|
1238
|
+
value: unknown,
|
|
1239
|
+
currentPath: Path,
|
|
1240
|
+
label: string,
|
|
1241
|
+
): void {
|
|
1084
1242
|
if (value === undefined) return;
|
|
1085
1243
|
|
|
1086
1244
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
@@ -1116,10 +1274,85 @@ function validateLogoImagePath(
|
|
|
1116
1274
|
|
|
1117
1275
|
if (!fs.existsSync(fullPath)) {
|
|
1118
1276
|
throwConfigError(
|
|
1119
|
-
`${label} file not found. Expected: ${normalizedPath}
|
|
1277
|
+
`${label} file not found. Expected: ${normalizedPath}`,
|
|
1278
|
+
currentPath,
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
function validateAssistantIconSource(
|
|
1284
|
+
iconSource: unknown,
|
|
1285
|
+
currentPath: Path,
|
|
1286
|
+
): string {
|
|
1287
|
+
checkType(iconSource, "string", currentPath, "Assistant icon source");
|
|
1288
|
+
if (typeof iconSource !== "string") {
|
|
1289
|
+
throwConfigError("Assistant icon source must be a string.", currentPath);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
const trimmedSource = iconSource.trim();
|
|
1293
|
+
if (trimmedSource.length === 0) {
|
|
1294
|
+
throwConfigError("Assistant icon source cannot be empty.", currentPath);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
if (isUrl(trimmedSource)) {
|
|
1298
|
+
throwConfigError(
|
|
1299
|
+
"Assistant icon source must be a local image path relative to docs.json.",
|
|
1300
|
+
currentPath,
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (trimmedSource.includes(":")) {
|
|
1305
|
+
throwConfigError(
|
|
1306
|
+
`Invalid assistant icon source: "${trimmedSource}". Assistant icons must be local image files, not Iconify icon names.`,
|
|
1307
|
+
currentPath,
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
if (
|
|
1312
|
+
trimmedSource.startsWith("//") ||
|
|
1313
|
+
trimmedSource.startsWith("#") ||
|
|
1314
|
+
trimmedSource.startsWith("?") ||
|
|
1315
|
+
trimmedSource.startsWith("./") ||
|
|
1316
|
+
trimmedSource.startsWith("../")
|
|
1317
|
+
) {
|
|
1318
|
+
throwConfigError(
|
|
1319
|
+
"Assistant icon source must be a local image path relative to docs.json.",
|
|
1320
|
+
currentPath,
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const parsed = new URL(trimmedSource, "https://docs.invalid/");
|
|
1325
|
+
const validExtensions = [
|
|
1326
|
+
".svg",
|
|
1327
|
+
".png",
|
|
1328
|
+
".jpg",
|
|
1329
|
+
".jpeg",
|
|
1330
|
+
".webp",
|
|
1331
|
+
".gif",
|
|
1332
|
+
".ico",
|
|
1333
|
+
".avif",
|
|
1334
|
+
];
|
|
1335
|
+
const hasValidExtension = validExtensions.some((ext) =>
|
|
1336
|
+
parsed.pathname.toLowerCase().endsWith(ext),
|
|
1337
|
+
);
|
|
1338
|
+
if (!hasValidExtension) {
|
|
1339
|
+
throwConfigError(
|
|
1340
|
+
"Assistant icon source must be a valid image file (.svg, .png, .jpg, .jpeg, .webp, .gif, .ico, .avif).",
|
|
1341
|
+
currentPath,
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
const normalizedPath = parsed.pathname.replace(/^\/+/, "");
|
|
1346
|
+
const fullPath = path.join(DOCS_DIR, normalizedPath);
|
|
1347
|
+
|
|
1348
|
+
if (!fs.existsSync(fullPath)) {
|
|
1349
|
+
throwConfigError(
|
|
1350
|
+
`Assistant icon source file not found. Expected: ${normalizedPath}`,
|
|
1120
1351
|
currentPath,
|
|
1121
1352
|
);
|
|
1122
1353
|
}
|
|
1354
|
+
|
|
1355
|
+
return trimmedSource;
|
|
1123
1356
|
}
|
|
1124
1357
|
|
|
1125
1358
|
function validateLogoVariant(
|
|
@@ -1134,7 +1367,11 @@ function validateLogoVariant(
|
|
|
1134
1367
|
return;
|
|
1135
1368
|
}
|
|
1136
1369
|
|
|
1137
|
-
if (
|
|
1370
|
+
if (
|
|
1371
|
+
typeof variant !== "object" ||
|
|
1372
|
+
variant === null ||
|
|
1373
|
+
Array.isArray(variant)
|
|
1374
|
+
) {
|
|
1138
1375
|
throwConfigError(
|
|
1139
1376
|
`Logo ${mode} must be a string path or an object with 'image' and optional 'padding'.`,
|
|
1140
1377
|
currentPath,
|
|
@@ -1152,13 +1389,17 @@ function validateLogoVariant(
|
|
|
1152
1389
|
}
|
|
1153
1390
|
|
|
1154
1391
|
if (typeof variant.image !== "string") {
|
|
1155
|
-
throwConfigError(
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
);
|
|
1392
|
+
throwConfigError(`Logo ${mode} object must include an 'image' string.`, [
|
|
1393
|
+
...currentPath,
|
|
1394
|
+
"image",
|
|
1395
|
+
]);
|
|
1159
1396
|
}
|
|
1160
1397
|
|
|
1161
|
-
validateLogoImagePath(
|
|
1398
|
+
validateLogoImagePath(
|
|
1399
|
+
variant.image,
|
|
1400
|
+
[...currentPath, "image"],
|
|
1401
|
+
`Logo ${mode} image`,
|
|
1402
|
+
);
|
|
1162
1403
|
|
|
1163
1404
|
if (variant.padding === undefined) return;
|
|
1164
1405
|
|
|
@@ -1242,10 +1483,463 @@ function validateLogo(logo: DocsConfig["logo"]) {
|
|
|
1242
1483
|
);
|
|
1243
1484
|
}
|
|
1244
1485
|
} else if (logo.pill !== false) {
|
|
1245
|
-
throwConfigError("Logo pill must be a string or false.", [
|
|
1486
|
+
throwConfigError("Logo pill must be a string or false.", [
|
|
1487
|
+
"logo",
|
|
1488
|
+
"pill",
|
|
1489
|
+
]);
|
|
1246
1490
|
}
|
|
1247
1491
|
}
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
function validateTheme(theme: DocsConfig["theme"]): void {
|
|
1495
|
+
if (theme === undefined) return;
|
|
1496
|
+
|
|
1497
|
+
checkType(theme, "object", ["theme"], "Theme configuration");
|
|
1498
|
+
if (typeof theme !== "object" || theme === null || Array.isArray(theme)) {
|
|
1499
|
+
throwConfigError("Theme configuration must be an object.", ["theme"]);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
const normalizeBaseColor = (
|
|
1503
|
+
value: unknown,
|
|
1504
|
+
currentPath: Path,
|
|
1505
|
+
label: string,
|
|
1506
|
+
): BaseColorOption => {
|
|
1507
|
+
checkType(value, "string", currentPath, label);
|
|
1508
|
+
if (typeof value !== "string") {
|
|
1509
|
+
throwConfigError(`${label} must be a string.`, currentPath);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
const normalizedBaseColor = (value as string).trim().toLowerCase();
|
|
1513
|
+
if (normalizedBaseColor.length === 0) {
|
|
1514
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
if (!BASE_COLOR_OPTIONS.includes(normalizedBaseColor as BaseColorOption)) {
|
|
1518
|
+
throwConfigError(
|
|
1519
|
+
`${label} must be one of: ${BASE_COLOR_OPTIONS.join(", ")}.`,
|
|
1520
|
+
currentPath,
|
|
1521
|
+
);
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
return normalizedBaseColor as BaseColorOption;
|
|
1525
|
+
};
|
|
1526
|
+
|
|
1527
|
+
if (theme.baseColor !== undefined) {
|
|
1528
|
+
if (typeof theme.baseColor === "string") {
|
|
1529
|
+
theme.baseColor = normalizeBaseColor(
|
|
1530
|
+
theme.baseColor,
|
|
1531
|
+
["theme", "baseColor"],
|
|
1532
|
+
"Theme base color",
|
|
1533
|
+
);
|
|
1534
|
+
} else {
|
|
1535
|
+
checkType(
|
|
1536
|
+
theme.baseColor,
|
|
1537
|
+
"object",
|
|
1538
|
+
["theme", "baseColor"],
|
|
1539
|
+
"Theme base color",
|
|
1540
|
+
);
|
|
1541
|
+
if (
|
|
1542
|
+
typeof theme.baseColor !== "object" ||
|
|
1543
|
+
theme.baseColor === null ||
|
|
1544
|
+
Array.isArray(theme.baseColor)
|
|
1545
|
+
) {
|
|
1546
|
+
throwConfigError(
|
|
1547
|
+
"Theme base color must be a string or an object with light/dark values.",
|
|
1548
|
+
["theme", "baseColor"],
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
const baseColorByMode = theme.baseColor as Record<string, unknown>;
|
|
1553
|
+
const allowedKeys = new Set(["light", "dark"]);
|
|
1554
|
+
for (const key of Object.keys(baseColorByMode)) {
|
|
1555
|
+
if (!allowedKeys.has(key)) {
|
|
1556
|
+
throwConfigError(
|
|
1557
|
+
"Theme base color object only supports 'light' and 'dark'.",
|
|
1558
|
+
["theme", "baseColor", key],
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1248
1562
|
|
|
1563
|
+
const light =
|
|
1564
|
+
baseColorByMode.light !== undefined
|
|
1565
|
+
? normalizeBaseColor(
|
|
1566
|
+
baseColorByMode.light,
|
|
1567
|
+
["theme", "baseColor", "light"],
|
|
1568
|
+
"Theme base color light",
|
|
1569
|
+
)
|
|
1570
|
+
: undefined;
|
|
1571
|
+
const dark =
|
|
1572
|
+
baseColorByMode.dark !== undefined
|
|
1573
|
+
? normalizeBaseColor(
|
|
1574
|
+
baseColorByMode.dark,
|
|
1575
|
+
["theme", "baseColor", "dark"],
|
|
1576
|
+
"Theme base color dark",
|
|
1577
|
+
)
|
|
1578
|
+
: undefined;
|
|
1579
|
+
|
|
1580
|
+
if (!light && !dark) {
|
|
1581
|
+
throwConfigError(
|
|
1582
|
+
"Theme base color object must include 'light', 'dark', or both.",
|
|
1583
|
+
["theme", "baseColor"],
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
const resolvedLight: BaseColorOption = light ?? "neutral";
|
|
1588
|
+
const resolvedDark: BaseColorOption = dark ?? "neutral";
|
|
1589
|
+
|
|
1590
|
+
theme.baseColor = {
|
|
1591
|
+
light: resolvedLight,
|
|
1592
|
+
dark: resolvedDark,
|
|
1593
|
+
};
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
if (theme.themeColor === undefined) {
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
if (typeof theme.themeColor === "string") {
|
|
1602
|
+
theme.themeColor = normalizeHexColor(
|
|
1603
|
+
theme.themeColor,
|
|
1604
|
+
["theme", "themeColor"],
|
|
1605
|
+
"Theme color",
|
|
1606
|
+
);
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
checkType(theme.themeColor, "object", ["theme", "themeColor"], "Theme color");
|
|
1611
|
+
if (
|
|
1612
|
+
typeof theme.themeColor !== "object" ||
|
|
1613
|
+
theme.themeColor === null ||
|
|
1614
|
+
Array.isArray(theme.themeColor)
|
|
1615
|
+
) {
|
|
1616
|
+
throwConfigError(
|
|
1617
|
+
"Theme color must be a string or an object with light/dark values.",
|
|
1618
|
+
["theme", "themeColor"],
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const themeColorByMode = theme.themeColor as Record<string, unknown>;
|
|
1623
|
+
const allowedKeys = new Set(["light", "dark"]);
|
|
1624
|
+
for (const key of Object.keys(themeColorByMode)) {
|
|
1625
|
+
if (!allowedKeys.has(key)) {
|
|
1626
|
+
throwConfigError("Theme color object only supports 'light' and 'dark'.", [
|
|
1627
|
+
"theme",
|
|
1628
|
+
"themeColor",
|
|
1629
|
+
key,
|
|
1630
|
+
]);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
const light =
|
|
1635
|
+
themeColorByMode.light !== undefined
|
|
1636
|
+
? normalizeHexColor(
|
|
1637
|
+
themeColorByMode.light,
|
|
1638
|
+
["theme", "themeColor", "light"],
|
|
1639
|
+
"Theme color light",
|
|
1640
|
+
)
|
|
1641
|
+
: undefined;
|
|
1642
|
+
const dark =
|
|
1643
|
+
themeColorByMode.dark !== undefined
|
|
1644
|
+
? normalizeHexColor(
|
|
1645
|
+
themeColorByMode.dark,
|
|
1646
|
+
["theme", "themeColor", "dark"],
|
|
1647
|
+
"Theme color dark",
|
|
1648
|
+
)
|
|
1649
|
+
: undefined;
|
|
1650
|
+
|
|
1651
|
+
if (!light && !dark) {
|
|
1652
|
+
throwConfigError(
|
|
1653
|
+
"Theme color object must include 'light', 'dark', or both.",
|
|
1654
|
+
["theme", "themeColor"],
|
|
1655
|
+
);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
theme.themeColor = {
|
|
1659
|
+
...(light !== undefined ? { light } : {}),
|
|
1660
|
+
...(dark !== undefined ? { dark } : {}),
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function validateAssistant(assistant: DocsConfig["assistant"]): void {
|
|
1665
|
+
if (assistant === undefined) return;
|
|
1666
|
+
|
|
1667
|
+
checkType(assistant, "object", ["assistant"], "Assistant configuration");
|
|
1668
|
+
if (
|
|
1669
|
+
typeof assistant !== "object" ||
|
|
1670
|
+
assistant === null ||
|
|
1671
|
+
Array.isArray(assistant)
|
|
1672
|
+
) {
|
|
1673
|
+
throwConfigError("Assistant configuration must be an object.", [
|
|
1674
|
+
"assistant",
|
|
1675
|
+
]);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
const allowedAssistantKeys = new Set([
|
|
1679
|
+
"button",
|
|
1680
|
+
"heading",
|
|
1681
|
+
"questions",
|
|
1682
|
+
"themeColor",
|
|
1683
|
+
"icon",
|
|
1684
|
+
]);
|
|
1685
|
+
for (const key of Object.keys(assistant)) {
|
|
1686
|
+
if (!allowedAssistantKeys.has(key)) {
|
|
1687
|
+
throwConfigError(
|
|
1688
|
+
"Assistant configuration only supports 'button', 'heading', 'questions', 'themeColor', and 'icon'.",
|
|
1689
|
+
["assistant", key],
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
if (assistant.button !== undefined) {
|
|
1695
|
+
checkType(
|
|
1696
|
+
assistant.button,
|
|
1697
|
+
"object",
|
|
1698
|
+
["assistant", "button"],
|
|
1699
|
+
"Assistant button configuration",
|
|
1700
|
+
);
|
|
1701
|
+
if (
|
|
1702
|
+
typeof assistant.button !== "object" ||
|
|
1703
|
+
assistant.button === null ||
|
|
1704
|
+
Array.isArray(assistant.button)
|
|
1705
|
+
) {
|
|
1706
|
+
throwConfigError("Assistant button configuration must be an object.", [
|
|
1707
|
+
"assistant",
|
|
1708
|
+
"button",
|
|
1709
|
+
]);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
const allowedButtonKeys = new Set(["size"]);
|
|
1713
|
+
for (const key of Object.keys(assistant.button)) {
|
|
1714
|
+
if (!allowedButtonKeys.has(key)) {
|
|
1715
|
+
throwConfigError(
|
|
1716
|
+
"Assistant button configuration only supports 'size'.",
|
|
1717
|
+
["assistant", "button", key],
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
if (assistant.button.size !== undefined) {
|
|
1723
|
+
checkType(
|
|
1724
|
+
assistant.button.size,
|
|
1725
|
+
"string",
|
|
1726
|
+
["assistant", "button", "size"],
|
|
1727
|
+
"Assistant button size",
|
|
1728
|
+
);
|
|
1729
|
+
if (typeof assistant.button.size !== "string") {
|
|
1730
|
+
throwConfigError("Assistant button size must be a string.", [
|
|
1731
|
+
"assistant",
|
|
1732
|
+
"button",
|
|
1733
|
+
"size",
|
|
1734
|
+
]);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
const trimmedSize = assistant.button.size.trim();
|
|
1738
|
+
if (trimmedSize !== "small" && trimmedSize !== "default") {
|
|
1739
|
+
throwConfigError(
|
|
1740
|
+
"Assistant button size must be either 'small' or 'default'.",
|
|
1741
|
+
["assistant", "button", "size"],
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
assistant.button.size = trimmedSize;
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
if (assistant.heading !== undefined) {
|
|
1749
|
+
checkType(
|
|
1750
|
+
assistant.heading,
|
|
1751
|
+
"string",
|
|
1752
|
+
["assistant", "heading"],
|
|
1753
|
+
"Assistant heading",
|
|
1754
|
+
);
|
|
1755
|
+
if (typeof assistant.heading !== "string") {
|
|
1756
|
+
throwConfigError("Assistant heading must be a string.", [
|
|
1757
|
+
"assistant",
|
|
1758
|
+
"heading",
|
|
1759
|
+
]);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
const trimmedHeading = assistant.heading.trim();
|
|
1763
|
+
if (trimmedHeading.length === 0) {
|
|
1764
|
+
throwConfigError("Assistant heading cannot be empty.", [
|
|
1765
|
+
"assistant",
|
|
1766
|
+
"heading",
|
|
1767
|
+
]);
|
|
1768
|
+
}
|
|
1769
|
+
assistant.heading = trimmedHeading;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
if (assistant.questions !== undefined) {
|
|
1773
|
+
checkType(
|
|
1774
|
+
assistant.questions,
|
|
1775
|
+
"array",
|
|
1776
|
+
["assistant", "questions"],
|
|
1777
|
+
"Assistant questions",
|
|
1778
|
+
);
|
|
1779
|
+
if (!Array.isArray(assistant.questions)) {
|
|
1780
|
+
throwConfigError("Assistant questions must be an array.", [
|
|
1781
|
+
"assistant",
|
|
1782
|
+
"questions",
|
|
1783
|
+
]);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
if (assistant.questions.length > 3) {
|
|
1787
|
+
throwConfigError("Assistant questions can include at most 3 questions.", [
|
|
1788
|
+
"assistant",
|
|
1789
|
+
"questions",
|
|
1790
|
+
]);
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
assistant.questions = assistant.questions.map((question, index) => {
|
|
1794
|
+
checkType(
|
|
1795
|
+
question,
|
|
1796
|
+
"string",
|
|
1797
|
+
["assistant", "questions", String(index)],
|
|
1798
|
+
"Assistant question",
|
|
1799
|
+
);
|
|
1800
|
+
if (typeof question !== "string") {
|
|
1801
|
+
throwConfigError("Assistant question must be a string.", [
|
|
1802
|
+
"assistant",
|
|
1803
|
+
"questions",
|
|
1804
|
+
String(index),
|
|
1805
|
+
]);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
const trimmedQuestion = question.trim();
|
|
1809
|
+
if (trimmedQuestion.length === 0) {
|
|
1810
|
+
throwConfigError(
|
|
1811
|
+
"Assistant question cannot be empty.",
|
|
1812
|
+
["assistant", "questions", String(index)],
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
return trimmedQuestion;
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
if (assistant.themeColor !== undefined) {
|
|
1821
|
+
if (typeof assistant.themeColor === "string") {
|
|
1822
|
+
assistant.themeColor = normalizeHexColor(
|
|
1823
|
+
assistant.themeColor,
|
|
1824
|
+
["assistant", "themeColor"],
|
|
1825
|
+
"Assistant theme color",
|
|
1826
|
+
);
|
|
1827
|
+
} else {
|
|
1828
|
+
checkType(
|
|
1829
|
+
assistant.themeColor,
|
|
1830
|
+
"object",
|
|
1831
|
+
["assistant", "themeColor"],
|
|
1832
|
+
"Assistant theme color",
|
|
1833
|
+
);
|
|
1834
|
+
if (
|
|
1835
|
+
typeof assistant.themeColor !== "object" ||
|
|
1836
|
+
assistant.themeColor === null ||
|
|
1837
|
+
Array.isArray(assistant.themeColor)
|
|
1838
|
+
) {
|
|
1839
|
+
throwConfigError(
|
|
1840
|
+
"Assistant theme color must be a string or an object with light/dark values.",
|
|
1841
|
+
["assistant", "themeColor"],
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
const assistantThemeColorByMode = assistant.themeColor as Record<
|
|
1846
|
+
string,
|
|
1847
|
+
unknown
|
|
1848
|
+
>;
|
|
1849
|
+
const allowedThemeColorKeys = new Set(["light", "dark"]);
|
|
1850
|
+
for (const key of Object.keys(assistantThemeColorByMode)) {
|
|
1851
|
+
if (!allowedThemeColorKeys.has(key)) {
|
|
1852
|
+
throwConfigError(
|
|
1853
|
+
"Assistant theme color object only supports 'light' and 'dark'.",
|
|
1854
|
+
["assistant", "themeColor", key],
|
|
1855
|
+
);
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const light =
|
|
1860
|
+
assistantThemeColorByMode.light !== undefined
|
|
1861
|
+
? normalizeHexColor(
|
|
1862
|
+
assistantThemeColorByMode.light,
|
|
1863
|
+
["assistant", "themeColor", "light"],
|
|
1864
|
+
"Assistant theme color light",
|
|
1865
|
+
)
|
|
1866
|
+
: undefined;
|
|
1867
|
+
const dark =
|
|
1868
|
+
assistantThemeColorByMode.dark !== undefined
|
|
1869
|
+
? normalizeHexColor(
|
|
1870
|
+
assistantThemeColorByMode.dark,
|
|
1871
|
+
["assistant", "themeColor", "dark"],
|
|
1872
|
+
"Assistant theme color dark",
|
|
1873
|
+
)
|
|
1874
|
+
: undefined;
|
|
1875
|
+
|
|
1876
|
+
if (!light && !dark) {
|
|
1877
|
+
throwConfigError(
|
|
1878
|
+
"Assistant theme color object must include 'light', 'dark', or both.",
|
|
1879
|
+
["assistant", "themeColor"],
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
assistant.themeColor = {
|
|
1884
|
+
...(light !== undefined ? { light } : {}),
|
|
1885
|
+
...(dark !== undefined ? { dark } : {}),
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
if (assistant.icon === undefined) return;
|
|
1891
|
+
|
|
1892
|
+
checkType(
|
|
1893
|
+
assistant.icon,
|
|
1894
|
+
"object",
|
|
1895
|
+
["assistant", "icon"],
|
|
1896
|
+
"Assistant icon",
|
|
1897
|
+
);
|
|
1898
|
+
if (
|
|
1899
|
+
typeof assistant.icon !== "object" ||
|
|
1900
|
+
assistant.icon === null ||
|
|
1901
|
+
Array.isArray(assistant.icon)
|
|
1902
|
+
) {
|
|
1903
|
+
throwConfigError("Assistant icon must be an object.", [
|
|
1904
|
+
"assistant",
|
|
1905
|
+
"icon",
|
|
1906
|
+
]);
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
const allowedIconKeys = new Set(["src", "color"]);
|
|
1910
|
+
for (const key of Object.keys(assistant.icon)) {
|
|
1911
|
+
if (!allowedIconKeys.has(key)) {
|
|
1912
|
+
throwConfigError(
|
|
1913
|
+
"Assistant icon only supports 'src' and 'color'.",
|
|
1914
|
+
["assistant", "icon", key],
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
if (
|
|
1920
|
+
assistant.icon.src === undefined &&
|
|
1921
|
+
assistant.icon.color === undefined
|
|
1922
|
+
) {
|
|
1923
|
+
throwConfigError(
|
|
1924
|
+
"Assistant icon must include 'src', 'color', or both.",
|
|
1925
|
+
["assistant", "icon"],
|
|
1926
|
+
);
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
if (assistant.icon.src !== undefined) {
|
|
1930
|
+
assistant.icon.src = validateAssistantIconSource(
|
|
1931
|
+
assistant.icon.src,
|
|
1932
|
+
["assistant", "icon", "src"],
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
if (assistant.icon.color !== undefined) {
|
|
1937
|
+
assistant.icon.color = normalizeHexColor(
|
|
1938
|
+
assistant.icon.color,
|
|
1939
|
+
["assistant", "icon", "color"],
|
|
1940
|
+
"Assistant icon color",
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1249
1943
|
}
|
|
1250
1944
|
|
|
1251
1945
|
function validateHome(home: DocsConfig["home"]): string | undefined {
|
|
@@ -1256,8 +1950,9 @@ function validateHome(home: DocsConfig["home"]): string | undefined {
|
|
|
1256
1950
|
return normalizedHome;
|
|
1257
1951
|
}
|
|
1258
1952
|
|
|
1259
|
-
function validateNavbar(navbar: DocsConfig["navbar"]) {
|
|
1260
|
-
|
|
1953
|
+
function validateNavbar(navbar: DocsConfig["navbar"]): HiddenPageRoute[] {
|
|
1954
|
+
const hiddenPageRoutes: HiddenPageRoute[] = [];
|
|
1955
|
+
if (navbar === undefined) return hiddenPageRoutes; // Navbar itself is optional
|
|
1261
1956
|
|
|
1262
1957
|
checkType(navbar, "object", ["navbar"], "Navbar configuration");
|
|
1263
1958
|
|
|
@@ -1265,10 +1960,18 @@ function validateNavbar(navbar: DocsConfig["navbar"]) {
|
|
|
1265
1960
|
checkType(navbar.blur, "boolean", ["navbar", "blur"], "Navbar blur setting");
|
|
1266
1961
|
|
|
1267
1962
|
// Validate 'primary' item
|
|
1268
|
-
validateNavbarItem(navbar.primary, [
|
|
1963
|
+
const primaryPageRoute = validateNavbarItem(navbar.primary, [
|
|
1964
|
+
"navbar",
|
|
1965
|
+
"primary",
|
|
1966
|
+
]);
|
|
1967
|
+
if (primaryPageRoute) hiddenPageRoutes.push(primaryPageRoute);
|
|
1269
1968
|
|
|
1270
1969
|
// Validate 'secondary' item
|
|
1271
|
-
validateNavbarItem(navbar.secondary, [
|
|
1970
|
+
const secondaryPageRoute = validateNavbarItem(navbar.secondary, [
|
|
1971
|
+
"navbar",
|
|
1972
|
+
"secondary",
|
|
1973
|
+
]);
|
|
1974
|
+
if (secondaryPageRoute) hiddenPageRoutes.push(secondaryPageRoute);
|
|
1272
1975
|
|
|
1273
1976
|
// Validate 'links' array
|
|
1274
1977
|
if (navbar.links !== undefined) {
|
|
@@ -1282,13 +1985,17 @@ function validateNavbar(navbar: DocsConfig["navbar"]) {
|
|
|
1282
1985
|
}
|
|
1283
1986
|
|
|
1284
1987
|
navbar.links.forEach((link: any, i: number) => {
|
|
1285
|
-
validateNavbarItem(link, ["navbar", "links", i]);
|
|
1988
|
+
const hiddenPageRoute = validateNavbarItem(link, ["navbar", "links", i]);
|
|
1989
|
+
if (hiddenPageRoute) hiddenPageRoutes.push(hiddenPageRoute);
|
|
1286
1990
|
});
|
|
1287
1991
|
}
|
|
1992
|
+
|
|
1993
|
+
return hiddenPageRoutes;
|
|
1288
1994
|
}
|
|
1289
1995
|
|
|
1290
|
-
function validateFooter(footer: DocsConfig["footer"]) {
|
|
1291
|
-
|
|
1996
|
+
function validateFooter(footer: DocsConfig["footer"]): HiddenPageRoute[] {
|
|
1997
|
+
const hiddenPageRoutes: HiddenPageRoute[] = [];
|
|
1998
|
+
if (footer === undefined) return hiddenPageRoutes;
|
|
1292
1999
|
|
|
1293
2000
|
checkType(footer, "object", ["footer"], "Footer configuration");
|
|
1294
2001
|
|
|
@@ -1367,19 +2074,19 @@ function validateFooter(footer: DocsConfig["footer"]) {
|
|
|
1367
2074
|
]);
|
|
1368
2075
|
}
|
|
1369
2076
|
|
|
1370
|
-
const
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
if (
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
["footer", "links", i, "href"],
|
|
1379
|
-
);
|
|
2077
|
+
const hiddenPageRoute = normalizeInternalPageHref(
|
|
2078
|
+
link.href,
|
|
2079
|
+
["footer", "links", i, "href"],
|
|
2080
|
+
"Footer link href",
|
|
2081
|
+
);
|
|
2082
|
+
if (hiddenPageRoute) {
|
|
2083
|
+
link.href = hiddenPageRoute.linkHref;
|
|
2084
|
+
hiddenPageRoutes.push(hiddenPageRoute);
|
|
1380
2085
|
}
|
|
1381
2086
|
});
|
|
1382
2087
|
}
|
|
2088
|
+
|
|
2089
|
+
return hiddenPageRoutes;
|
|
1383
2090
|
}
|
|
1384
2091
|
|
|
1385
2092
|
async function validateNavigation(navigation: DocsConfig["navigation"]) {
|
|
@@ -1433,8 +2140,8 @@ async function validateConfig(config: any): Promise<DocsConfig> {
|
|
|
1433
2140
|
// Execute top-level checks sequentially
|
|
1434
2141
|
validateTitle(config.title);
|
|
1435
2142
|
validateLogo(config.logo);
|
|
1436
|
-
|
|
1437
|
-
|
|
2143
|
+
validateTheme(config.theme);
|
|
2144
|
+
validateAssistant(config.assistant);
|
|
1438
2145
|
await validateNavigation(config.navigation);
|
|
1439
2146
|
config.home = validateHome(config.home);
|
|
1440
2147
|
|
|
@@ -1449,6 +2156,16 @@ async function validateConfig(config: any): Promise<DocsConfig> {
|
|
|
1449
2156
|
config.home = fallbackHome;
|
|
1450
2157
|
}
|
|
1451
2158
|
|
|
2159
|
+
const hiddenPageRoutes = [
|
|
2160
|
+
...validateNavbar(config.navbar),
|
|
2161
|
+
...validateFooter(config.footer),
|
|
2162
|
+
];
|
|
2163
|
+
const dedupedHiddenPageRoutes = new Map<string, HiddenPageRoute>();
|
|
2164
|
+
for (const route of hiddenPageRoutes) {
|
|
2165
|
+
dedupedHiddenPageRoutes.set(route.href, route);
|
|
2166
|
+
}
|
|
2167
|
+
config.hiddenPageRoutes = Array.from(dedupedHiddenPageRoutes.values());
|
|
2168
|
+
|
|
1452
2169
|
// --- 4. Validate Playground ---
|
|
1453
2170
|
if (config.playground !== undefined) {
|
|
1454
2171
|
checkType(config.playground, "object", ["playground"], "Playground");
|