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
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type NavOpenApiPage,
|
|
6
6
|
type NavMenuItem,
|
|
7
7
|
type NavOpenApi,
|
|
8
|
+
type HiddenPageRoute,
|
|
8
9
|
loadOpenApiSpec,
|
|
9
10
|
} from "./validation";
|
|
10
11
|
import {
|
|
@@ -21,6 +22,7 @@ type MdxNavPageItem = string | NavPage;
|
|
|
21
22
|
export interface BaseRoute {
|
|
22
23
|
slug: string;
|
|
23
24
|
title: string;
|
|
25
|
+
hidden?: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// MDX route
|
|
@@ -95,6 +97,35 @@ function processPageItem(
|
|
|
95
97
|
};
|
|
96
98
|
}
|
|
97
99
|
|
|
100
|
+
function processHiddenPageRoute(
|
|
101
|
+
route: HiddenPageRoute,
|
|
102
|
+
docs: any[],
|
|
103
|
+
): MdxRoute {
|
|
104
|
+
const entry = docs.find((doc: any) => {
|
|
105
|
+
const docPath = doc.id.replace(/\.mdx$/, "");
|
|
106
|
+
return docPath === route.filePath;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!entry) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Could not find content collection entry for path: ${route.filePath}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const slug = route.href.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
type: "mdx",
|
|
119
|
+
slug,
|
|
120
|
+
filePath: route.filePath,
|
|
121
|
+
title: resolveMdxPageTitle({
|
|
122
|
+
entry,
|
|
123
|
+
filePath: route.filePath,
|
|
124
|
+
}),
|
|
125
|
+
hidden: true,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
98
129
|
type OpenApiOperationLookup = {
|
|
99
130
|
method: string;
|
|
100
131
|
path: string;
|
|
@@ -368,7 +399,7 @@ function assertUniqueRouteSlugs(routes: Route[]): void {
|
|
|
368
399
|
: `openapi:${route.filePath}:${route.openApiMethod.toUpperCase()} ${route.openApiPath}`;
|
|
369
400
|
|
|
370
401
|
throw new Error(
|
|
371
|
-
`Duplicate route slug "${route.slug}" generated by "${existingLabel}" and "${candidateLabel}".`,
|
|
402
|
+
`[USER_ERROR]: Invalid docs.json: Duplicate route slug "${route.slug}" generated by "${existingLabel}" and "${candidateLabel}". Remove one of the duplicate references or change navigation structure so each route resolves to a unique URL.`,
|
|
372
403
|
);
|
|
373
404
|
}
|
|
374
405
|
}
|
|
@@ -405,6 +436,23 @@ export async function getAllRoutes(): Promise<Route[]> {
|
|
|
405
436
|
}
|
|
406
437
|
}
|
|
407
438
|
|
|
439
|
+
for (const hiddenPageRoute of config.hiddenPageRoutes ?? []) {
|
|
440
|
+
const hiddenRoute = processHiddenPageRoute(hiddenPageRoute, docs);
|
|
441
|
+
const existingRoute = allRoutes.find(
|
|
442
|
+
(route) => route.slug === hiddenRoute.slug,
|
|
443
|
+
);
|
|
444
|
+
if (existingRoute) {
|
|
445
|
+
if (
|
|
446
|
+
existingRoute.type === "mdx" &&
|
|
447
|
+
existingRoute.filePath === hiddenRoute.filePath
|
|
448
|
+
) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
allRoutes.push(hiddenRoute);
|
|
454
|
+
}
|
|
455
|
+
|
|
408
456
|
assertUniqueRouteSlugs(allRoutes);
|
|
409
457
|
return allRoutes;
|
|
410
458
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withBasePath } from "./base-path";
|
|
2
|
+
|
|
1
3
|
type AssetsPrefixValue = string | Record<string, string> | undefined;
|
|
2
4
|
|
|
3
5
|
function normalizePrefix(value: string): string {
|
|
@@ -54,7 +56,7 @@ export function resolveStaticAssetUrl(rawPath: string): string {
|
|
|
54
56
|
|
|
55
57
|
const prefix = resolveAssetsPrefix(import.meta.env.ASSETS_PREFIX);
|
|
56
58
|
if (!prefix) {
|
|
57
|
-
return `${normalizedPathname}${parsed.search}${parsed.hash}`;
|
|
59
|
+
return `${withBasePath(normalizedPathname)}${parsed.search}${parsed.hash}`;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
const normalizedPrefixPath = `${prefix}/${normalizedPathname.replace(/^\/+/, "")}`;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import colors from "tailwindcss/colors";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_THEME_COLOR_DARK,
|
|
4
|
+
DEFAULT_THEME_COLOR_LIGHT,
|
|
5
|
+
type BaseColorOption,
|
|
6
|
+
type DocsConfig,
|
|
7
|
+
} from "./validation";
|
|
8
|
+
|
|
9
|
+
const neutralColorShades = [
|
|
10
|
+
"50",
|
|
11
|
+
"100",
|
|
12
|
+
"200",
|
|
13
|
+
"300",
|
|
14
|
+
"400",
|
|
15
|
+
"500",
|
|
16
|
+
"600",
|
|
17
|
+
"700",
|
|
18
|
+
"800",
|
|
19
|
+
"900",
|
|
20
|
+
"950",
|
|
21
|
+
] as const;
|
|
22
|
+
type NeutralColorShade = (typeof neutralColorShades)[number];
|
|
23
|
+
|
|
24
|
+
const paletteColors = colors as unknown as Record<
|
|
25
|
+
string,
|
|
26
|
+
Record<NeutralColorShade, string>
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
function normalizeHexColorToRgb(
|
|
30
|
+
hexColor: string,
|
|
31
|
+
): { r: number; g: number; b: number } | null {
|
|
32
|
+
const normalized = hexColor.replace("#", "").trim();
|
|
33
|
+
if (/^[a-fA-F0-9]{3,4}$/.test(normalized)) {
|
|
34
|
+
const [r, g, b] = normalized.split("");
|
|
35
|
+
if (!r || !g || !b) return null;
|
|
36
|
+
return {
|
|
37
|
+
r: Number.parseInt(`${r}${r}`, 16),
|
|
38
|
+
g: Number.parseInt(`${g}${g}`, 16),
|
|
39
|
+
b: Number.parseInt(`${b}${b}`, 16),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (/^[a-fA-F0-9]{6,8}$/.test(normalized)) {
|
|
44
|
+
return {
|
|
45
|
+
r: Number.parseInt(normalized.slice(0, 2), 16),
|
|
46
|
+
g: Number.parseInt(normalized.slice(2, 4), 16),
|
|
47
|
+
b: Number.parseInt(normalized.slice(4, 6), 16),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getOklabLightness(color: string): number | null {
|
|
55
|
+
const match = color
|
|
56
|
+
.trim()
|
|
57
|
+
.match(/^okl(?:ab|ch)\(\s*([+-]?(?:\d+\.?\d*|\.\d+))(%?)/i);
|
|
58
|
+
if (!match?.[1]) return null;
|
|
59
|
+
|
|
60
|
+
const lightness = Number.parseFloat(match[1]);
|
|
61
|
+
if (!Number.isFinite(lightness)) return null;
|
|
62
|
+
|
|
63
|
+
const normalizedLightness =
|
|
64
|
+
match[2] === "%" || lightness > 1 ? lightness / 100 : lightness;
|
|
65
|
+
return Math.min(Math.max(normalizedLightness, 0), 1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getThemeForegroundColor(
|
|
69
|
+
color: string,
|
|
70
|
+
darkForeground = "#111827",
|
|
71
|
+
): string {
|
|
72
|
+
const rgb = normalizeHexColorToRgb(color);
|
|
73
|
+
const oklabLightness = getOklabLightness(color);
|
|
74
|
+
if (!rgb && oklabLightness !== null) {
|
|
75
|
+
return oklabLightness > 0.6 ? darkForeground : "#ffffff";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!rgb) return "#ffffff";
|
|
79
|
+
|
|
80
|
+
const toLinear = (channel: number): number => {
|
|
81
|
+
const normalized = channel / 255;
|
|
82
|
+
return normalized <= 0.03928
|
|
83
|
+
? normalized / 12.92
|
|
84
|
+
: ((normalized + 0.055) / 1.055) ** 2.4;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const luminance =
|
|
88
|
+
0.2126 * toLinear(rgb.r) +
|
|
89
|
+
0.7152 * toLinear(rgb.g) +
|
|
90
|
+
0.0722 * toLinear(rgb.b);
|
|
91
|
+
return luminance > 0.45 ? darkForeground : "#ffffff";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function getThemeColorVariables(themeColor: string): string[] {
|
|
95
|
+
const foreground = getThemeForegroundColor(themeColor);
|
|
96
|
+
const iconFilter = foreground === "#ffffff" ? "invert(1)" : "none";
|
|
97
|
+
|
|
98
|
+
return [
|
|
99
|
+
`--color-theme: ${themeColor};`,
|
|
100
|
+
`--color-theme-foreground: ${foreground};`,
|
|
101
|
+
`--color-theme-icon-filter: ${iconFilter};`,
|
|
102
|
+
"--color-theme-top: color-mix(in oklab, var(--color-theme) 88%, white);",
|
|
103
|
+
"--color-theme-bottom: color-mix(in oklab, var(--color-theme) 90%, black);",
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getNeutralPaletteVariables(baseColor: BaseColorOption): string[] {
|
|
108
|
+
const palette = paletteColors[baseColor] ?? paletteColors.neutral;
|
|
109
|
+
return [
|
|
110
|
+
`--color-neutral: ${palette["500"]};`,
|
|
111
|
+
...neutralColorShades.map(
|
|
112
|
+
(shade) => `--color-neutral-${shade}: ${palette[shade]};`,
|
|
113
|
+
),
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getDocsBaseColorShade(
|
|
118
|
+
theme: DocsConfig["theme"],
|
|
119
|
+
mode: "light" | "dark",
|
|
120
|
+
shade: NeutralColorShade,
|
|
121
|
+
): string {
|
|
122
|
+
const themeBaseColor = theme?.baseColor ?? "neutral";
|
|
123
|
+
const baseColor: BaseColorOption =
|
|
124
|
+
typeof themeBaseColor === "string" ? themeBaseColor : themeBaseColor[mode];
|
|
125
|
+
const palette = paletteColors[baseColor] ?? paletteColors.neutral;
|
|
126
|
+
return palette[shade];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function getDocsLightBaseColorShade(
|
|
130
|
+
theme: DocsConfig["theme"],
|
|
131
|
+
shade: NeutralColorShade,
|
|
132
|
+
): string {
|
|
133
|
+
return getDocsBaseColorShade(theme, "light", shade);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getDocsDarkBaseColorShade(
|
|
137
|
+
theme: DocsConfig["theme"],
|
|
138
|
+
shade: NeutralColorShade,
|
|
139
|
+
): string {
|
|
140
|
+
return getDocsBaseColorShade(theme, "dark", shade);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function getDocsThemeCss(theme: DocsConfig["theme"]): string {
|
|
144
|
+
const themeBaseColor = theme?.baseColor ?? "neutral";
|
|
145
|
+
const themeThemeColor = theme?.themeColor;
|
|
146
|
+
const lightBaseColor: BaseColorOption =
|
|
147
|
+
typeof themeBaseColor === "string" ? themeBaseColor : themeBaseColor.light;
|
|
148
|
+
const darkBaseColor: BaseColorOption =
|
|
149
|
+
typeof themeBaseColor === "string" ? themeBaseColor : themeBaseColor.dark;
|
|
150
|
+
const lightThemeColor =
|
|
151
|
+
typeof themeThemeColor === "string"
|
|
152
|
+
? themeThemeColor
|
|
153
|
+
: (themeThemeColor?.light ?? DEFAULT_THEME_COLOR_LIGHT);
|
|
154
|
+
const darkThemeColor =
|
|
155
|
+
typeof themeThemeColor === "string"
|
|
156
|
+
? themeThemeColor
|
|
157
|
+
: (themeThemeColor?.dark ?? DEFAULT_THEME_COLOR_DARK);
|
|
158
|
+
|
|
159
|
+
const lightNeutralVariables = getNeutralPaletteVariables(lightBaseColor);
|
|
160
|
+
const darkNeutralVariables = getNeutralPaletteVariables(darkBaseColor);
|
|
161
|
+
const lightThemeColorVariables = getThemeColorVariables(lightThemeColor);
|
|
162
|
+
const darkThemeColorVariables = getThemeColorVariables(darkThemeColor);
|
|
163
|
+
|
|
164
|
+
return [
|
|
165
|
+
"html[data-theme='light'], html:not(.dark):not([data-theme='dark']) {",
|
|
166
|
+
...[...lightNeutralVariables, ...lightThemeColorVariables].map(
|
|
167
|
+
(declaration) => ` ${declaration.replace(/;$/, " !important;")}`,
|
|
168
|
+
),
|
|
169
|
+
"}",
|
|
170
|
+
"html.dark, html[data-theme='dark'] {",
|
|
171
|
+
...[...darkNeutralVariables, ...darkThemeColorVariables].map(
|
|
172
|
+
(declaration) => ` ${declaration.replace(/;$/, " !important;")}`,
|
|
173
|
+
),
|
|
174
|
+
"}",
|
|
175
|
+
].join("\n");
|
|
176
|
+
}
|
|
@@ -5,6 +5,7 @@ import remarkRehype from "remark-rehype";
|
|
|
5
5
|
import rehypeStringify from "rehype-stringify";
|
|
6
6
|
import rehypeExternalLinks from "./mdx/rehype-external-links";
|
|
7
7
|
import path from "node:path";
|
|
8
|
+
import { prependBasePath } from "./base-path";
|
|
8
9
|
|
|
9
10
|
export function slugify(text: string): string {
|
|
10
11
|
if (typeof text !== "string") {
|
|
@@ -66,7 +67,7 @@ export function buildMdxPageHref(args: {
|
|
|
66
67
|
homePath?: string;
|
|
67
68
|
}): string {
|
|
68
69
|
if (args.homePath && args.filePath === args.homePath) {
|
|
69
|
-
return "/";
|
|
70
|
+
return prependBasePath("/");
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
const filename = path.basename(args.filePath);
|
|
@@ -75,9 +76,11 @@ export function buildMdxPageHref(args: {
|
|
|
75
76
|
.replace(/^\/+/, "")
|
|
76
77
|
.replace(/\/+$/, "");
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
const href = normalizedGroupSlug
|
|
79
80
|
? `/${normalizedGroupSlug}/${pageSlug}`
|
|
80
81
|
: `/${pageSlug}`;
|
|
82
|
+
|
|
83
|
+
return prependBasePath(href);
|
|
81
84
|
}
|
|
82
85
|
|
|
83
86
|
export function parseOpenApiEndpoint(
|
|
@@ -103,7 +106,10 @@ export function parseOpenApiEndpoint(
|
|
|
103
106
|
};
|
|
104
107
|
}
|
|
105
108
|
|
|
106
|
-
export function buildOpenApiEndpointSlug(
|
|
109
|
+
export function buildOpenApiEndpointSlug(
|
|
110
|
+
pathStr: string,
|
|
111
|
+
method: string,
|
|
112
|
+
): string {
|
|
107
113
|
const normalizedPath = pathStr.startsWith("/") ? pathStr : `/${pathStr}`;
|
|
108
114
|
const pathSlug = normalizedPath
|
|
109
115
|
.replace(/^\//, "")
|
|
@@ -123,7 +129,9 @@ export function buildOpenApiEndpointHref(args: {
|
|
|
123
129
|
.replace(/^\/+/, "")
|
|
124
130
|
.replace(/\/+$/, "");
|
|
125
131
|
|
|
126
|
-
|
|
132
|
+
const href = normalizedGroupSlug
|
|
127
133
|
? `/${normalizedGroupSlug}/${endpointSlug}`
|
|
128
134
|
: `/${endpointSlug}`;
|
|
135
|
+
|
|
136
|
+
return prependBasePath(href);
|
|
129
137
|
}
|