radiant-docs 0.1.49 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +75 -5
- package/package.json +1 -1
- package/template/astro.config.mjs +7 -2
- package/template/package.json +1 -0
- package/template/src/components/Header.astro +8 -8
- package/template/src/components/SidebarGroup.astro +1 -1
- package/template/src/components/SidebarLink.astro +3 -3
- package/template/src/components/SidebarSubgroup.astro +1 -1
- package/template/src/components/sidebar/SidebarEndpointLink.astro +3 -2
- package/template/src/components/ui/Tag.astro +64 -2
- package/template/src/components/user/CodeBlock.astro +1 -1
- package/template/src/components/user/ComponentPreviewBlock.astro +1 -1
- package/template/src/components/user/Image.astro +51 -34
- package/template/src/lib/assistant-embed-script.ts +279 -18
- package/template/src/lib/validation.ts +124 -23
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ var log = {
|
|
|
36
36
|
),
|
|
37
37
|
blank: () => console.log("")
|
|
38
38
|
};
|
|
39
|
+
var SUPPORTED_COMMANDS = /* @__PURE__ */ new Set(["dev", "check"]);
|
|
39
40
|
function getUserDocsDir() {
|
|
40
41
|
return process.cwd();
|
|
41
42
|
}
|
|
@@ -75,6 +76,9 @@ async function getDirectoryFingerprint(dir) {
|
|
|
75
76
|
await walk(dir);
|
|
76
77
|
return hash.digest("hex");
|
|
77
78
|
}
|
|
79
|
+
function hasFrameworkDependencies(cacheDir) {
|
|
80
|
+
return fs.existsSync(path.join(cacheDir, "node_modules/.bin/astro"));
|
|
81
|
+
}
|
|
78
82
|
async function validateUserDocs(docsDir) {
|
|
79
83
|
const docsJsonPath = path.join(docsDir, "docs.json");
|
|
80
84
|
if (!fs.existsSync(docsJsonPath)) {
|
|
@@ -94,7 +98,7 @@ async function validateUserDocs(docsDir) {
|
|
|
94
98
|
}
|
|
95
99
|
async function setupCache(cacheDir) {
|
|
96
100
|
const templateDir = getTemplateDir();
|
|
97
|
-
const
|
|
101
|
+
const dependenciesInstalled = hasFrameworkDependencies(cacheDir);
|
|
98
102
|
const cacheExists = fs.existsSync(cacheDir);
|
|
99
103
|
if (!fs.existsSync(templateDir)) {
|
|
100
104
|
log.error(
|
|
@@ -132,7 +136,7 @@ async function setupCache(cacheDir) {
|
|
|
132
136
|
log.success("Framework copied to cache.");
|
|
133
137
|
return true;
|
|
134
138
|
}
|
|
135
|
-
return !
|
|
139
|
+
return !dependenciesInstalled;
|
|
136
140
|
}
|
|
137
141
|
async function installDependencies(cacheDir) {
|
|
138
142
|
log.info("Installing dependencies (first run only)...");
|
|
@@ -149,6 +153,7 @@ async function installDependencies(cacheDir) {
|
|
|
149
153
|
});
|
|
150
154
|
log.success("Dependencies installed.");
|
|
151
155
|
} catch (error) {
|
|
156
|
+
await fs.remove(path.join(cacheDir, "node_modules"));
|
|
152
157
|
log.error("Failed to install dependencies.");
|
|
153
158
|
log.error(`Error details: ${error.message}`);
|
|
154
159
|
throw error;
|
|
@@ -179,6 +184,66 @@ async function runAstroSync(cacheDir) {
|
|
|
179
184
|
} catch {
|
|
180
185
|
}
|
|
181
186
|
}
|
|
187
|
+
function toCommandOutput(value) {
|
|
188
|
+
if (!value) return "";
|
|
189
|
+
if (Buffer.isBuffer(value)) return value.toString("utf-8");
|
|
190
|
+
return String(value);
|
|
191
|
+
}
|
|
192
|
+
function getCommandOutput(error) {
|
|
193
|
+
return [
|
|
194
|
+
toCommandOutput(error?.stdout),
|
|
195
|
+
toCommandOutput(error?.stderr),
|
|
196
|
+
toCommandOutput(error?.message)
|
|
197
|
+
].filter(Boolean).join("\n");
|
|
198
|
+
}
|
|
199
|
+
function extractUserErrors(output) {
|
|
200
|
+
const errors = [];
|
|
201
|
+
for (const line of output.split("\n")) {
|
|
202
|
+
if (!line.includes("[USER_ERROR]")) continue;
|
|
203
|
+
const message = line.split("[USER_ERROR]:")[1]?.trim() || line.trim();
|
|
204
|
+
if (message && !errors.includes(message)) {
|
|
205
|
+
errors.push(message);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return errors;
|
|
209
|
+
}
|
|
210
|
+
function getOutputTail(output, maxLines = 40) {
|
|
211
|
+
return output.split("\n").map((line) => line.trim()).filter(Boolean).slice(-maxLines).join("\n");
|
|
212
|
+
}
|
|
213
|
+
function runFrameworkCheck(cacheDir) {
|
|
214
|
+
log.info("Checking documentation...");
|
|
215
|
+
try {
|
|
216
|
+
execSync("npm run check", {
|
|
217
|
+
cwd: cacheDir,
|
|
218
|
+
stdio: "pipe",
|
|
219
|
+
env: {
|
|
220
|
+
...process.env,
|
|
221
|
+
RADIANT_CHECK: "1"
|
|
222
|
+
},
|
|
223
|
+
encoding: "utf-8"
|
|
224
|
+
});
|
|
225
|
+
log.success("Documentation check passed.");
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const output = getCommandOutput(error);
|
|
228
|
+
const userErrors = extractUserErrors(output);
|
|
229
|
+
log.blank();
|
|
230
|
+
log.error("Documentation check failed.");
|
|
231
|
+
if (userErrors.length > 0) {
|
|
232
|
+
log.blank();
|
|
233
|
+
for (const userError of userErrors) {
|
|
234
|
+
log.error(userError);
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
const outputTail = getOutputTail(output);
|
|
238
|
+
if (outputTail) {
|
|
239
|
+
log.blank();
|
|
240
|
+
console.log(outputTail);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
log.blank();
|
|
244
|
+
process.exit(typeof error?.status === "number" ? error.status : 1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
182
247
|
function startDevServer(cacheDir) {
|
|
183
248
|
const child = spawn("npm", ["run", "dev"], {
|
|
184
249
|
cwd: cacheDir,
|
|
@@ -284,12 +349,13 @@ ${colors.bright}${colors.cyan}\u25C6 Radiant${colors.reset} v${VERSION}
|
|
|
284
349
|
${colors.bright}Usage:${colors.reset}
|
|
285
350
|
radiant Start the dev server
|
|
286
351
|
radiant dev Start the dev server
|
|
352
|
+
radiant check Validate and build the docs once
|
|
287
353
|
radiant --help Show this help message
|
|
288
354
|
radiant --version Show version
|
|
289
355
|
|
|
290
356
|
${colors.bright}Description:${colors.reset}
|
|
291
357
|
Run this command from your documentation folder (containing docs.json)
|
|
292
|
-
to preview your docs locally with hot reload.
|
|
358
|
+
to preview your docs locally with hot reload, or run a one-time check.
|
|
293
359
|
`);
|
|
294
360
|
}
|
|
295
361
|
async function run() {
|
|
@@ -302,8 +368,8 @@ async function run() {
|
|
|
302
368
|
console.log(VERSION);
|
|
303
369
|
process.exit(0);
|
|
304
370
|
}
|
|
305
|
-
const command = args[0];
|
|
306
|
-
if (command
|
|
371
|
+
const command = args[0] || "dev";
|
|
372
|
+
if (!SUPPORTED_COMMANDS.has(command)) {
|
|
307
373
|
log.error(`Unknown command: ${command}`);
|
|
308
374
|
showHelp();
|
|
309
375
|
process.exit(1);
|
|
@@ -325,6 +391,10 @@ async function run() {
|
|
|
325
391
|
await syncContent(docsDir, contentDir);
|
|
326
392
|
await runAstroSync(cacheDir);
|
|
327
393
|
log.success("Content synced.");
|
|
394
|
+
if (command === "check") {
|
|
395
|
+
runFrameworkCheck(cacheDir);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
328
398
|
log.info("Starting dev server...");
|
|
329
399
|
const devServer = startDevServer(cacheDir);
|
|
330
400
|
const watcher = watchUserContent(docsDir, contentDir, async () => {
|
package/package.json
CHANGED
|
@@ -68,8 +68,13 @@ const headingAnchorContent = {
|
|
|
68
68
|
],
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
const shouldValidateDocs =
|
|
72
|
+
process.env.npm_lifecycle_event === "build" ||
|
|
73
|
+
process.env.npm_lifecycle_event === "check" ||
|
|
74
|
+
process.env.RADIANT_CHECK === "1";
|
|
75
|
+
|
|
76
|
+
if (shouldValidateDocs) {
|
|
77
|
+
// Run validation before Astro's internal validation for build/check commands.
|
|
73
78
|
try {
|
|
74
79
|
await getConfig();
|
|
75
80
|
await validateMdxContent();
|
package/template/package.json
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "astro dev",
|
|
7
7
|
"start": "tsx runner.ts",
|
|
8
|
+
"check": "astro build",
|
|
8
9
|
"prebuild": "rm -rf public/pagefind",
|
|
9
10
|
"build": "astro build && node scripts/remove-assistant-for-non-pro.mjs && node scripts/generate-og-metadata.mjs && node scripts/generate-og-images.mjs && node scripts/stamp-og-image-versions.mjs && node scripts/stamp-image-versions.mjs && pagefind --site dist && node scripts/stamp-pagefind-runtime-version.mjs && node scripts/generate-proxy-allowed-origins.mjs && node scripts/generate-robots-txt.mjs",
|
|
10
11
|
"preview": "astro preview",
|
|
@@ -77,14 +77,14 @@ const assistantHeaderButtonStyle = assistantHeaderThemeColors
|
|
|
77
77
|
? [
|
|
78
78
|
`--assistant-header-theme-light: ${assistantHeaderThemeColors.light}`,
|
|
79
79
|
`--assistant-header-theme-dark: ${assistantHeaderThemeColors.dark}`,
|
|
80
|
-
`--assistant-header-foreground-light: ${
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}`,
|
|
84
|
-
`--assistant-header-foreground-dark: ${
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}`,
|
|
80
|
+
`--assistant-header-foreground-light: ${getButtonForegroundColor(
|
|
81
|
+
"light",
|
|
82
|
+
assistantHeaderThemeColors.light,
|
|
83
|
+
)}`,
|
|
84
|
+
`--assistant-header-foreground-dark: ${getButtonForegroundColor(
|
|
85
|
+
"dark",
|
|
86
|
+
assistantHeaderThemeColors.dark,
|
|
87
|
+
)}`,
|
|
88
88
|
].join("; ")
|
|
89
89
|
: "";
|
|
90
90
|
|
|
@@ -22,7 +22,7 @@ const currentPrefix = parentSlug ? `${parentSlug}/${groupSlug}` : groupSlug;
|
|
|
22
22
|
<div class:list={["text-sm font-semibold mb-2 flex items-center gap-2 px-2"]}>
|
|
23
23
|
{item.icon && <Icon name={item.icon} class="size-4 text-neutral-500" />}
|
|
24
24
|
{item.group}
|
|
25
|
-
{item.tag && <Tag
|
|
25
|
+
{item.tag && <Tag tag={item.tag} />}
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
28
|
<ul>
|
|
@@ -3,13 +3,13 @@ import { buildMdxPageHref, deriveTitleFromEntryId } from "../lib/utils";
|
|
|
3
3
|
import Icon from "./ui/Icon.astro";
|
|
4
4
|
import Tag from "./ui/Tag.astro";
|
|
5
5
|
import { getCollection } from "astro:content";
|
|
6
|
-
import { getConfig } from "../lib/validation";
|
|
6
|
+
import { getConfig, type NavTag } from "../lib/validation";
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
path: string;
|
|
10
10
|
groupSlug?: string;
|
|
11
11
|
icon?: string | null;
|
|
12
|
-
tag?:
|
|
12
|
+
tag?: NavTag;
|
|
13
13
|
title?: string;
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -61,6 +61,6 @@ const isActive = currentPath === targetPath;
|
|
|
61
61
|
<div class="flex items-center gap-2">
|
|
62
62
|
{icon && <Icon name={icon} class="size-4 opacity-75" />}
|
|
63
63
|
{text}
|
|
64
|
-
{tag && <Tag
|
|
64
|
+
{tag && <Tag tag={tag} />}
|
|
65
65
|
</div>
|
|
66
66
|
</a>
|
|
@@ -82,7 +82,7 @@ const containsActivePage = item.pages.some((child) => {
|
|
|
82
82
|
<div class="flex items-center gap-2">
|
|
83
83
|
{item.icon && <Icon name={item.icon} class="size-4 opacity-75" />}
|
|
84
84
|
{item.group}
|
|
85
|
-
{item.tag && <Tag
|
|
85
|
+
{item.tag && <Tag tag={item.tag} />}
|
|
86
86
|
</div>
|
|
87
87
|
<svg
|
|
88
88
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import Tag from "../ui/Tag.astro";
|
|
3
3
|
import { buildOpenApiEndpointHref } from "../../lib/utils";
|
|
4
4
|
import { methodColors } from "../../lib/utils";
|
|
5
|
+
import type { NavTag } from "../../lib/validation";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
8
|
method: string;
|
|
8
9
|
path: string;
|
|
9
10
|
summary?: string;
|
|
10
11
|
title?: string;
|
|
11
|
-
tag?:
|
|
12
|
+
tag?: NavTag;
|
|
12
13
|
parentSlug?: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -49,6 +50,6 @@ const isActive = currentPath === targetPath;
|
|
|
49
50
|
</span>
|
|
50
51
|
<div class="flex items-center gap-2 min-w-0">
|
|
51
52
|
<span>{text}</span>
|
|
52
|
-
{tag && <Tag
|
|
53
|
+
{tag && <Tag tag={tag} />}
|
|
53
54
|
</div>
|
|
54
55
|
</a>
|
|
@@ -1,5 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
getConfig,
|
|
4
|
+
type NavTag,
|
|
5
|
+
type ThemeColorByMode,
|
|
6
|
+
} from "../../lib/validation";
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
tag?: NavTag;
|
|
10
|
+
color?: string | ThemeColorByMode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { tag, color } = Astro.props;
|
|
14
|
+
const config = await getConfig();
|
|
15
|
+
|
|
16
|
+
function getTagText(value: NavTag | undefined): string | undefined {
|
|
17
|
+
if (typeof value === "string") return value;
|
|
18
|
+
return value?.text;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getTagColor(
|
|
22
|
+
value: NavTag | undefined,
|
|
23
|
+
): string | ThemeColorByMode | undefined {
|
|
24
|
+
if (typeof value === "object" && value !== null) {
|
|
25
|
+
return value.color;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function resolveColorByMode(
|
|
31
|
+
value: string | ThemeColorByMode | undefined,
|
|
32
|
+
): { light: string; dark: string } | undefined {
|
|
33
|
+
if (typeof value === "string") {
|
|
34
|
+
return { light: value, dark: value };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const light = value?.light ?? value?.dark;
|
|
38
|
+
const dark = value?.dark ?? value?.light;
|
|
39
|
+
if (!light || !dark) return undefined;
|
|
40
|
+
|
|
41
|
+
return { light, dark };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const text = getTagText(tag);
|
|
45
|
+
const configuredColor =
|
|
46
|
+
color ?? getTagColor(tag) ?? config.theme?.tag?.color ?? undefined;
|
|
47
|
+
const resolvedColor = resolveColorByMode(configuredColor);
|
|
48
|
+
const colorStyle = resolvedColor
|
|
49
|
+
? `--rd-tag-color-light:${resolvedColor.light};--rd-tag-color-dark:${resolvedColor.dark};`
|
|
50
|
+
: undefined;
|
|
51
|
+
---
|
|
52
|
+
|
|
1
53
|
<span
|
|
2
|
-
class="text-[9px]
|
|
54
|
+
class="rd-tag text-[9px] border px-[5px] py-[2px] rounded-full tracking-wide leading-none font-medium shrink-0"
|
|
55
|
+
style={colorStyle}
|
|
3
56
|
>
|
|
4
|
-
<slot />
|
|
57
|
+
{text ? text : <slot />}
|
|
5
58
|
</span>
|
|
59
|
+
|
|
60
|
+
<style>
|
|
61
|
+
.rd-tag {
|
|
62
|
+
--rd-tag-color: var(--rd-tag-color-light, var(--color-theme));
|
|
63
|
+
background-color: color-mix(in oklab, var(--rd-tag-color) 8%, transparent);
|
|
64
|
+
border-color: color-mix(in oklab, var(--rd-tag-color) 0%, transparent);
|
|
65
|
+
color: color-mix(in oklab, var(--rd-tag-color) 95%, transparent);
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
@@ -98,7 +98,7 @@ const isInitiallyExpanded = shouldShowAllCode || totalLineCount <= visibleLines;
|
|
|
98
98
|
>
|
|
99
99
|
<button
|
|
100
100
|
type="button"
|
|
101
|
-
class="pointer-events-auto inline-flex h-8 items-center justify-center rounded-xl [corner-shape:superellipse(1.2)] border-[0.5px] border-neutral-900/10 dark:border-white/8 bg-linear-to-br from-white via-white/10 to-neutral-900/5 dark:from-white/7 dark:via-white/6 dark:to-white/2 px-3 text-sm font-medium text-neutral-600 hover:text-neutral-900 dark:text-neutral-300/90 hover:dark:text-neutral-200 transition-colors duration-200 hover:bg-neutral-50/80 cursor-pointer dark:hover:bg-neutral-700/30 shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-4px_rgba(0,0,0,0.08)] dark:shadow-[0_-0.5px_0px_rgba(255,255,255,0.15),0_5px_12px_-4px_rgba(0,0,0,0.
|
|
101
|
+
class="pointer-events-auto inline-flex h-8 items-center justify-center rounded-xl [corner-shape:superellipse(1.2)] border-[0.5px] border-neutral-900/10 dark:border-white/8 bg-linear-to-br from-white via-white/10 to-neutral-900/5 dark:from-white/7 dark:via-white/6 dark:to-white/2 px-3 text-sm font-medium text-neutral-600 hover:text-neutral-900 dark:text-neutral-300/90 hover:dark:text-neutral-200 transition-colors duration-200 hover:bg-neutral-50/80 cursor-pointer dark:hover:bg-neutral-700/30 shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-4px_rgba(0,0,0,0.08)] dark:shadow-[0_-0.5px_0px_rgba(255,255,255,0.15),0_5px_12px_-4px_rgba(0,0,0,0.18)]"
|
|
102
102
|
data-rd-preview-expand
|
|
103
103
|
>
|
|
104
104
|
View code
|
|
@@ -114,7 +114,23 @@ function isConstrainedWidthValue(value: unknown): boolean {
|
|
|
114
114
|
return true;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
function toCssWidthValue(value: unknown): string | undefined {
|
|
118
|
+
if (typeof value === "number") {
|
|
119
|
+
if (!Number.isFinite(value) || value <= 0) return undefined;
|
|
120
|
+
return `${value}px`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof value !== "string") return undefined;
|
|
124
|
+
const normalized = value.trim();
|
|
125
|
+
if (!normalized) return undefined;
|
|
126
|
+
|
|
127
|
+
return /^-?\d*\.?\d+$/.test(normalized) ? `${normalized}px` : normalized;
|
|
128
|
+
}
|
|
129
|
+
|
|
117
130
|
const hasCustomImageWidth = isConstrainedWidthValue(rawWidth);
|
|
131
|
+
const contentWidthStyle = hasCustomImageWidth
|
|
132
|
+
? `width: ${toCssWidthValue(rawWidth) ?? "auto"};`
|
|
133
|
+
: undefined;
|
|
118
134
|
|
|
119
135
|
const slotCaptionHtml = Astro.slots.has("default")
|
|
120
136
|
? (await Astro.slots.render("default")).trim()
|
|
@@ -240,45 +256,46 @@ const hasCaption = slotCaptionHtml.length > 0;
|
|
|
240
256
|
}"
|
|
241
257
|
>
|
|
242
258
|
<div
|
|
243
|
-
class="
|
|
259
|
+
class:list={["max-w-full", !hasCustomImageWidth && "w-full"]}
|
|
260
|
+
style={contentWidthStyle}
|
|
244
261
|
>
|
|
245
|
-
<
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
262
|
+
<div class="overflow-hidden rounded-[11px]">
|
|
263
|
+
<img
|
|
264
|
+
{...lightImageAttrs}
|
|
265
|
+
x-ref="lightImg"
|
|
266
|
+
title={title}
|
|
267
|
+
class:list={[
|
|
268
|
+
"h-auto my-0! block w-full transition-opacity",
|
|
269
|
+
hasDarkSource && "dark:hidden",
|
|
270
|
+
zoomEnabled && "cursor-zoom-in",
|
|
271
|
+
]}
|
|
272
|
+
:class="showZoomed ? 'opacity-0 duration-0' : 'opacity-100 duration-300'"
|
|
273
|
+
@click={zoomEnabled ? "zoom()" : undefined}
|
|
274
|
+
/>
|
|
275
|
+
{
|
|
276
|
+
darkImageAttrs && (
|
|
277
|
+
<img
|
|
278
|
+
{...darkImageAttrs}
|
|
279
|
+
x-ref="darkImg"
|
|
280
|
+
title={title}
|
|
281
|
+
class:list={[
|
|
282
|
+
"h-auto my-0! hidden w-full transition-opacity dark:block",
|
|
283
|
+
zoomEnabled && "cursor-zoom-in",
|
|
284
|
+
]}
|
|
285
|
+
:class="showZoomed ? 'opacity-0 duration-0' : 'opacity-100 duration-300'"
|
|
286
|
+
@click={zoomEnabled ? "zoom()" : undefined}
|
|
287
|
+
/>
|
|
288
|
+
)
|
|
289
|
+
}
|
|
290
|
+
</div>
|
|
258
291
|
{
|
|
259
|
-
|
|
260
|
-
<
|
|
261
|
-
{
|
|
262
|
-
|
|
263
|
-
title={title}
|
|
264
|
-
class:list={[
|
|
265
|
-
"h-auto my-0! hidden transition-opacity dark:block",
|
|
266
|
-
zoomEnabled && "cursor-zoom-in",
|
|
267
|
-
hasCustomImageWidth ? "max-w-full" : "w-full",
|
|
268
|
-
]}
|
|
269
|
-
:class="showZoomed ? 'opacity-0 duration-0' : 'opacity-100 duration-300'"
|
|
270
|
-
@click={zoomEnabled ? "zoom()" : undefined}
|
|
271
|
-
/>
|
|
292
|
+
hasCaption && (
|
|
293
|
+
<figcaption class="prose-rules mt-1! xs:mt-1.5! max-w-none! *:max-w-none! text-center text-xs! xs:text-sm! text-neutral-500 dark:text-neutral-400 leading-relaxed px-2">
|
|
294
|
+
<Fragment set:html={slotCaptionHtml} />
|
|
295
|
+
</figcaption>
|
|
272
296
|
)
|
|
273
297
|
}
|
|
274
298
|
</div>
|
|
275
|
-
{
|
|
276
|
-
hasCaption && (
|
|
277
|
-
<figcaption class="prose-rules mt-1! xs:mt-1.5! max-w-none! *:max-w-none! text-center text-xs! xs:text-sm! text-neutral-500 dark:text-neutral-400 leading-relaxed px-2">
|
|
278
|
-
<Fragment set:html={slotCaptionHtml} />
|
|
279
|
-
</figcaption>
|
|
280
|
-
)
|
|
281
|
-
}
|
|
282
299
|
|
|
283
300
|
{
|
|
284
301
|
zoomEnabled && (
|
|
@@ -153,18 +153,14 @@ export function getAssistantLauncherIconConfig(
|
|
|
153
153
|
dark: getAssistantButtonColor(config, "dark"),
|
|
154
154
|
};
|
|
155
155
|
const iconColors = {
|
|
156
|
-
light:
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
getThemeForegroundColor(
|
|
165
|
-
themeColors.dark,
|
|
166
|
-
getDocsBaseColorShade(config.theme, "dark", "900"),
|
|
167
|
-
),
|
|
156
|
+
light: getThemeForegroundColor(
|
|
157
|
+
themeColors.light,
|
|
158
|
+
getDocsBaseColorShade(config.theme, "light", "900"),
|
|
159
|
+
),
|
|
160
|
+
dark: getThemeForegroundColor(
|
|
161
|
+
themeColors.dark,
|
|
162
|
+
getDocsBaseColorShade(config.theme, "dark", "900"),
|
|
163
|
+
),
|
|
168
164
|
};
|
|
169
165
|
const icon = resolveLauncherIcon(assistantConfig?.icon?.src);
|
|
170
166
|
|
|
@@ -297,9 +293,240 @@ export function renderAssistantEmbedScript(config: DocsConfig): string {
|
|
|
297
293
|
});
|
|
298
294
|
}
|
|
299
295
|
|
|
296
|
+
function normalizePathForMatching(value) {
|
|
297
|
+
var pathname = String(value || "").trim();
|
|
298
|
+
if (!pathname) return "";
|
|
299
|
+
if (pathname === "*") return "*";
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
if (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(pathname)) {
|
|
303
|
+
pathname = new URL(pathname).pathname;
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
// Fall back to treating the value as a path pattern.
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
var hashIndex = pathname.indexOf("#");
|
|
310
|
+
if (hashIndex >= 0) {
|
|
311
|
+
pathname = pathname.slice(0, hashIndex);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
var queryIndex = pathname.indexOf("?");
|
|
315
|
+
if (queryIndex >= 0) {
|
|
316
|
+
pathname = pathname.slice(0, queryIndex);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (!pathname) return "";
|
|
320
|
+
if (pathname.charAt(0) !== "/") {
|
|
321
|
+
pathname = "/" + pathname;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
pathname = pathname.replace(/\\/{2,}/g, "/");
|
|
325
|
+
if (pathname.length > 1 && !pathname.endsWith("/*")) {
|
|
326
|
+
pathname = pathname.replace(/\\/+$/, "");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return pathname || "/";
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function parsePathPatterns(value) {
|
|
333
|
+
var rawPatterns = [];
|
|
334
|
+
|
|
335
|
+
if (Array.isArray(value)) {
|
|
336
|
+
rawPatterns = value;
|
|
337
|
+
} else if (typeof value === "string") {
|
|
338
|
+
var trimmed = value.trim();
|
|
339
|
+
if (!trimmed) return [];
|
|
340
|
+
|
|
341
|
+
if (trimmed.charAt(0) === "[") {
|
|
342
|
+
try {
|
|
343
|
+
var parsed = JSON.parse(trimmed);
|
|
344
|
+
if (Array.isArray(parsed)) {
|
|
345
|
+
rawPatterns = parsed;
|
|
346
|
+
} else {
|
|
347
|
+
rawPatterns = [trimmed];
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
rawPatterns = trimmed.split(/[\\n,]/);
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
rawPatterns = trimmed.split(/[\\n,]/);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
var seenPatterns = {};
|
|
358
|
+
return rawPatterns
|
|
359
|
+
.map(normalizePathForMatching)
|
|
360
|
+
.filter(function (pattern) {
|
|
361
|
+
if (!pattern || seenPatterns[pattern]) return false;
|
|
362
|
+
seenPatterns[pattern] = true;
|
|
363
|
+
return true;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function escapeRegExp(value) {
|
|
368
|
+
return String(value).replace(/[|\\\\{}()[\\]^$+?.]/g, "\\\\$&");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function pathMatchesPattern(pathname, pattern) {
|
|
372
|
+
if (pattern === "*") return true;
|
|
373
|
+
|
|
374
|
+
if (pattern.endsWith("/*")) {
|
|
375
|
+
var basePath = normalizePathForMatching(pattern.slice(0, -2));
|
|
376
|
+
return pathname === basePath || pathname.startsWith(basePath + "/");
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (pattern.indexOf("*") >= 0) {
|
|
380
|
+
var patternRegex = new RegExp(
|
|
381
|
+
"^" + pattern.split("*").map(escapeRegExp).join(".*") + "$",
|
|
382
|
+
);
|
|
383
|
+
return patternRegex.test(pathname);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return pathname === pattern;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function isCurrentPathExcluded() {
|
|
390
|
+
if (!excludedPathPatterns.length) return false;
|
|
391
|
+
var pathname = normalizePathForMatching(window.location.pathname || "/");
|
|
392
|
+
return excludedPathPatterns.some(function (pattern) {
|
|
393
|
+
return pathMatchesPattern(pathname, pattern);
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function parseDurationMs(value, fallbackMs) {
|
|
398
|
+
var rawValue = String(value || "").trim();
|
|
399
|
+
var match = rawValue.match(/^([0-9]*\\.?[0-9]+)(ms|s)$/);
|
|
400
|
+
if (!match) return fallbackMs;
|
|
401
|
+
|
|
402
|
+
var amount = Number.parseFloat(match[1]);
|
|
403
|
+
if (!Number.isFinite(amount)) return fallbackMs;
|
|
404
|
+
|
|
405
|
+
return match[2] === "s" ? amount * 1000 : amount;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function clearRouteVisibilityRemovalTimeout() {
|
|
409
|
+
if (!routeVisibilityRemovalTimeout) return;
|
|
410
|
+
window.clearTimeout(routeVisibilityRemovalTimeout);
|
|
411
|
+
routeVisibilityRemovalTimeout = undefined;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function unmountEmbedElements() {
|
|
415
|
+
clearRouteVisibilityRemovalTimeout();
|
|
416
|
+
setOpen(false);
|
|
417
|
+
|
|
418
|
+
if (launcherButton) {
|
|
419
|
+
launcherButton.remove();
|
|
420
|
+
launcherButton = undefined;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (panelShell) {
|
|
424
|
+
panelShell.remove();
|
|
425
|
+
panelShell = undefined;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
panelFrame = undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function hideEmbedElementsForRouteExclusion() {
|
|
432
|
+
if (!panelShell && !launcherButton) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
clearRouteVisibilityRemovalTimeout();
|
|
437
|
+
setOpen(false);
|
|
438
|
+
|
|
439
|
+
if (launcherButton) {
|
|
440
|
+
launcherButton.dataset.routeHidden = "true";
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
routeVisibilityRemovalTimeout = window.setTimeout(function () {
|
|
444
|
+
routeVisibilityRemovalTimeout = undefined;
|
|
445
|
+
if (isCurrentPathExcluded()) {
|
|
446
|
+
unmountEmbedElements();
|
|
447
|
+
}
|
|
448
|
+
}, routeVisibilityRemovalDelayMs);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function applyRouteVisibility() {
|
|
452
|
+
if (isCurrentPathExcluded()) {
|
|
453
|
+
hideEmbedElementsForRouteExclusion();
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
mount();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function scheduleRouteVisibilityCheck() {
|
|
461
|
+
window.setTimeout(applyRouteVisibility, 0);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function installRouteVisibilityListeners() {
|
|
465
|
+
if (!excludedPathPatterns.length || routeVisibilityListenersInstalled) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
routeVisibilityListenersInstalled = true;
|
|
470
|
+
|
|
471
|
+
if (window.history && typeof window.history.pushState === "function") {
|
|
472
|
+
originalHistoryPushState = window.history.pushState;
|
|
473
|
+
patchedHistoryPushState = function () {
|
|
474
|
+
var result = originalHistoryPushState.apply(this, arguments);
|
|
475
|
+
scheduleRouteVisibilityCheck();
|
|
476
|
+
return result;
|
|
477
|
+
};
|
|
478
|
+
window.history.pushState = patchedHistoryPushState;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (window.history && typeof window.history.replaceState === "function") {
|
|
482
|
+
originalHistoryReplaceState = window.history.replaceState;
|
|
483
|
+
patchedHistoryReplaceState = function () {
|
|
484
|
+
var result = originalHistoryReplaceState.apply(this, arguments);
|
|
485
|
+
scheduleRouteVisibilityCheck();
|
|
486
|
+
return result;
|
|
487
|
+
};
|
|
488
|
+
window.history.replaceState = patchedHistoryReplaceState;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
window.addEventListener("popstate", scheduleRouteVisibilityCheck);
|
|
492
|
+
window.addEventListener("hashchange", scheduleRouteVisibilityCheck);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function restoreRouteVisibilityListeners() {
|
|
496
|
+
if (!routeVisibilityListenersInstalled) {
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (
|
|
501
|
+
window.history &&
|
|
502
|
+
patchedHistoryPushState &&
|
|
503
|
+
window.history.pushState === patchedHistoryPushState
|
|
504
|
+
) {
|
|
505
|
+
window.history.pushState = originalHistoryPushState;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (
|
|
509
|
+
window.history &&
|
|
510
|
+
patchedHistoryReplaceState &&
|
|
511
|
+
window.history.replaceState === patchedHistoryReplaceState
|
|
512
|
+
) {
|
|
513
|
+
window.history.replaceState = originalHistoryReplaceState;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
window.removeEventListener("popstate", scheduleRouteVisibilityCheck);
|
|
517
|
+
window.removeEventListener("hashchange", scheduleRouteVisibilityCheck);
|
|
518
|
+
routeVisibilityListenersInstalled = false;
|
|
519
|
+
}
|
|
520
|
+
|
|
300
521
|
var docsOrigin = resolveDocsOrigin();
|
|
301
522
|
var docsBasePath = resolveDocsBasePath();
|
|
302
523
|
var chrome = generatedConfig.chrome || {};
|
|
524
|
+
var excludedPathPatterns = parsePathPatterns(
|
|
525
|
+
scriptDataset.excludePaths ||
|
|
526
|
+
globalConfig.excludePaths ||
|
|
527
|
+
globalConfig.excludedPaths ||
|
|
528
|
+
"",
|
|
529
|
+
);
|
|
303
530
|
var zIndex = scriptDataset.zIndex || chrome.zIndex || "2147483000";
|
|
304
531
|
var configuredMode =
|
|
305
532
|
scriptDataset.mode ||
|
|
@@ -339,6 +566,7 @@ export function renderAssistantEmbedScript(config: DocsConfig): string {
|
|
|
339
566
|
var panelSizeTiming = chrome.panelSizeTiming || "cubic-bezier(.22,1.18,.36,1)";
|
|
340
567
|
var panelCloseDuration = chrome.panelCloseDuration || ".2s";
|
|
341
568
|
var panelCloseTiming = chrome.panelCloseTiming || "cubic-bezier(.25,1,.5,1)";
|
|
569
|
+
var launcherRouteExitDurationMs = 460;
|
|
342
570
|
var mobileBreakpoint = chrome.mobileBreakpoint || "640px";
|
|
343
571
|
var panelFullscreenHeightBreakpoint =
|
|
344
572
|
chrome.panelFullscreenHeightBreakpoint || "560px";
|
|
@@ -367,6 +595,17 @@ export function renderAssistantEmbedScript(config: DocsConfig): string {
|
|
|
367
595
|
var panelSize = readPanelSize();
|
|
368
596
|
var panelSizeTransitionTimeout;
|
|
369
597
|
var isPanelFullscreen = false;
|
|
598
|
+
var routeVisibilityRemovalTimeout;
|
|
599
|
+
var routeVisibilityRemovalDelayMs =
|
|
600
|
+
Math.max(
|
|
601
|
+
parseDurationMs(panelCloseDuration, 200),
|
|
602
|
+
launcherRouteExitDurationMs,
|
|
603
|
+
) + 80;
|
|
604
|
+
var routeVisibilityListenersInstalled = false;
|
|
605
|
+
var originalHistoryPushState;
|
|
606
|
+
var originalHistoryReplaceState;
|
|
607
|
+
var patchedHistoryPushState;
|
|
608
|
+
var patchedHistoryReplaceState;
|
|
370
609
|
var systemThemeMediaQuery =
|
|
371
610
|
typeof window.matchMedia === "function"
|
|
372
611
|
? window.matchMedia("(prefers-color-scheme: dark)")
|
|
@@ -508,9 +747,11 @@ export function renderAssistantEmbedScript(config: DocsConfig): string {
|
|
|
508
747
|
style.textContent = [
|
|
509
748
|
".assistant-embed-frame{display:block;width:100%;height:100%;border:0;background:transparent;color-scheme:normal;}",
|
|
510
749
|
"@keyframes assistant-embed-launcher-enter{0%{opacity:0;transform:translateY(3px) scale(.96);}46%{opacity:1;transform:translateY(0) scale(1.012);}72%{opacity:1;transform:translateY(0) scale(.998);}100%{opacity:1;transform:translateY(0) scale(1);}}",
|
|
750
|
+
"@keyframes assistant-embed-launcher-exit{0%{opacity:1;transform:translateY(0) scale(1);}28%{opacity:1;transform:translateY(0) scale(.998);}54%{opacity:1;transform:translateY(0) scale(1.012);}100%{opacity:0;transform:translateY(3px) scale(.96);}}",
|
|
511
751
|
".assistant-embed-launcher{position:fixed;display:inline-flex;align-items:center;justify-content:center;border:0;padding:0;cursor:pointer;color:var(--assistant-embed-icon-color);background:linear-gradient(to bottom,color-mix(in oklab,var(--assistant-embed-theme) 88%,white),color-mix(in oklab,var(--assistant-embed-theme) 90%,black));animation:assistant-embed-launcher-enter 460ms linear backwards;transition:transform 180ms ease,opacity 160ms ease;transform-origin:center center;}",
|
|
512
752
|
".assistant-embed-launcher:hover{opacity:.96;transform:translateY(-1px) scale(1.03);}",
|
|
513
753
|
".assistant-embed-launcher:active{transform:translateY(0) scale(.96);}",
|
|
754
|
+
".assistant-embed-launcher[data-route-hidden='true']{pointer-events:none;animation:assistant-embed-launcher-exit 460ms linear forwards;}",
|
|
514
755
|
".assistant-embed-launcher{width:" +
|
|
515
756
|
launcherSize +
|
|
516
757
|
";height:" +
|
|
@@ -883,6 +1124,10 @@ export function renderAssistantEmbedScript(config: DocsConfig): string {
|
|
|
883
1124
|
}
|
|
884
1125
|
|
|
885
1126
|
function setOpen(nextOpen) {
|
|
1127
|
+
if (nextOpen === true && isCurrentPathExcluded()) {
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
886
1131
|
var wasOpen = isOpen;
|
|
887
1132
|
isOpen = nextOpen === true;
|
|
888
1133
|
setPanelOpen(isOpen);
|
|
@@ -972,6 +1217,25 @@ export function renderAssistantEmbedScript(config: DocsConfig): string {
|
|
|
972
1217
|
return;
|
|
973
1218
|
}
|
|
974
1219
|
|
|
1220
|
+
if (isCurrentPathExcluded()) {
|
|
1221
|
+
hideEmbedElementsForRouteExclusion();
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
clearRouteVisibilityRemovalTimeout();
|
|
1226
|
+
|
|
1227
|
+
if (launcherButton) {
|
|
1228
|
+
delete launcherButton.dataset.routeHidden;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
if (panelShell && launcherButton) {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (panelShell || launcherButton) {
|
|
1236
|
+
unmountEmbedElements();
|
|
1237
|
+
}
|
|
1238
|
+
|
|
975
1239
|
ensureStyle();
|
|
976
1240
|
updatePanelLayout();
|
|
977
1241
|
|
|
@@ -1063,16 +1327,12 @@ export function renderAssistantEmbedScript(config: DocsConfig): string {
|
|
|
1063
1327
|
},
|
|
1064
1328
|
destroy: function () {
|
|
1065
1329
|
setSystemThemeListener(false);
|
|
1330
|
+
restoreRouteVisibilityListeners();
|
|
1066
1331
|
window.removeEventListener("resize", updatePanelLayout);
|
|
1067
1332
|
if (panelSizeTransitionTimeout) {
|
|
1068
1333
|
window.clearTimeout(panelSizeTransitionTimeout);
|
|
1069
1334
|
}
|
|
1070
|
-
|
|
1071
|
-
launcherButton.remove();
|
|
1072
|
-
}
|
|
1073
|
-
if (panelShell) {
|
|
1074
|
-
panelShell.remove();
|
|
1075
|
-
}
|
|
1335
|
+
unmountEmbedElements();
|
|
1076
1336
|
var style = document.getElementById(styleId);
|
|
1077
1337
|
if (style) {
|
|
1078
1338
|
style.remove();
|
|
@@ -1082,6 +1342,7 @@ export function renderAssistantEmbedScript(config: DocsConfig): string {
|
|
|
1082
1342
|
};
|
|
1083
1343
|
|
|
1084
1344
|
setSystemThemeListener(themeMode === "system");
|
|
1345
|
+
installRouteVisibilityListeners();
|
|
1085
1346
|
mount();
|
|
1086
1347
|
})();
|
|
1087
1348
|
`;
|
|
@@ -131,7 +131,7 @@ const INTERNAL_ONLY_COMPONENTS = new Set(["ComponentPreview"]);
|
|
|
131
131
|
export type NavPage = {
|
|
132
132
|
page: string;
|
|
133
133
|
icon?: string | null;
|
|
134
|
-
tag?:
|
|
134
|
+
tag?: NavTag;
|
|
135
135
|
title?: string;
|
|
136
136
|
};
|
|
137
137
|
export type NavOpenApiPageRef = {
|
|
@@ -141,14 +141,14 @@ export type NavOpenApiPageRef = {
|
|
|
141
141
|
export type NavOpenApiPage = {
|
|
142
142
|
openapi: NavOpenApiPageRef;
|
|
143
143
|
title?: string;
|
|
144
|
-
tag?:
|
|
144
|
+
tag?: NavTag;
|
|
145
145
|
};
|
|
146
146
|
export type NavGroup = {
|
|
147
147
|
group: string;
|
|
148
148
|
pages: (string | NavPage | NavGroup | NavOpenApiPage)[];
|
|
149
149
|
icon?: string | null;
|
|
150
150
|
expanded?: boolean; // need to add this logic
|
|
151
|
-
tag?:
|
|
151
|
+
tag?: NavTag;
|
|
152
152
|
};
|
|
153
153
|
export type NavOpenApi = {
|
|
154
154
|
source: string;
|
|
@@ -199,6 +199,16 @@ export type Logo = {
|
|
|
199
199
|
href?: string;
|
|
200
200
|
pill?: string | false;
|
|
201
201
|
};
|
|
202
|
+
export type ThemeColorByMode = {
|
|
203
|
+
light?: string;
|
|
204
|
+
dark?: string;
|
|
205
|
+
};
|
|
206
|
+
export type NavTag =
|
|
207
|
+
| string
|
|
208
|
+
| {
|
|
209
|
+
text: string;
|
|
210
|
+
color?: string | ThemeColorByMode;
|
|
211
|
+
};
|
|
202
212
|
export const BASE_COLOR_OPTIONS = [
|
|
203
213
|
"slate",
|
|
204
214
|
"gray",
|
|
@@ -217,10 +227,6 @@ export type BaseColorByMode = {
|
|
|
217
227
|
};
|
|
218
228
|
export const DEFAULT_THEME_COLOR_LIGHT = "#171717";
|
|
219
229
|
export const DEFAULT_THEME_COLOR_DARK = "#f5f5f5";
|
|
220
|
-
export type ThemeColorByMode = {
|
|
221
|
-
light?: string;
|
|
222
|
-
dark?: string;
|
|
223
|
-
};
|
|
224
230
|
export type CardCoverTheme = {
|
|
225
231
|
colors?: string[];
|
|
226
232
|
colorSeed?: string;
|
|
@@ -241,15 +247,18 @@ export type CodeSyntaxThemeConfig =
|
|
|
241
247
|
export type CodeTheme = {
|
|
242
248
|
syntaxTheme?: CodeSyntaxThemeConfig;
|
|
243
249
|
};
|
|
250
|
+
export type TagTheme = {
|
|
251
|
+
color?: string | ThemeColorByMode;
|
|
252
|
+
};
|
|
244
253
|
export type DocsTheme = {
|
|
245
254
|
baseColor?: BaseColorOption | BaseColorByMode;
|
|
246
255
|
themeColor?: string | ThemeColorByMode;
|
|
247
256
|
card?: CardTheme;
|
|
248
257
|
code?: CodeTheme;
|
|
258
|
+
tag?: TagTheme;
|
|
249
259
|
};
|
|
250
260
|
export type AssistantIcon = {
|
|
251
261
|
src?: string;
|
|
252
|
-
color?: string;
|
|
253
262
|
};
|
|
254
263
|
export type AssistantButtonSize = "small" | "default";
|
|
255
264
|
export type AssistantButtonConfig = {
|
|
@@ -428,6 +437,71 @@ function normalizeThemeColorConfig(
|
|
|
428
437
|
};
|
|
429
438
|
}
|
|
430
439
|
|
|
440
|
+
function normalizeNavTagConfig(
|
|
441
|
+
value: unknown,
|
|
442
|
+
currentPath: Path,
|
|
443
|
+
label: string,
|
|
444
|
+
): NavTag | undefined {
|
|
445
|
+
if (value === undefined || value === null) return undefined;
|
|
446
|
+
|
|
447
|
+
if (typeof value === "string") {
|
|
448
|
+
const trimmedText = value.trim();
|
|
449
|
+
if (trimmedText.length === 0) {
|
|
450
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
451
|
+
}
|
|
452
|
+
return trimmedText;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
checkType(value, "object", currentPath, label);
|
|
456
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
457
|
+
throwConfigError(
|
|
458
|
+
`${label} must be a string or an object with text and optional color.`,
|
|
459
|
+
currentPath,
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const tagConfig = value as Record<string, unknown>;
|
|
464
|
+
const allowedKeys = new Set(["text", "color"]);
|
|
465
|
+
for (const key of Object.keys(tagConfig)) {
|
|
466
|
+
if (!allowedKeys.has(key)) {
|
|
467
|
+
throwConfigError(`${label} object only supports 'text' and 'color'.`, [
|
|
468
|
+
...currentPath,
|
|
469
|
+
key,
|
|
470
|
+
]);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
checkType(tagConfig.text, "string", [...currentPath, "text"], `${label} text`);
|
|
475
|
+
if (typeof tagConfig.text !== "string") {
|
|
476
|
+
throwConfigError(`${label} text must be a string.`, [
|
|
477
|
+
...currentPath,
|
|
478
|
+
"text",
|
|
479
|
+
]);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const trimmedText = tagConfig.text.trim();
|
|
483
|
+
if (trimmedText.length === 0) {
|
|
484
|
+
throwConfigError(`${label} text cannot be empty.`, [
|
|
485
|
+
...currentPath,
|
|
486
|
+
"text",
|
|
487
|
+
]);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const color =
|
|
491
|
+
tagConfig.color !== undefined
|
|
492
|
+
? normalizeThemeColorConfig(
|
|
493
|
+
tagConfig.color,
|
|
494
|
+
[...currentPath, "color"],
|
|
495
|
+
`${label} color`,
|
|
496
|
+
)
|
|
497
|
+
: undefined;
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
text: trimmedText,
|
|
501
|
+
...(color !== undefined ? { color } : {}),
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
431
505
|
function normalizeHexColorArray(
|
|
432
506
|
value: unknown,
|
|
433
507
|
currentPath: Path,
|
|
@@ -909,6 +983,7 @@ async function validateNavigationNode(
|
|
|
909
983
|
checkType(item.expanded, "boolean", [...path, "expanded"], "Expanded");
|
|
910
984
|
|
|
911
985
|
validateIcon(item.icon, [...path, "icon"]);
|
|
986
|
+
item.tag = normalizeNavTagConfig(item.tag, [...path, "tag"], "Group tag");
|
|
912
987
|
|
|
913
988
|
// Check if pages array exists and validate children
|
|
914
989
|
if (!item.pages)
|
|
@@ -947,6 +1022,7 @@ async function validateNavigationNode(
|
|
|
947
1022
|
|
|
948
1023
|
// Validate optional title
|
|
949
1024
|
checkType(item.title, "string", [...path, "title"], "Page title");
|
|
1025
|
+
item.tag = normalizeNavTagConfig(item.tag, [...path, "tag"], "Page tag");
|
|
950
1026
|
|
|
951
1027
|
// Check D.2/D.3: Page cannot have group properties
|
|
952
1028
|
if ("expanded" in item)
|
|
@@ -971,7 +1047,11 @@ async function validateNavigationNode(
|
|
|
971
1047
|
|
|
972
1048
|
await validateNavOpenApiPage(item.openapi, [...path, "openapi"]);
|
|
973
1049
|
checkType(item.title, "string", [...path, "title"], "Open API page title");
|
|
974
|
-
|
|
1050
|
+
item.tag = normalizeNavTagConfig(
|
|
1051
|
+
item.tag,
|
|
1052
|
+
[...path, "tag"],
|
|
1053
|
+
"Open API page tag",
|
|
1054
|
+
);
|
|
975
1055
|
|
|
976
1056
|
if ("expanded" in item)
|
|
977
1057
|
throwConfigError("Open API page items cannot have 'expanded'.", [
|
|
@@ -1855,6 +1935,37 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1855
1935
|
}
|
|
1856
1936
|
}
|
|
1857
1937
|
|
|
1938
|
+
if (theme.tag !== undefined) {
|
|
1939
|
+
checkType(theme.tag, "object", ["theme", "tag"], "Theme tag");
|
|
1940
|
+
if (
|
|
1941
|
+
typeof theme.tag !== "object" ||
|
|
1942
|
+
theme.tag === null ||
|
|
1943
|
+
Array.isArray(theme.tag)
|
|
1944
|
+
) {
|
|
1945
|
+
throwConfigError("Theme tag must be an object.", ["theme", "tag"]);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
const tagTheme = theme.tag as TagTheme & Record<string, unknown>;
|
|
1949
|
+
const allowedTagKeys = new Set(["color"]);
|
|
1950
|
+
for (const key of Object.keys(tagTheme)) {
|
|
1951
|
+
if (!allowedTagKeys.has(key)) {
|
|
1952
|
+
throwConfigError("Theme tag configuration only supports 'color'.", [
|
|
1953
|
+
"theme",
|
|
1954
|
+
"tag",
|
|
1955
|
+
key,
|
|
1956
|
+
]);
|
|
1957
|
+
}
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (tagTheme.color !== undefined) {
|
|
1961
|
+
tagTheme.color = normalizeThemeColorConfig(
|
|
1962
|
+
tagTheme.color,
|
|
1963
|
+
["theme", "tag", "color"],
|
|
1964
|
+
"Theme tag color",
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1858
1969
|
if (theme.card !== undefined) {
|
|
1859
1970
|
checkType(theme.card, "object", ["theme", "card"], "Theme card");
|
|
1860
1971
|
if (
|
|
@@ -2293,22 +2404,19 @@ function validateAssistant(assistant: DocsConfig["assistant"]): void {
|
|
|
2293
2404
|
]);
|
|
2294
2405
|
}
|
|
2295
2406
|
|
|
2296
|
-
const allowedIconKeys = new Set(["src"
|
|
2407
|
+
const allowedIconKeys = new Set(["src"]);
|
|
2297
2408
|
for (const key of Object.keys(assistant.icon)) {
|
|
2298
2409
|
if (!allowedIconKeys.has(key)) {
|
|
2299
2410
|
throwConfigError(
|
|
2300
|
-
"Assistant icon only supports 'src'
|
|
2411
|
+
"Assistant icon only supports 'src'.",
|
|
2301
2412
|
["assistant", "icon", key],
|
|
2302
2413
|
);
|
|
2303
2414
|
}
|
|
2304
2415
|
}
|
|
2305
2416
|
|
|
2306
|
-
if (
|
|
2307
|
-
assistant.icon.src === undefined &&
|
|
2308
|
-
assistant.icon.color === undefined
|
|
2309
|
-
) {
|
|
2417
|
+
if (assistant.icon.src === undefined) {
|
|
2310
2418
|
throwConfigError(
|
|
2311
|
-
"Assistant icon must include 'src'
|
|
2419
|
+
"Assistant icon must include 'src'.",
|
|
2312
2420
|
["assistant", "icon"],
|
|
2313
2421
|
);
|
|
2314
2422
|
}
|
|
@@ -2320,13 +2428,6 @@ function validateAssistant(assistant: DocsConfig["assistant"]): void {
|
|
|
2320
2428
|
);
|
|
2321
2429
|
}
|
|
2322
2430
|
|
|
2323
|
-
if (assistant.icon.color !== undefined) {
|
|
2324
|
-
assistant.icon.color = normalizeHexColor(
|
|
2325
|
-
assistant.icon.color,
|
|
2326
|
-
["assistant", "icon", "color"],
|
|
2327
|
-
"Assistant icon color",
|
|
2328
|
-
);
|
|
2329
|
-
}
|
|
2330
2431
|
}
|
|
2331
2432
|
|
|
2332
2433
|
function validateHome(home: DocsConfig["home"]): string | undefined {
|