radiant-docs 0.1.47 → 0.1.48
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/src/components/OpenApiPage.astro +121 -64
- package/template/src/components/PagePagination.astro +1 -1
- package/template/src/components/SidebarSubgroup.astro +1 -1
- package/template/src/components/endpoint/RequestSnippets.astro +175 -16
- package/template/src/components/endpoint/ResponseDisplay.astro +21 -16
- package/template/src/components/endpoint/ResponseSnippets.astro +175 -16
- package/template/src/components/ui/CodeTabEdge.astro +11 -60
- package/template/src/components/user/Callout.astro +1 -1
- package/template/src/components/user/CodeBlock.astro +31 -20
- package/template/src/components/user/CodeGroup.astro +46 -36
- package/template/src/lib/code/code-block.ts +89 -10
- package/template/src/lib/code/shiki-theme-config.ts +16 -0
- package/template/src/lib/validation.ts +137 -0
- package/template/src/styles/global.css +51 -3
|
@@ -4,11 +4,11 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<div
|
|
7
|
-
class="rd-prose-block group/prose-code-group not-prose relative w-full max-w-full min-w-0
|
|
7
|
+
class="rd-prose-block group/prose-code-group not-prose relative w-full max-w-full min-w-0 rounded-xl"
|
|
8
8
|
data-rd-code-group-root="true"
|
|
9
9
|
>
|
|
10
10
|
<div
|
|
11
|
-
class="relative z-10 overflow-visible rounded-t-xl
|
|
11
|
+
class="relative z-10 overflow-visible rounded-t-xl bg-(--rd-code-header-surface)"
|
|
12
12
|
>
|
|
13
13
|
<div class="flex min-w-0 items-end justify-between gap-2">
|
|
14
14
|
<div class="min-w-0 flex-1 overflow-hidden rounded-t-xl">
|
|
@@ -19,18 +19,16 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
19
19
|
<div
|
|
20
20
|
data-rd-code-group-pill
|
|
21
21
|
aria-hidden="true"
|
|
22
|
-
class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-[9px] border-[0.5px] border-
|
|
22
|
+
class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-[9px] border-[0.5px] border-(--rd-code-tab-edge-border) bg-(--rd-code-surface) opacity-0 transition-[left,width,opacity] duration-200 ease-out"
|
|
23
23
|
>
|
|
24
24
|
</div>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
27
|
|
|
28
28
|
<div
|
|
29
|
-
class="relative h-9 w-5 shrink-0 rounded-tr-xl bg-
|
|
29
|
+
class="relative h-9 w-5 shrink-0 rounded-tr-xl bg-(--rd-code-surface) border-t-[0.5px] border-r-[0.5px] border-(--rd-code-tab-edge-border)"
|
|
30
30
|
>
|
|
31
|
-
<div
|
|
32
|
-
class="absolute inset-x-0 -bottom-px h-px bg-white dark:bg-(--rd-code-surface)"
|
|
33
|
-
>
|
|
31
|
+
<div class="absolute inset-x-0 -bottom-px h-px bg-(--rd-code-surface)">
|
|
34
32
|
</div>
|
|
35
33
|
<CodeTabEdge
|
|
36
34
|
className="pointer-events-none absolute -top-px right-full z-10 h-[calc(100%+2px)] rotate-y-180"
|
|
@@ -49,7 +47,7 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
49
47
|
/>
|
|
50
48
|
<Icon
|
|
51
49
|
name="lucide:check"
|
|
52
|
-
class="absolute size-3.5 stroke-3 origin-center scale-25 rotate-6 opacity-0 text-green-
|
|
50
|
+
class="absolute size-3.5 stroke-3 origin-center scale-25 rotate-6 opacity-0 text-green-800/70 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none dark:text-green-400/90"
|
|
53
51
|
data-rd-copy-check
|
|
54
52
|
aria-hidden="true"
|
|
55
53
|
/>
|
|
@@ -60,7 +58,7 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
60
58
|
|
|
61
59
|
<div
|
|
62
60
|
data-rd-code-group-content
|
|
63
|
-
class="relative min-w-0 overflow-hidden transition-[height] duration-
|
|
61
|
+
class="relative min-w-0 overflow-hidden transition-[height] duration-[360ms] ease-[cubic-bezier(0.22,1,0.36,1)] bg-(--rd-code-surface) rounded-xl rounded-tr-none border-[0.5px] border-(--rd-code-tab-edge-border)"
|
|
64
62
|
>
|
|
65
63
|
<slot />
|
|
66
64
|
</div>
|
|
@@ -93,7 +91,8 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
93
91
|
const prefersReducedMotion =
|
|
94
92
|
typeof window.matchMedia === "function" &&
|
|
95
93
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
96
|
-
const TRANSITION_DURATION_MS = prefersReducedMotion ? 0 :
|
|
94
|
+
const TRANSITION_DURATION_MS = prefersReducedMotion ? 0 : 360;
|
|
95
|
+
const TRANSITION_EASING = "cubic-bezier(0.22, 1, 0.36, 1)";
|
|
97
96
|
|
|
98
97
|
let activeIndex = 0;
|
|
99
98
|
let transitionTimeoutId = null;
|
|
@@ -139,7 +138,7 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
139
138
|
|
|
140
139
|
const tabButton = document.createElement("button");
|
|
141
140
|
tabButton.type = "button";
|
|
142
|
-
tabButton.className = `relative inline-flex h-9 items-center border-0 bg-transparent px-3 py-1.5 text-xs font-medium
|
|
141
|
+
tabButton.className = `relative inline-flex h-9 items-center border-0 bg-transparent px-3 py-1.5 text-xs font-medium transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer ${hasTabIcon ? "gap-2" : ""}`;
|
|
143
142
|
tabButton.setAttribute("aria-label", filename);
|
|
144
143
|
tabButton.setAttribute("data-rd-code-group-tab", String(index));
|
|
145
144
|
|
|
@@ -205,12 +204,11 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
205
204
|
const updateTabButtonStates = () => {
|
|
206
205
|
tabs.forEach(({ tabButton, iconContainer }, index) => {
|
|
207
206
|
const isActive = index === activeIndex;
|
|
208
|
-
tabButton.classList.toggle("text-
|
|
209
|
-
tabButton.classList.toggle("text-
|
|
210
|
-
|
|
207
|
+
tabButton.classList.toggle("text-neutral-500/80", !isActive);
|
|
208
|
+
tabButton.classList.toggle("dark:text-neutral-400/80", !isActive);
|
|
211
209
|
if (!iconContainer) return;
|
|
212
|
-
iconContainer.classList.toggle("opacity-100", isActive);
|
|
213
210
|
iconContainer.classList.toggle("opacity-80", !isActive);
|
|
211
|
+
iconContainer.classList.toggle("grayscale-100", !isActive);
|
|
214
212
|
});
|
|
215
213
|
syncPill();
|
|
216
214
|
};
|
|
@@ -269,15 +267,31 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
269
267
|
activeItemResizeObserver.observe(activeItem);
|
|
270
268
|
};
|
|
271
269
|
|
|
270
|
+
const setItemToTransitionPosition = (itemElement) => {
|
|
271
|
+
itemElement.style.position = "absolute";
|
|
272
|
+
itemElement.style.inset = "";
|
|
273
|
+
itemElement.style.top = "0";
|
|
274
|
+
itemElement.style.right = "0";
|
|
275
|
+
itemElement.style.bottom = "";
|
|
276
|
+
itemElement.style.left = "0";
|
|
277
|
+
};
|
|
278
|
+
|
|
272
279
|
const setPanelsToRestState = () => {
|
|
273
280
|
tabs.forEach(({ itemElement, panelElement, frameElement }, index) => {
|
|
274
281
|
const isActive = index === activeIndex;
|
|
275
282
|
itemElement.style.position = isActive ? "relative" : "absolute";
|
|
276
|
-
itemElement.style.inset =
|
|
283
|
+
itemElement.style.inset = "";
|
|
284
|
+
itemElement.style.top = isActive ? "" : "0";
|
|
285
|
+
itemElement.style.right = isActive ? "" : "0";
|
|
286
|
+
itemElement.style.bottom = isActive ? "" : "0";
|
|
287
|
+
itemElement.style.left = isActive ? "" : "0";
|
|
277
288
|
itemElement.style.opacity = isActive ? "1" : "0";
|
|
278
289
|
itemElement.style.visibility = isActive ? "visible" : "hidden";
|
|
279
290
|
itemElement.style.pointerEvents = isActive ? "auto" : "none";
|
|
280
291
|
itemElement.style.zIndex = isActive ? "1" : "0";
|
|
292
|
+
itemElement.style.transform = "translateX(0)";
|
|
293
|
+
itemElement.style.animation = "none";
|
|
294
|
+
itemElement.style.willChange = "auto";
|
|
281
295
|
panelElement.style.transform = "translateX(0)";
|
|
282
296
|
panelElement.style.animation = "none";
|
|
283
297
|
panelElement.style.willChange = "auto";
|
|
@@ -313,8 +327,6 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
313
327
|
const nextItem = nextTab?.itemElement;
|
|
314
328
|
const previousPanel = previousTab?.panelElement;
|
|
315
329
|
const nextPanel = nextTab?.panelElement;
|
|
316
|
-
const previousFrame = previousTab?.frameElement;
|
|
317
|
-
const nextFrame = nextTab?.frameElement;
|
|
318
330
|
if (!previousItem || !nextItem || !previousPanel || !nextPanel) {
|
|
319
331
|
normalizeToCurrentState();
|
|
320
332
|
return;
|
|
@@ -331,37 +343,35 @@ import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
|
331
343
|
});
|
|
332
344
|
}
|
|
333
345
|
|
|
334
|
-
previousItem
|
|
335
|
-
previousItem.style.inset = "0";
|
|
346
|
+
setItemToTransitionPosition(previousItem);
|
|
336
347
|
previousItem.style.opacity = "1";
|
|
337
348
|
previousItem.style.visibility = "visible";
|
|
338
349
|
previousItem.style.pointerEvents = "none";
|
|
339
350
|
previousItem.style.zIndex = "1";
|
|
351
|
+
previousItem.style.transform = "translateX(0)";
|
|
352
|
+
previousItem.style.willChange = "transform, opacity";
|
|
340
353
|
previousPanel.style.transform = "translateX(0)";
|
|
341
|
-
previousPanel.style.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
previousPanel.style.animation =
|
|
354
|
+
previousPanel.style.animation = "none";
|
|
355
|
+
previousPanel.style.willChange = "auto";
|
|
356
|
+
previousItem.style.animation =
|
|
346
357
|
direction === 1
|
|
347
|
-
? `rd-code-group-slide-out-to-left ${TRANSITION_DURATION_MS}ms
|
|
348
|
-
: `rd-code-group-slide-out-to-right ${TRANSITION_DURATION_MS}ms
|
|
358
|
+
? `rd-code-group-slide-out-to-left ${TRANSITION_DURATION_MS}ms ${TRANSITION_EASING} both`
|
|
359
|
+
: `rd-code-group-slide-out-to-right ${TRANSITION_DURATION_MS}ms ${TRANSITION_EASING} both`;
|
|
349
360
|
|
|
350
|
-
nextItem
|
|
351
|
-
nextItem.style.inset = "0";
|
|
361
|
+
setItemToTransitionPosition(nextItem);
|
|
352
362
|
nextItem.style.opacity = "1";
|
|
353
363
|
nextItem.style.visibility = "visible";
|
|
354
364
|
nextItem.style.pointerEvents = "auto";
|
|
355
365
|
nextItem.style.zIndex = "2";
|
|
366
|
+
nextItem.style.transform = "translateX(0)";
|
|
367
|
+
nextItem.style.willChange = "transform, opacity";
|
|
356
368
|
nextPanel.style.transform = "translateX(0)";
|
|
357
|
-
nextPanel.style.
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
361
|
-
nextPanel.style.animation =
|
|
369
|
+
nextPanel.style.animation = "none";
|
|
370
|
+
nextPanel.style.willChange = "auto";
|
|
371
|
+
nextItem.style.animation =
|
|
362
372
|
direction === 1
|
|
363
|
-
? `rd-code-group-slide-in-from-right ${TRANSITION_DURATION_MS}ms
|
|
364
|
-
: `rd-code-group-slide-in-from-left ${TRANSITION_DURATION_MS}ms
|
|
373
|
+
? `rd-code-group-slide-in-from-right ${TRANSITION_DURATION_MS}ms ${TRANSITION_EASING} both`
|
|
374
|
+
: `rd-code-group-slide-in-from-left ${TRANSITION_DURATION_MS}ms ${TRANSITION_EASING} both`;
|
|
365
375
|
|
|
366
376
|
transitionTimeoutId = window.setTimeout(() => {
|
|
367
377
|
transitionTimeoutId = null;
|
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
type ThemedToken,
|
|
8
8
|
} from "shiki";
|
|
9
9
|
import { DEFAULT_FILE, getIconForFile } from "vscode-icons-js";
|
|
10
|
+
import { getConfig } from "../validation";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_SHIKI_DARK_THEME,
|
|
13
|
+
DEFAULT_SHIKI_LIGHT_THEME,
|
|
14
|
+
type CodeSyntaxThemeByMode,
|
|
15
|
+
} from "./shiki-theme-config";
|
|
10
16
|
|
|
11
17
|
export const DEFAULT_CODE_BLOCK_LANGUAGE = "plaintext";
|
|
12
18
|
|
|
@@ -137,9 +143,8 @@ const CODE_BLOCK_LANGUAGE_ICON_FILE_BY_VALUE: Record<string, string> = {
|
|
|
137
143
|
yaml: "file_type_yaml_official.svg",
|
|
138
144
|
};
|
|
139
145
|
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const SHIKI_THEMES = [SHIKI_LIGHT_THEME, SHIKI_DARK_THEME] as const;
|
|
146
|
+
const SHIKI_LIGHT_LINE_HIGHLIGHT_FALLBACK = "#f6f8fa";
|
|
147
|
+
const SHIKI_DARK_LINE_HIGHLIGHT_FALLBACK = "#2b3036";
|
|
143
148
|
const BUNDLED_LANGUAGE_SET = new Set(Object.keys(bundledLanguages));
|
|
144
149
|
const LANGUAGE_RUNTIME_DEPENDENCIES: Record<string, string[]> = {
|
|
145
150
|
// MDX tokenization relies on TSX grammar injections for JSX-style tags.
|
|
@@ -159,6 +164,7 @@ let iconFileNameSetPromise: Promise<Set<string>> | null = null;
|
|
|
159
164
|
let highlighterPromise:
|
|
160
165
|
| Promise<Awaited<ReturnType<typeof getSingletonHighlighter>>>
|
|
161
166
|
| null = null;
|
|
167
|
+
let highlighterThemeKey = "";
|
|
162
168
|
const loadedLanguageSet = new Set<string>([DEFAULT_CODE_BLOCK_LANGUAGE]);
|
|
163
169
|
const languageLoadPromiseByName = new Map<string, Promise<void>>();
|
|
164
170
|
|
|
@@ -371,10 +377,30 @@ function namespaceSvgIds(svg: string, namespace: string): string {
|
|
|
371
377
|
return namespacedSvg;
|
|
372
378
|
}
|
|
373
379
|
|
|
374
|
-
async function
|
|
375
|
-
|
|
380
|
+
async function getConfiguredCodeSyntaxThemes(): Promise<CodeSyntaxThemeByMode> {
|
|
381
|
+
const config = await getConfig();
|
|
382
|
+
const configuredSyntaxTheme = config.theme?.code?.syntaxTheme;
|
|
383
|
+
|
|
384
|
+
if (typeof configuredSyntaxTheme === "string") {
|
|
385
|
+
return {
|
|
386
|
+
light: configuredSyntaxTheme,
|
|
387
|
+
dark: configuredSyntaxTheme,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
light: configuredSyntaxTheme?.light ?? DEFAULT_SHIKI_LIGHT_THEME,
|
|
393
|
+
dark: configuredSyntaxTheme?.dark ?? DEFAULT_SHIKI_DARK_THEME,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function getHighlighter(syntaxThemes: CodeSyntaxThemeByMode) {
|
|
398
|
+
const nextThemeKey = `${syntaxThemes.light}\u0000${syntaxThemes.dark}`;
|
|
399
|
+
|
|
400
|
+
if (!highlighterPromise || highlighterThemeKey !== nextThemeKey) {
|
|
401
|
+
highlighterThemeKey = nextThemeKey;
|
|
376
402
|
highlighterPromise = getSingletonHighlighter({
|
|
377
|
-
themes: [
|
|
403
|
+
themes: Array.from(new Set([syntaxThemes.light, syntaxThemes.dark])),
|
|
378
404
|
langs: [DEFAULT_CODE_BLOCK_LANGUAGE],
|
|
379
405
|
});
|
|
380
406
|
}
|
|
@@ -382,6 +408,40 @@ async function getHighlighter() {
|
|
|
382
408
|
return highlighterPromise;
|
|
383
409
|
}
|
|
384
410
|
|
|
411
|
+
function getThemeColor(
|
|
412
|
+
highlighter: Awaited<ReturnType<typeof getSingletonHighlighter>>,
|
|
413
|
+
themeName: string,
|
|
414
|
+
colorName: string,
|
|
415
|
+
fallback: string,
|
|
416
|
+
): string {
|
|
417
|
+
const colorValue = highlighter.getTheme(themeName)?.colors?.[colorName];
|
|
418
|
+
return typeof colorValue === "string" && colorValue.trim().length > 0
|
|
419
|
+
? colorValue.trim()
|
|
420
|
+
: fallback;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function getCodeThemeColors(
|
|
424
|
+
highlighter: Awaited<ReturnType<typeof getSingletonHighlighter>>,
|
|
425
|
+
syntaxThemes: CodeSyntaxThemeByMode,
|
|
426
|
+
): CodeThemeColors {
|
|
427
|
+
return {
|
|
428
|
+
lineHighlightBackground: {
|
|
429
|
+
light: getThemeColor(
|
|
430
|
+
highlighter,
|
|
431
|
+
syntaxThemes.light,
|
|
432
|
+
"editor.lineHighlightBackground",
|
|
433
|
+
SHIKI_LIGHT_LINE_HIGHLIGHT_FALLBACK,
|
|
434
|
+
),
|
|
435
|
+
dark: getThemeColor(
|
|
436
|
+
highlighter,
|
|
437
|
+
syntaxThemes.dark,
|
|
438
|
+
"editor.lineHighlightBackground",
|
|
439
|
+
SHIKI_DARK_LINE_HIGHLIGHT_FALLBACK,
|
|
440
|
+
),
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
385
445
|
function resolveLoadableLanguage(
|
|
386
446
|
highlighter: Awaited<ReturnType<typeof getSingletonHighlighter>>,
|
|
387
447
|
normalizedLanguage: string,
|
|
@@ -496,8 +556,11 @@ export async function getCodeLineTokens({
|
|
|
496
556
|
}): Promise<{
|
|
497
557
|
normalizedLanguage: string;
|
|
498
558
|
lines: CodeLineToken[][];
|
|
559
|
+
themeColors: CodeThemeColors;
|
|
499
560
|
}> {
|
|
500
|
-
const
|
|
561
|
+
const syntaxThemes = await getConfiguredCodeSyntaxThemes();
|
|
562
|
+
const highlighter = await getHighlighter(syntaxThemes);
|
|
563
|
+
const themeColors = getCodeThemeColors(highlighter, syntaxThemes);
|
|
501
564
|
const normalizedLanguage = normalizeCodeLanguageValue(language);
|
|
502
565
|
const loadableLanguage = resolveLoadableLanguage(
|
|
503
566
|
highlighter,
|
|
@@ -525,26 +588,41 @@ export async function getCodeLineTokens({
|
|
|
525
588
|
}
|
|
526
589
|
|
|
527
590
|
try {
|
|
528
|
-
const themedTokenLines = getThemedTokenLines(
|
|
591
|
+
const themedTokenLines = getThemedTokenLines(
|
|
592
|
+
highlighter,
|
|
593
|
+
code,
|
|
594
|
+
targetLanguage,
|
|
595
|
+
syntaxThemes,
|
|
596
|
+
);
|
|
529
597
|
|
|
530
598
|
return {
|
|
531
599
|
normalizedLanguage: targetLanguage,
|
|
532
600
|
lines: mergeTokenLines(themedTokenLines.light, themedTokenLines.dark),
|
|
601
|
+
themeColors,
|
|
533
602
|
};
|
|
534
603
|
} catch {
|
|
535
604
|
const themedTokenLines = getThemedTokenLines(
|
|
536
605
|
highlighter,
|
|
537
606
|
code,
|
|
538
607
|
DEFAULT_CODE_BLOCK_LANGUAGE,
|
|
608
|
+
syntaxThemes,
|
|
539
609
|
);
|
|
540
610
|
|
|
541
611
|
return {
|
|
542
612
|
normalizedLanguage: DEFAULT_CODE_BLOCK_LANGUAGE,
|
|
543
613
|
lines: mergeTokenLines(themedTokenLines.light, themedTokenLines.dark),
|
|
614
|
+
themeColors,
|
|
544
615
|
};
|
|
545
616
|
}
|
|
546
617
|
}
|
|
547
618
|
|
|
619
|
+
export type CodeThemeColors = {
|
|
620
|
+
lineHighlightBackground: {
|
|
621
|
+
light: string;
|
|
622
|
+
dark: string;
|
|
623
|
+
};
|
|
624
|
+
};
|
|
625
|
+
|
|
548
626
|
export type CodeLineToken = {
|
|
549
627
|
content: string;
|
|
550
628
|
color?: string;
|
|
@@ -560,6 +638,7 @@ function getThemedTokenLines(
|
|
|
560
638
|
highlighter: Awaited<ReturnType<typeof getSingletonHighlighter>>,
|
|
561
639
|
code: string,
|
|
562
640
|
lang: string,
|
|
641
|
+
syntaxThemes: CodeSyntaxThemeByMode,
|
|
563
642
|
): {
|
|
564
643
|
light: ThemedToken[][];
|
|
565
644
|
dark: ThemedToken[][];
|
|
@@ -567,11 +646,11 @@ function getThemedTokenLines(
|
|
|
567
646
|
const shikiLanguage = lang as keyof typeof bundledLanguages;
|
|
568
647
|
const lightTokenResult = highlighter.codeToTokens(code, {
|
|
569
648
|
lang: shikiLanguage,
|
|
570
|
-
theme:
|
|
649
|
+
theme: syntaxThemes.light,
|
|
571
650
|
});
|
|
572
651
|
const darkTokenResult = highlighter.codeToTokens(code, {
|
|
573
652
|
lang: shikiLanguage,
|
|
574
|
-
theme:
|
|
653
|
+
theme: syntaxThemes.dark,
|
|
575
654
|
});
|
|
576
655
|
|
|
577
656
|
return {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { bundledThemes } from "shiki";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_SHIKI_LIGHT_THEME = "github-light";
|
|
4
|
+
export const DEFAULT_SHIKI_DARK_THEME = "github-dark";
|
|
5
|
+
|
|
6
|
+
export type CodeSyntaxThemeByMode = {
|
|
7
|
+
light: string;
|
|
8
|
+
dark: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const SHIKI_BUNDLED_THEME_NAMES = Object.keys(bundledThemes).sort();
|
|
12
|
+
const SHIKI_BUNDLED_THEME_NAME_SET = new Set(SHIKI_BUNDLED_THEME_NAMES);
|
|
13
|
+
|
|
14
|
+
export function isBundledShikiThemeName(value: string): boolean {
|
|
15
|
+
return SHIKI_BUNDLED_THEME_NAME_SET.has(value);
|
|
16
|
+
}
|
|
@@ -6,6 +6,12 @@ import { oas } from "@stoplight/spectral-rulesets";
|
|
|
6
6
|
import { compile } from "@mdx-js/mdx";
|
|
7
7
|
import yaml from "yaml";
|
|
8
8
|
import { docsSchema } from "./frontmatter-schema";
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_SHIKI_DARK_THEME,
|
|
11
|
+
DEFAULT_SHIKI_LIGHT_THEME,
|
|
12
|
+
SHIKI_BUNDLED_THEME_NAMES,
|
|
13
|
+
isBundledShikiThemeName,
|
|
14
|
+
} from "./code/shiki-theme-config";
|
|
9
15
|
|
|
10
16
|
// --- Configuration Constants ---
|
|
11
17
|
const CWD = process.cwd();
|
|
@@ -226,10 +232,20 @@ export type CardTheme = {
|
|
|
226
232
|
cover?: CardCoverTheme;
|
|
227
233
|
button?: CardButtonTheme;
|
|
228
234
|
};
|
|
235
|
+
export type CodeSyntaxThemeConfig =
|
|
236
|
+
| string
|
|
237
|
+
| {
|
|
238
|
+
light?: string;
|
|
239
|
+
dark?: string;
|
|
240
|
+
};
|
|
241
|
+
export type CodeTheme = {
|
|
242
|
+
syntaxTheme?: CodeSyntaxThemeConfig;
|
|
243
|
+
};
|
|
229
244
|
export type DocsTheme = {
|
|
230
245
|
baseColor?: BaseColorOption | BaseColorByMode;
|
|
231
246
|
themeColor?: string | ThemeColorByMode;
|
|
232
247
|
card?: CardTheme;
|
|
248
|
+
code?: CodeTheme;
|
|
233
249
|
};
|
|
234
250
|
export type AssistantIcon = {
|
|
235
251
|
src?: string;
|
|
@@ -1718,6 +1734,127 @@ function validateTheme(theme: DocsConfig["theme"]): void {
|
|
|
1718
1734
|
}
|
|
1719
1735
|
}
|
|
1720
1736
|
|
|
1737
|
+
const normalizeShikiThemeName = (
|
|
1738
|
+
value: unknown,
|
|
1739
|
+
currentPath: Path,
|
|
1740
|
+
label: string,
|
|
1741
|
+
): string => {
|
|
1742
|
+
checkType(value, "string", currentPath, label);
|
|
1743
|
+
if (typeof value !== "string") {
|
|
1744
|
+
throwConfigError(`${label} must be a string.`, currentPath);
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
const normalizedThemeName = value.trim().toLowerCase();
|
|
1748
|
+
if (normalizedThemeName.length === 0) {
|
|
1749
|
+
throwConfigError(`${label} cannot be empty.`, currentPath);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
if (!isBundledShikiThemeName(normalizedThemeName)) {
|
|
1753
|
+
throwConfigError(
|
|
1754
|
+
`${label} must be a bundled Shiki theme name. Supported themes include: ${SHIKI_BUNDLED_THEME_NAMES.join(", ")}.`,
|
|
1755
|
+
currentPath,
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
return normalizedThemeName;
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
if (theme.code !== undefined) {
|
|
1763
|
+
checkType(theme.code, "object", ["theme", "code"], "Theme code");
|
|
1764
|
+
if (
|
|
1765
|
+
typeof theme.code !== "object" ||
|
|
1766
|
+
theme.code === null ||
|
|
1767
|
+
Array.isArray(theme.code)
|
|
1768
|
+
) {
|
|
1769
|
+
throwConfigError("Theme code must be an object.", ["theme", "code"]);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
const codeTheme = theme.code as CodeTheme & Record<string, unknown>;
|
|
1773
|
+
const allowedCodeKeys = new Set(["syntaxTheme"]);
|
|
1774
|
+
for (const key of Object.keys(codeTheme)) {
|
|
1775
|
+
if (!allowedCodeKeys.has(key)) {
|
|
1776
|
+
throwConfigError(
|
|
1777
|
+
"Theme code configuration only supports 'syntaxTheme'.",
|
|
1778
|
+
["theme", "code", key],
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
if (codeTheme.syntaxTheme !== undefined) {
|
|
1784
|
+
if (typeof codeTheme.syntaxTheme === "string") {
|
|
1785
|
+
const themeName = normalizeShikiThemeName(
|
|
1786
|
+
codeTheme.syntaxTheme,
|
|
1787
|
+
["theme", "code", "syntaxTheme"],
|
|
1788
|
+
"Theme code syntax theme",
|
|
1789
|
+
);
|
|
1790
|
+
codeTheme.syntaxTheme = {
|
|
1791
|
+
light: themeName,
|
|
1792
|
+
dark: themeName,
|
|
1793
|
+
};
|
|
1794
|
+
} else {
|
|
1795
|
+
checkType(
|
|
1796
|
+
codeTheme.syntaxTheme,
|
|
1797
|
+
"object",
|
|
1798
|
+
["theme", "code", "syntaxTheme"],
|
|
1799
|
+
"Theme code syntax theme",
|
|
1800
|
+
);
|
|
1801
|
+
if (
|
|
1802
|
+
typeof codeTheme.syntaxTheme !== "object" ||
|
|
1803
|
+
codeTheme.syntaxTheme === null ||
|
|
1804
|
+
Array.isArray(codeTheme.syntaxTheme)
|
|
1805
|
+
) {
|
|
1806
|
+
throwConfigError(
|
|
1807
|
+
"Theme code syntax theme must be a string or an object with light/dark values.",
|
|
1808
|
+
["theme", "code", "syntaxTheme"],
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
const syntaxThemeByMode = codeTheme.syntaxTheme as Record<
|
|
1813
|
+
string,
|
|
1814
|
+
unknown
|
|
1815
|
+
>;
|
|
1816
|
+
const allowedSyntaxThemeKeys = new Set(["light", "dark"]);
|
|
1817
|
+
for (const key of Object.keys(syntaxThemeByMode)) {
|
|
1818
|
+
if (!allowedSyntaxThemeKeys.has(key)) {
|
|
1819
|
+
throwConfigError(
|
|
1820
|
+
"Theme code syntax theme object only supports 'light' and 'dark'.",
|
|
1821
|
+
["theme", "code", "syntaxTheme", key],
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
const light =
|
|
1827
|
+
syntaxThemeByMode.light !== undefined
|
|
1828
|
+
? normalizeShikiThemeName(
|
|
1829
|
+
syntaxThemeByMode.light,
|
|
1830
|
+
["theme", "code", "syntaxTheme", "light"],
|
|
1831
|
+
"Theme code syntax theme light",
|
|
1832
|
+
)
|
|
1833
|
+
: undefined;
|
|
1834
|
+
const dark =
|
|
1835
|
+
syntaxThemeByMode.dark !== undefined
|
|
1836
|
+
? normalizeShikiThemeName(
|
|
1837
|
+
syntaxThemeByMode.dark,
|
|
1838
|
+
["theme", "code", "syntaxTheme", "dark"],
|
|
1839
|
+
"Theme code syntax theme dark",
|
|
1840
|
+
)
|
|
1841
|
+
: undefined;
|
|
1842
|
+
|
|
1843
|
+
if (!light && !dark) {
|
|
1844
|
+
throwConfigError(
|
|
1845
|
+
"Theme code syntax theme object must include 'light', 'dark', or both.",
|
|
1846
|
+
["theme", "code", "syntaxTheme"],
|
|
1847
|
+
);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
codeTheme.syntaxTheme = {
|
|
1851
|
+
light: light ?? DEFAULT_SHIKI_LIGHT_THEME,
|
|
1852
|
+
dark: dark ?? DEFAULT_SHIKI_DARK_THEME,
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1721
1858
|
if (theme.card !== undefined) {
|
|
1722
1859
|
checkType(theme.card, "object", ["theme", "card"], "Theme card");
|
|
1723
1860
|
if (
|
|
@@ -55,9 +55,18 @@
|
|
|
55
55
|
--border-light: var(--color-neutral-100);
|
|
56
56
|
--input: oklch(0.922 0 0);
|
|
57
57
|
--ring: oklch(0.708 0 0);
|
|
58
|
-
--rd-code-surface:
|
|
58
|
+
--rd-code-surface: color-mix(
|
|
59
|
+
in srgb,
|
|
60
|
+
var(--color-neutral-100) 60%,
|
|
61
|
+
var(--background) 40%
|
|
62
|
+
);
|
|
63
|
+
--rd-code-header-surface: var(--color-white);
|
|
59
64
|
--rd-code-tab-edge-bg: var(--rd-code-surface);
|
|
60
|
-
--rd-code-tab-edge-border:
|
|
65
|
+
--rd-code-tab-edge-border: color-mix(
|
|
66
|
+
in oklab,
|
|
67
|
+
var(--color-neutral-900) 4%,
|
|
68
|
+
var(--color-white) 96%
|
|
69
|
+
);
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
/* 3. Dark Mode */
|
|
@@ -85,8 +94,17 @@
|
|
|
85
94
|
var(--color-neutral-800) 55%,
|
|
86
95
|
var(--color-neutral-900) 45%
|
|
87
96
|
);
|
|
97
|
+
--rd-code-header-surface: color-mix(
|
|
98
|
+
in srgb,
|
|
99
|
+
var(--color-neutral-900) 100%,
|
|
100
|
+
var(--rd-code-surface) 0%
|
|
101
|
+
);
|
|
88
102
|
--rd-code-tab-edge-bg: var(--rd-code-surface);
|
|
89
|
-
--rd-code-tab-edge-border:
|
|
103
|
+
--rd-code-tab-edge-border: color-mix(
|
|
104
|
+
in oklab,
|
|
105
|
+
white 4%,
|
|
106
|
+
var(--rd-code-tab-edge-bg) 96%
|
|
107
|
+
);
|
|
90
108
|
}
|
|
91
109
|
|
|
92
110
|
@variant dark (&:where(.dark, .dark *));
|
|
@@ -197,6 +215,16 @@
|
|
|
197
215
|
}
|
|
198
216
|
|
|
199
217
|
@layer base {
|
|
218
|
+
.prose-rules > :first-child {
|
|
219
|
+
margin-block-start: 0 !important;
|
|
220
|
+
margin-top: 0 !important;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.prose-rules > :last-child {
|
|
224
|
+
margin-block-end: 0 !important;
|
|
225
|
+
margin-bottom: 0 !important;
|
|
226
|
+
}
|
|
227
|
+
|
|
200
228
|
.prose-rules > .rd-prose-block:first-child,
|
|
201
229
|
.prose-rules > .react-renderer:first-child > .rd-prose-block,
|
|
202
230
|
.prose-rules > [data-node-view-content-react]:first-child > :first-child,
|
|
@@ -326,6 +354,26 @@
|
|
|
326
354
|
background-color: var(--rd-token-bg, transparent);
|
|
327
355
|
}
|
|
328
356
|
|
|
357
|
+
[data-rd-code-block-root] {
|
|
358
|
+
--rd-code-line-highlight-bg: color-mix(
|
|
359
|
+
in srgb,
|
|
360
|
+
var(--rd-code-line-highlight-theme-bg-light) 90%,
|
|
361
|
+
var(--color-neutral-400) 10%
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.dark [data-rd-code-block-root] {
|
|
366
|
+
--rd-code-line-highlight-bg: color-mix(
|
|
367
|
+
in srgb,
|
|
368
|
+
var(--rd-code-line-highlight-theme-bg-dark) 80%,
|
|
369
|
+
var(--color-neutral-700) 20%
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
[data-rd-code-line-highlighted="true"] {
|
|
374
|
+
background-color: var(--rd-code-line-highlight-bg);
|
|
375
|
+
}
|
|
376
|
+
|
|
329
377
|
.dark [data-rd-code-theme] [data-rd-token] {
|
|
330
378
|
color: var(--rd-token-color-dark, var(--rd-token-color, currentColor));
|
|
331
379
|
background-color: var(--rd-token-bg-dark, var(--rd-token-bg, transparent));
|