radiant-docs 0.1.40 → 0.1.42
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 +42 -40
- package/template/package-lock.json +7 -0
- package/template/package.json +3 -2
- package/template/public/favicon.svg +16 -8
- package/template/scripts/remove-assistant-for-non-pro.mjs +28 -0
- package/template/src/components/Header.astro +151 -17
- package/template/src/components/MdxPage.astro +76 -22
- package/template/src/components/PagePagination.astro +44 -8
- package/template/src/components/Sidebar.astro +10 -1
- package/template/src/components/TableOfContents.astro +159 -53
- package/template/src/components/chat/AssistantDocsWidget.astro +16 -0
- package/template/src/components/chat/AssistantDocsWidget.tsx +615 -0
- package/template/src/components/chat/AssistantEmbedPanel.tsx +2679 -0
- package/template/src/components/chat/AssistantEmbedPanelPage.astro +95 -0
- package/template/src/components/user/Accordion.astro +2 -2
- package/template/src/components/user/AccordionGroup.astro +1 -1
- package/template/src/components/user/Callout.astro +10 -4
- package/template/src/components/user/Card.astro +488 -0
- package/template/src/components/user/CardGradient.astro +964 -0
- package/template/src/components/user/CodeBlock.astro +1 -1
- package/template/src/components/user/CodeGroup.astro +1 -1
- package/template/src/components/user/Column.astro +25 -0
- package/template/src/components/user/Columns.astro +200 -0
- package/template/src/components/user/ComponentPreviewBlock.astro +1 -1
- package/template/src/components/user/Image.astro +1 -1
- package/template/src/components/user/Step.astro +1 -1
- package/template/src/components/user/Steps.astro +1 -1
- package/template/src/components/user/Tab.astro +1 -3
- package/template/src/components/user/Tabs.astro +2 -2
- package/template/src/layouts/Layout.astro +13 -156
- package/template/src/lib/assistant-chrome-defaults.ts +86 -0
- package/template/src/lib/assistant-chrome.ts +39 -0
- package/template/src/lib/assistant-embed-script.ts +1088 -0
- package/template/src/lib/assistant-panel-config.ts +80 -0
- package/template/src/lib/favicon.ts +31 -0
- package/template/src/lib/theme-css.ts +176 -0
- package/template/src/lib/validation.ts +668 -41
- 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 +4 -4
- package/template/src/styles/global.css +81 -4
- package/template/src/components/chat/AskAiWidget.tsx +0 -2011
|
@@ -110,6 +110,9 @@ const AVAILABLE_COMPONENTS = [
|
|
|
110
110
|
"Step",
|
|
111
111
|
"Accordion",
|
|
112
112
|
"AccordionGroup",
|
|
113
|
+
"Card",
|
|
114
|
+
"Column",
|
|
115
|
+
"Columns",
|
|
113
116
|
"Image",
|
|
114
117
|
"CodeGroup",
|
|
115
118
|
"ComponentPreview",
|
|
@@ -166,6 +169,7 @@ export type NavbarItem = {
|
|
|
166
169
|
text: string;
|
|
167
170
|
href: string;
|
|
168
171
|
icon?: string | null;
|
|
172
|
+
color?: string | ThemeColorByMode;
|
|
169
173
|
};
|
|
170
174
|
export type HiddenPageRoute = {
|
|
171
175
|
filePath: string;
|
|
@@ -208,17 +212,51 @@ export type BaseColorByMode = {
|
|
|
208
212
|
export const DEFAULT_THEME_COLOR_LIGHT = "#171717";
|
|
209
213
|
export const DEFAULT_THEME_COLOR_DARK = "#f5f5f5";
|
|
210
214
|
export type ThemeColorByMode = {
|
|
211
|
-
light
|
|
212
|
-
dark
|
|
215
|
+
light?: string;
|
|
216
|
+
dark?: string;
|
|
217
|
+
};
|
|
218
|
+
export type CardCoverTheme = {
|
|
219
|
+
colors?: string[];
|
|
220
|
+
colorSeed?: string;
|
|
221
|
+
};
|
|
222
|
+
export type CardButtonTheme = {
|
|
223
|
+
color?: string | ThemeColorByMode;
|
|
224
|
+
};
|
|
225
|
+
export type CardTheme = {
|
|
226
|
+
cover?: CardCoverTheme;
|
|
227
|
+
button?: CardButtonTheme;
|
|
213
228
|
};
|
|
214
229
|
export type DocsTheme = {
|
|
215
230
|
baseColor?: BaseColorOption | BaseColorByMode;
|
|
216
231
|
themeColor?: string | ThemeColorByMode;
|
|
232
|
+
card?: CardTheme;
|
|
233
|
+
};
|
|
234
|
+
export type AssistantIcon = {
|
|
235
|
+
src?: string;
|
|
236
|
+
color?: string;
|
|
237
|
+
};
|
|
238
|
+
export type AssistantButtonSize = "small" | "default";
|
|
239
|
+
export type AssistantButtonConfig = {
|
|
240
|
+
size?: AssistantButtonSize;
|
|
241
|
+
color?: string | ThemeColorByMode;
|
|
242
|
+
};
|
|
243
|
+
export type AssistantNavbarButtonConfig = {
|
|
244
|
+
enabled?: boolean;
|
|
245
|
+
text?: string;
|
|
246
|
+
color?: string | ThemeColorByMode;
|
|
247
|
+
};
|
|
248
|
+
export type AssistantConfig = {
|
|
249
|
+
button?: AssistantButtonConfig;
|
|
250
|
+
navbarButton?: AssistantNavbarButtonConfig;
|
|
251
|
+
heading?: string;
|
|
252
|
+
questions?: string[];
|
|
253
|
+
icon?: AssistantIcon;
|
|
217
254
|
};
|
|
218
255
|
export type DocsConfig = {
|
|
219
256
|
title: string;
|
|
220
257
|
logo?: Logo;
|
|
221
258
|
theme?: DocsTheme;
|
|
259
|
+
assistant?: AssistantConfig;
|
|
222
260
|
home?: string;
|
|
223
261
|
navigation: NavigationItem;
|
|
224
262
|
navbar?: {
|
|
@@ -292,6 +330,125 @@ function checkType(
|
|
|
292
330
|
}
|
|
293
331
|
}
|
|
294
332
|
|
|
333
|
+
function normalizeHexColor(
|
|
334
|
+
value: unknown,
|
|
335
|
+
currentPath: Path,
|
|
336
|
+
label: string,
|
|
337
|
+
): string {
|
|
338
|
+
checkType(value, "string", currentPath, label);
|
|
339
|
+
if (typeof value !== "string") {
|
|
340
|
+
throwConfigError(`${label} must be a string.`, currentPath);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const trimmedValue = value.trim();
|
|
344
|
+
if (trimmedValue.length === 0) {
|
|
345
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const normalizedValue = trimmedValue.startsWith("#")
|
|
349
|
+
? trimmedValue
|
|
350
|
+
: `#${trimmedValue}`;
|
|
351
|
+
if (
|
|
352
|
+
!/^#(?:[A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/.test(
|
|
353
|
+
normalizedValue,
|
|
354
|
+
)
|
|
355
|
+
) {
|
|
356
|
+
throwConfigError(
|
|
357
|
+
`${label} must be a valid hex color (for example: #1d4ed8).`,
|
|
358
|
+
currentPath,
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return normalizedValue.toLowerCase();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function normalizeThemeColorConfig(
|
|
366
|
+
value: unknown,
|
|
367
|
+
currentPath: Path,
|
|
368
|
+
label: string,
|
|
369
|
+
): string | ThemeColorByMode {
|
|
370
|
+
if (typeof value === "string") {
|
|
371
|
+
return normalizeHexColor(value, currentPath, label);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
checkType(value, "object", currentPath, label);
|
|
375
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
376
|
+
throwConfigError(
|
|
377
|
+
`${label} must be a string or an object with light/dark values.`,
|
|
378
|
+
currentPath,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const colorByMode = value as Record<string, unknown>;
|
|
383
|
+
const allowedKeys = new Set(["light", "dark"]);
|
|
384
|
+
for (const key of Object.keys(colorByMode)) {
|
|
385
|
+
if (!allowedKeys.has(key)) {
|
|
386
|
+
throwConfigError(`${label} object only supports 'light' and 'dark'.`, [
|
|
387
|
+
...currentPath,
|
|
388
|
+
key,
|
|
389
|
+
]);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const light =
|
|
394
|
+
colorByMode.light !== undefined
|
|
395
|
+
? normalizeHexColor(colorByMode.light, [...currentPath, "light"], label)
|
|
396
|
+
: undefined;
|
|
397
|
+
const dark =
|
|
398
|
+
colorByMode.dark !== undefined
|
|
399
|
+
? normalizeHexColor(colorByMode.dark, [...currentPath, "dark"], label)
|
|
400
|
+
: undefined;
|
|
401
|
+
|
|
402
|
+
if (light === undefined && dark === undefined) {
|
|
403
|
+
throwConfigError(
|
|
404
|
+
`${label} object must include 'light', 'dark', or both.`,
|
|
405
|
+
currentPath,
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
...(light !== undefined ? { light } : {}),
|
|
411
|
+
...(dark !== undefined ? { dark } : {}),
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function normalizeHexColorArray(
|
|
416
|
+
value: unknown,
|
|
417
|
+
currentPath: Path,
|
|
418
|
+
label: string,
|
|
419
|
+
): string[] {
|
|
420
|
+
checkType(value, "array", currentPath, label);
|
|
421
|
+
if (!Array.isArray(value)) {
|
|
422
|
+
throwConfigError(`${label} must be an array.`, currentPath);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (value.length < 1 || value.length > 4) {
|
|
426
|
+
throwConfigError(`${label} must include 1 to 4 colors.`, currentPath);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return value.map((color, index) =>
|
|
430
|
+
normalizeHexColor(color, [...currentPath, index], `${label} ${index + 1}`),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function normalizeSeedValue(
|
|
435
|
+
value: unknown,
|
|
436
|
+
currentPath: Path,
|
|
437
|
+
label: string,
|
|
438
|
+
): string {
|
|
439
|
+
checkType(value, "string", currentPath, label);
|
|
440
|
+
if (typeof value !== "string") {
|
|
441
|
+
throwConfigError(`${label} must be a string.`, currentPath);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const trimmedValue = value.trim();
|
|
445
|
+
if (trimmedValue.length === 0) {
|
|
446
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return trimmedValue;
|
|
450
|
+
}
|
|
451
|
+
|
|
295
452
|
function validateFileExistence(filePath: string, currentPath: Path): void {
|
|
296
453
|
// Assuming relative path from DOCS_DIR and .mdx extension
|
|
297
454
|
const fullPath = path.join(DOCS_DIR, `${filePath}.mdx`);
|
|
@@ -370,7 +527,11 @@ function normalizeInternalPageHref(
|
|
|
370
527
|
return null;
|
|
371
528
|
}
|
|
372
529
|
|
|
373
|
-
const filePath = normalizeDocsPagePath(
|
|
530
|
+
const filePath = normalizeDocsPagePath(
|
|
531
|
+
normalizedPathname,
|
|
532
|
+
currentPath,
|
|
533
|
+
label,
|
|
534
|
+
);
|
|
374
535
|
validateFileExistence(filePath, currentPath);
|
|
375
536
|
|
|
376
537
|
return {
|
|
@@ -1172,6 +1333,21 @@ function validateNavbarItem(
|
|
|
1172
1333
|
|
|
1173
1334
|
// Optional property
|
|
1174
1335
|
validateIcon(item.icon, [...currentPath, "icon"]);
|
|
1336
|
+
if (item.color !== undefined) {
|
|
1337
|
+
if (currentPath[0] !== "navbar" || currentPath[1] !== "primary") {
|
|
1338
|
+
throwConfigError(
|
|
1339
|
+
"Navbar item color is only supported on navbar.primary.",
|
|
1340
|
+
[...currentPath, "color"],
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
item.color = normalizeThemeColorConfig(
|
|
1345
|
+
item.color,
|
|
1346
|
+
[...currentPath, "color"],
|
|
1347
|
+
"Navbar primary color",
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1175
1351
|
return hiddenPageRoute;
|
|
1176
1352
|
}
|
|
1177
1353
|
|
|
@@ -1222,10 +1398,85 @@ function validateLogoImagePath(
|
|
|
1222
1398
|
|
|
1223
1399
|
if (!fs.existsSync(fullPath)) {
|
|
1224
1400
|
throwConfigError(
|
|
1225
|
-
`${label} file not found. Expected: ${normalizedPath}
|
|
1401
|
+
`${label} file not found. Expected: ${normalizedPath}`,
|
|
1402
|
+
currentPath,
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
function validateAssistantIconSource(
|
|
1408
|
+
iconSource: unknown,
|
|
1409
|
+
currentPath: Path,
|
|
1410
|
+
): string {
|
|
1411
|
+
checkType(iconSource, "string", currentPath, "Assistant icon source");
|
|
1412
|
+
if (typeof iconSource !== "string") {
|
|
1413
|
+
throwConfigError("Assistant icon source must be a string.", currentPath);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const trimmedSource = iconSource.trim();
|
|
1417
|
+
if (trimmedSource.length === 0) {
|
|
1418
|
+
throwConfigError("Assistant icon source cannot be empty.", currentPath);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (isUrl(trimmedSource)) {
|
|
1422
|
+
throwConfigError(
|
|
1423
|
+
"Assistant icon source must be a local image path relative to docs.json.",
|
|
1424
|
+
currentPath,
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if (trimmedSource.includes(":")) {
|
|
1429
|
+
throwConfigError(
|
|
1430
|
+
`Invalid assistant icon source: "${trimmedSource}". Assistant icons must be local image files, not Iconify icon names.`,
|
|
1431
|
+
currentPath,
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
if (
|
|
1436
|
+
trimmedSource.startsWith("//") ||
|
|
1437
|
+
trimmedSource.startsWith("#") ||
|
|
1438
|
+
trimmedSource.startsWith("?") ||
|
|
1439
|
+
trimmedSource.startsWith("./") ||
|
|
1440
|
+
trimmedSource.startsWith("../")
|
|
1441
|
+
) {
|
|
1442
|
+
throwConfigError(
|
|
1443
|
+
"Assistant icon source must be a local image path relative to docs.json.",
|
|
1444
|
+
currentPath,
|
|
1445
|
+
);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
const parsed = new URL(trimmedSource, "https://docs.invalid/");
|
|
1449
|
+
const validExtensions = [
|
|
1450
|
+
".svg",
|
|
1451
|
+
".png",
|
|
1452
|
+
".jpg",
|
|
1453
|
+
".jpeg",
|
|
1454
|
+
".webp",
|
|
1455
|
+
".gif",
|
|
1456
|
+
".ico",
|
|
1457
|
+
".avif",
|
|
1458
|
+
];
|
|
1459
|
+
const hasValidExtension = validExtensions.some((ext) =>
|
|
1460
|
+
parsed.pathname.toLowerCase().endsWith(ext),
|
|
1461
|
+
);
|
|
1462
|
+
if (!hasValidExtension) {
|
|
1463
|
+
throwConfigError(
|
|
1464
|
+
"Assistant icon source must be a valid image file (.svg, .png, .jpg, .jpeg, .webp, .gif, .ico, .avif).",
|
|
1465
|
+
currentPath,
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
const normalizedPath = parsed.pathname.replace(/^\/+/, "");
|
|
1470
|
+
const fullPath = path.join(DOCS_DIR, normalizedPath);
|
|
1471
|
+
|
|
1472
|
+
if (!fs.existsSync(fullPath)) {
|
|
1473
|
+
throwConfigError(
|
|
1474
|
+
`Assistant icon source file not found. Expected: ${normalizedPath}`,
|
|
1226
1475
|
currentPath,
|
|
1227
1476
|
);
|
|
1228
1477
|
}
|
|
1478
|
+
|
|
1479
|
+
return trimmedSource;
|
|
1229
1480
|
}
|
|
1230
1481
|
|
|
1231
1482
|
function validateLogoVariant(
|
|
@@ -1397,38 +1648,6 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1397
1648
|
return normalizedBaseColor as BaseColorOption;
|
|
1398
1649
|
};
|
|
1399
1650
|
|
|
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
|
-
}
|
|
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
1651
|
if (theme.baseColor !== undefined) {
|
|
1433
1652
|
if (typeof theme.baseColor === "string") {
|
|
1434
1653
|
theme.baseColor = normalizeBaseColor(
|
|
@@ -1499,12 +1718,122 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1499
1718
|
}
|
|
1500
1719
|
}
|
|
1501
1720
|
|
|
1721
|
+
if (theme.card !== undefined) {
|
|
1722
|
+
checkType(theme.card, "object", ["theme", "card"], "Theme card");
|
|
1723
|
+
if (
|
|
1724
|
+
typeof theme.card !== "object" ||
|
|
1725
|
+
theme.card === null ||
|
|
1726
|
+
Array.isArray(theme.card)
|
|
1727
|
+
) {
|
|
1728
|
+
throwConfigError("Theme card must be an object.", ["theme", "card"]);
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
const cardTheme = theme.card as CardTheme & Record<string, unknown>;
|
|
1732
|
+
const allowedCardKeys = new Set(["cover", "button"]);
|
|
1733
|
+
for (const key of Object.keys(cardTheme)) {
|
|
1734
|
+
if (!allowedCardKeys.has(key)) {
|
|
1735
|
+
throwConfigError(
|
|
1736
|
+
"Theme card configuration only supports 'cover' and 'button'.",
|
|
1737
|
+
["theme", "card", key],
|
|
1738
|
+
);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if (cardTheme.cover !== undefined) {
|
|
1743
|
+
checkType(
|
|
1744
|
+
cardTheme.cover,
|
|
1745
|
+
"object",
|
|
1746
|
+
["theme", "card", "cover"],
|
|
1747
|
+
"Theme card cover",
|
|
1748
|
+
);
|
|
1749
|
+
if (
|
|
1750
|
+
typeof cardTheme.cover !== "object" ||
|
|
1751
|
+
cardTheme.cover === null ||
|
|
1752
|
+
Array.isArray(cardTheme.cover)
|
|
1753
|
+
) {
|
|
1754
|
+
throwConfigError("Theme card cover must be an object.", [
|
|
1755
|
+
"theme",
|
|
1756
|
+
"card",
|
|
1757
|
+
"cover",
|
|
1758
|
+
]);
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
const coverTheme = cardTheme.cover as CardCoverTheme &
|
|
1762
|
+
Record<string, unknown>;
|
|
1763
|
+
const allowedCoverKeys = new Set(["colors", "colorSeed"]);
|
|
1764
|
+
for (const key of Object.keys(coverTheme)) {
|
|
1765
|
+
if (!allowedCoverKeys.has(key)) {
|
|
1766
|
+
throwConfigError(
|
|
1767
|
+
"Theme card cover configuration only supports 'colors' and 'colorSeed'.",
|
|
1768
|
+
["theme", "card", "cover", key],
|
|
1769
|
+
);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
if (coverTheme.colors !== undefined) {
|
|
1774
|
+
coverTheme.colors = normalizeHexColorArray(
|
|
1775
|
+
coverTheme.colors,
|
|
1776
|
+
["theme", "card", "cover", "colors"],
|
|
1777
|
+
"Theme card cover colors",
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
if (coverTheme.colorSeed !== undefined) {
|
|
1782
|
+
coverTheme.colorSeed = normalizeSeedValue(
|
|
1783
|
+
coverTheme.colorSeed,
|
|
1784
|
+
["theme", "card", "cover", "colorSeed"],
|
|
1785
|
+
"Theme card cover color seed",
|
|
1786
|
+
);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
if (cardTheme.button !== undefined) {
|
|
1791
|
+
checkType(
|
|
1792
|
+
cardTheme.button,
|
|
1793
|
+
"object",
|
|
1794
|
+
["theme", "card", "button"],
|
|
1795
|
+
"Theme card button",
|
|
1796
|
+
);
|
|
1797
|
+
if (
|
|
1798
|
+
typeof cardTheme.button !== "object" ||
|
|
1799
|
+
cardTheme.button === null ||
|
|
1800
|
+
Array.isArray(cardTheme.button)
|
|
1801
|
+
) {
|
|
1802
|
+
throwConfigError("Theme card button must be an object.", [
|
|
1803
|
+
"theme",
|
|
1804
|
+
"card",
|
|
1805
|
+
"button",
|
|
1806
|
+
]);
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
const buttonTheme = cardTheme.button as CardButtonTheme &
|
|
1810
|
+
Record<string, unknown>;
|
|
1811
|
+
const allowedButtonKeys = new Set(["color"]);
|
|
1812
|
+
for (const key of Object.keys(buttonTheme)) {
|
|
1813
|
+
if (!allowedButtonKeys.has(key)) {
|
|
1814
|
+
throwConfigError(
|
|
1815
|
+
"Theme card button configuration only supports 'color'.",
|
|
1816
|
+
["theme", "card", "button", key],
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
if (buttonTheme.color !== undefined) {
|
|
1822
|
+
buttonTheme.color = normalizeThemeColorConfig(
|
|
1823
|
+
buttonTheme.color,
|
|
1824
|
+
["theme", "card", "button", "color"],
|
|
1825
|
+
"Theme card button color",
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1502
1831
|
if (theme.themeColor === undefined) {
|
|
1503
1832
|
return;
|
|
1504
1833
|
}
|
|
1505
1834
|
|
|
1506
1835
|
if (typeof theme.themeColor === "string") {
|
|
1507
|
-
theme.themeColor =
|
|
1836
|
+
theme.themeColor = normalizeHexColor(
|
|
1508
1837
|
theme.themeColor,
|
|
1509
1838
|
["theme", "themeColor"],
|
|
1510
1839
|
"Theme color",
|
|
@@ -1538,7 +1867,7 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1538
1867
|
|
|
1539
1868
|
const light =
|
|
1540
1869
|
themeColorByMode.light !== undefined
|
|
1541
|
-
?
|
|
1870
|
+
? normalizeHexColor(
|
|
1542
1871
|
themeColorByMode.light,
|
|
1543
1872
|
["theme", "themeColor", "light"],
|
|
1544
1873
|
"Theme color light",
|
|
@@ -1546,7 +1875,7 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1546
1875
|
: undefined;
|
|
1547
1876
|
const dark =
|
|
1548
1877
|
themeColorByMode.dark !== undefined
|
|
1549
|
-
?
|
|
1878
|
+
? normalizeHexColor(
|
|
1550
1879
|
themeColorByMode.dark,
|
|
1551
1880
|
["theme", "themeColor", "dark"],
|
|
1552
1881
|
"Theme color dark",
|
|
@@ -1561,11 +1890,308 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1561
1890
|
}
|
|
1562
1891
|
|
|
1563
1892
|
theme.themeColor = {
|
|
1564
|
-
light
|
|
1565
|
-
dark
|
|
1893
|
+
...(light !== undefined ? { light } : {}),
|
|
1894
|
+
...(dark !== undefined ? { dark } : {}),
|
|
1566
1895
|
};
|
|
1567
1896
|
}
|
|
1568
1897
|
|
|
1898
|
+
function validateAssistant(assistant: DocsConfig["assistant"]): void {
|
|
1899
|
+
if (assistant === undefined) return;
|
|
1900
|
+
|
|
1901
|
+
checkType(assistant, "object", ["assistant"], "Assistant configuration");
|
|
1902
|
+
if (
|
|
1903
|
+
typeof assistant !== "object" ||
|
|
1904
|
+
assistant === null ||
|
|
1905
|
+
Array.isArray(assistant)
|
|
1906
|
+
) {
|
|
1907
|
+
throwConfigError("Assistant configuration must be an object.", [
|
|
1908
|
+
"assistant",
|
|
1909
|
+
]);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
const allowedAssistantKeys = new Set([
|
|
1913
|
+
"button",
|
|
1914
|
+
"navbarButton",
|
|
1915
|
+
"heading",
|
|
1916
|
+
"questions",
|
|
1917
|
+
"icon",
|
|
1918
|
+
]);
|
|
1919
|
+
for (const key of Object.keys(assistant)) {
|
|
1920
|
+
if (!allowedAssistantKeys.has(key)) {
|
|
1921
|
+
throwConfigError(
|
|
1922
|
+
"Assistant configuration only supports 'button', 'navbarButton', 'heading', 'questions', and 'icon'.",
|
|
1923
|
+
["assistant", key],
|
|
1924
|
+
);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
if (assistant.button !== undefined) {
|
|
1929
|
+
checkType(
|
|
1930
|
+
assistant.button,
|
|
1931
|
+
"object",
|
|
1932
|
+
["assistant", "button"],
|
|
1933
|
+
"Assistant button configuration",
|
|
1934
|
+
);
|
|
1935
|
+
if (
|
|
1936
|
+
typeof assistant.button !== "object" ||
|
|
1937
|
+
assistant.button === null ||
|
|
1938
|
+
Array.isArray(assistant.button)
|
|
1939
|
+
) {
|
|
1940
|
+
throwConfigError("Assistant button configuration must be an object.", [
|
|
1941
|
+
"assistant",
|
|
1942
|
+
"button",
|
|
1943
|
+
]);
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
const allowedButtonKeys = new Set(["size", "color"]);
|
|
1947
|
+
for (const key of Object.keys(assistant.button)) {
|
|
1948
|
+
if (!allowedButtonKeys.has(key)) {
|
|
1949
|
+
throwConfigError(
|
|
1950
|
+
"Assistant button configuration only supports 'size' and 'color'.",
|
|
1951
|
+
["assistant", "button", key],
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
if (assistant.button.size !== undefined) {
|
|
1957
|
+
checkType(
|
|
1958
|
+
assistant.button.size,
|
|
1959
|
+
"string",
|
|
1960
|
+
["assistant", "button", "size"],
|
|
1961
|
+
"Assistant button size",
|
|
1962
|
+
);
|
|
1963
|
+
if (typeof assistant.button.size !== "string") {
|
|
1964
|
+
throwConfigError("Assistant button size must be a string.", [
|
|
1965
|
+
"assistant",
|
|
1966
|
+
"button",
|
|
1967
|
+
"size",
|
|
1968
|
+
]);
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
const trimmedSize = assistant.button.size.trim();
|
|
1972
|
+
if (trimmedSize !== "small" && trimmedSize !== "default") {
|
|
1973
|
+
throwConfigError(
|
|
1974
|
+
"Assistant button size must be either 'small' or 'default'.",
|
|
1975
|
+
["assistant", "button", "size"],
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
assistant.button.size = trimmedSize;
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
if (assistant.button.color !== undefined) {
|
|
1982
|
+
assistant.button.color = normalizeThemeColorConfig(
|
|
1983
|
+
assistant.button.color,
|
|
1984
|
+
["assistant", "button", "color"],
|
|
1985
|
+
"Assistant button color",
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
if (assistant.navbarButton !== undefined) {
|
|
1991
|
+
checkType(
|
|
1992
|
+
assistant.navbarButton,
|
|
1993
|
+
"object",
|
|
1994
|
+
["assistant", "navbarButton"],
|
|
1995
|
+
"Assistant navbar button configuration",
|
|
1996
|
+
);
|
|
1997
|
+
if (
|
|
1998
|
+
typeof assistant.navbarButton !== "object" ||
|
|
1999
|
+
assistant.navbarButton === null ||
|
|
2000
|
+
Array.isArray(assistant.navbarButton)
|
|
2001
|
+
) {
|
|
2002
|
+
throwConfigError(
|
|
2003
|
+
"Assistant navbar button configuration must be an object.",
|
|
2004
|
+
["assistant", "navbarButton"],
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
const allowedNavbarButtonKeys = new Set(["enabled", "text", "color"]);
|
|
2009
|
+
for (const key of Object.keys(assistant.navbarButton)) {
|
|
2010
|
+
if (!allowedNavbarButtonKeys.has(key)) {
|
|
2011
|
+
throwConfigError(
|
|
2012
|
+
"Assistant navbar button configuration only supports 'enabled', 'text', and 'color'.",
|
|
2013
|
+
["assistant", "navbarButton", key],
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
if (assistant.navbarButton.enabled !== undefined) {
|
|
2019
|
+
checkType(
|
|
2020
|
+
assistant.navbarButton.enabled,
|
|
2021
|
+
"boolean",
|
|
2022
|
+
["assistant", "navbarButton", "enabled"],
|
|
2023
|
+
"Assistant navbar button enabled",
|
|
2024
|
+
);
|
|
2025
|
+
if (typeof assistant.navbarButton.enabled !== "boolean") {
|
|
2026
|
+
throwConfigError(
|
|
2027
|
+
"Assistant navbar button enabled must be a boolean.",
|
|
2028
|
+
["assistant", "navbarButton", "enabled"],
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
if (assistant.navbarButton.text !== undefined) {
|
|
2034
|
+
checkType(
|
|
2035
|
+
assistant.navbarButton.text,
|
|
2036
|
+
"string",
|
|
2037
|
+
["assistant", "navbarButton", "text"],
|
|
2038
|
+
"Assistant navbar button text",
|
|
2039
|
+
);
|
|
2040
|
+
if (typeof assistant.navbarButton.text !== "string") {
|
|
2041
|
+
throwConfigError("Assistant navbar button text must be a string.", [
|
|
2042
|
+
"assistant",
|
|
2043
|
+
"navbarButton",
|
|
2044
|
+
"text",
|
|
2045
|
+
]);
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
const trimmedText = assistant.navbarButton.text.trim();
|
|
2049
|
+
if (trimmedText.length === 0) {
|
|
2050
|
+
throwConfigError("Assistant navbar button text cannot be empty.", [
|
|
2051
|
+
"assistant",
|
|
2052
|
+
"navbarButton",
|
|
2053
|
+
"text",
|
|
2054
|
+
]);
|
|
2055
|
+
}
|
|
2056
|
+
assistant.navbarButton.text = trimmedText;
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
if (assistant.navbarButton.color !== undefined) {
|
|
2060
|
+
assistant.navbarButton.color = normalizeThemeColorConfig(
|
|
2061
|
+
assistant.navbarButton.color,
|
|
2062
|
+
["assistant", "navbarButton", "color"],
|
|
2063
|
+
"Assistant navbar button color",
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
if (assistant.heading !== undefined) {
|
|
2069
|
+
checkType(
|
|
2070
|
+
assistant.heading,
|
|
2071
|
+
"string",
|
|
2072
|
+
["assistant", "heading"],
|
|
2073
|
+
"Assistant heading",
|
|
2074
|
+
);
|
|
2075
|
+
if (typeof assistant.heading !== "string") {
|
|
2076
|
+
throwConfigError("Assistant heading must be a string.", [
|
|
2077
|
+
"assistant",
|
|
2078
|
+
"heading",
|
|
2079
|
+
]);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
const trimmedHeading = assistant.heading.trim();
|
|
2083
|
+
if (trimmedHeading.length === 0) {
|
|
2084
|
+
throwConfigError("Assistant heading cannot be empty.", [
|
|
2085
|
+
"assistant",
|
|
2086
|
+
"heading",
|
|
2087
|
+
]);
|
|
2088
|
+
}
|
|
2089
|
+
assistant.heading = trimmedHeading;
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
if (assistant.questions !== undefined) {
|
|
2093
|
+
checkType(
|
|
2094
|
+
assistant.questions,
|
|
2095
|
+
"array",
|
|
2096
|
+
["assistant", "questions"],
|
|
2097
|
+
"Assistant questions",
|
|
2098
|
+
);
|
|
2099
|
+
if (!Array.isArray(assistant.questions)) {
|
|
2100
|
+
throwConfigError("Assistant questions must be an array.", [
|
|
2101
|
+
"assistant",
|
|
2102
|
+
"questions",
|
|
2103
|
+
]);
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
if (assistant.questions.length > 3) {
|
|
2107
|
+
throwConfigError("Assistant questions can include at most 3 questions.", [
|
|
2108
|
+
"assistant",
|
|
2109
|
+
"questions",
|
|
2110
|
+
]);
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
assistant.questions = assistant.questions.map((question, index) => {
|
|
2114
|
+
checkType(
|
|
2115
|
+
question,
|
|
2116
|
+
"string",
|
|
2117
|
+
["assistant", "questions", String(index)],
|
|
2118
|
+
"Assistant question",
|
|
2119
|
+
);
|
|
2120
|
+
if (typeof question !== "string") {
|
|
2121
|
+
throwConfigError("Assistant question must be a string.", [
|
|
2122
|
+
"assistant",
|
|
2123
|
+
"questions",
|
|
2124
|
+
String(index),
|
|
2125
|
+
]);
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
const trimmedQuestion = question.trim();
|
|
2129
|
+
if (trimmedQuestion.length === 0) {
|
|
2130
|
+
throwConfigError(
|
|
2131
|
+
"Assistant question cannot be empty.",
|
|
2132
|
+
["assistant", "questions", String(index)],
|
|
2133
|
+
);
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
return trimmedQuestion;
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
if (assistant.icon === undefined) return;
|
|
2141
|
+
|
|
2142
|
+
checkType(
|
|
2143
|
+
assistant.icon,
|
|
2144
|
+
"object",
|
|
2145
|
+
["assistant", "icon"],
|
|
2146
|
+
"Assistant icon",
|
|
2147
|
+
);
|
|
2148
|
+
if (
|
|
2149
|
+
typeof assistant.icon !== "object" ||
|
|
2150
|
+
assistant.icon === null ||
|
|
2151
|
+
Array.isArray(assistant.icon)
|
|
2152
|
+
) {
|
|
2153
|
+
throwConfigError("Assistant icon must be an object.", [
|
|
2154
|
+
"assistant",
|
|
2155
|
+
"icon",
|
|
2156
|
+
]);
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
const allowedIconKeys = new Set(["src", "color"]);
|
|
2160
|
+
for (const key of Object.keys(assistant.icon)) {
|
|
2161
|
+
if (!allowedIconKeys.has(key)) {
|
|
2162
|
+
throwConfigError(
|
|
2163
|
+
"Assistant icon only supports 'src' and 'color'.",
|
|
2164
|
+
["assistant", "icon", key],
|
|
2165
|
+
);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
if (
|
|
2170
|
+
assistant.icon.src === undefined &&
|
|
2171
|
+
assistant.icon.color === undefined
|
|
2172
|
+
) {
|
|
2173
|
+
throwConfigError(
|
|
2174
|
+
"Assistant icon must include 'src', 'color', or both.",
|
|
2175
|
+
["assistant", "icon"],
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
|
|
2179
|
+
if (assistant.icon.src !== undefined) {
|
|
2180
|
+
assistant.icon.src = validateAssistantIconSource(
|
|
2181
|
+
assistant.icon.src,
|
|
2182
|
+
["assistant", "icon", "src"],
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
if (assistant.icon.color !== undefined) {
|
|
2187
|
+
assistant.icon.color = normalizeHexColor(
|
|
2188
|
+
assistant.icon.color,
|
|
2189
|
+
["assistant", "icon", "color"],
|
|
2190
|
+
"Assistant icon color",
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
1569
2195
|
function validateHome(home: DocsConfig["home"]): string | undefined {
|
|
1570
2196
|
if (home === undefined) return undefined;
|
|
1571
2197
|
|
|
@@ -1765,6 +2391,7 @@ async function validateConfig(config: any): Promise<DocsConfig> {
|
|
|
1765
2391
|
validateTitle(config.title);
|
|
1766
2392
|
validateLogo(config.logo);
|
|
1767
2393
|
validateTheme(config.theme);
|
|
2394
|
+
validateAssistant(config.assistant);
|
|
1768
2395
|
await validateNavigation(config.navigation);
|
|
1769
2396
|
config.home = validateHome(config.home);
|
|
1770
2397
|
|