radiant-docs 0.1.47 → 0.1.49
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/LogoLink.astro +2 -2
- 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 +201 -24
- package/template/src/components/endpoint/ResponseDisplay.astro +25 -21
- package/template/src/components/endpoint/ResponseSnippets.astro +198 -21
- package/template/src/components/ui/CodeTabEdge.astro +11 -60
- package/template/src/components/user/Accordion.astro +18 -4
- package/template/src/components/user/Callout.astro +1 -1
- package/template/src/components/user/CodeBlock.astro +42 -21
- package/template/src/components/user/CodeGroup.astro +68 -41
- package/template/src/components/user/ComponentPreviewBlock.astro +32 -24
- package/template/src/components/user/Image.astro +88 -24
- package/template/src/components/user/Step.astro +2 -2
- package/template/src/components/user/Tabs.astro +59 -27
- 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 +48 -3
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { validateNoUnknownProps, validateProps } from "../../lib/component-error";
|
|
3
|
-
import { renderMarkdown } from "../../lib/utils";
|
|
4
3
|
import { resolveStaticAssetUrl } from "../../lib/static-asset-url";
|
|
5
4
|
|
|
6
5
|
interface Props {
|
|
7
|
-
src: string;
|
|
6
|
+
src: string | { light?: string; dark?: string };
|
|
8
7
|
alt?: string;
|
|
9
8
|
title?: string;
|
|
10
9
|
width?: number | string;
|
|
@@ -24,7 +23,7 @@ validateProps(
|
|
|
24
23
|
"Image",
|
|
25
24
|
imageProps,
|
|
26
25
|
{
|
|
27
|
-
src: { required: true, type: "string" },
|
|
26
|
+
src: { required: true, type: ["string", "object"] },
|
|
28
27
|
alt: { type: "string" },
|
|
29
28
|
title: { type: "string" },
|
|
30
29
|
width: { type: ["number", "string"] },
|
|
@@ -35,17 +34,51 @@ validateProps(
|
|
|
35
34
|
|
|
36
35
|
const { src, alt, title, width, zoom = true } = imageProps as Props;
|
|
37
36
|
const zoomEnabled = zoom !== false;
|
|
38
|
-
const resolvedSrc = resolveStaticAssetUrl(src);
|
|
39
37
|
const rawWidth = width;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
|
|
39
|
+
function normalizeImageSource(value: Props["src"]): {
|
|
40
|
+
light: string;
|
|
41
|
+
dark?: string;
|
|
42
|
+
} {
|
|
43
|
+
if (typeof value === "string") {
|
|
44
|
+
return { light: value };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const light = typeof value.light === "string" ? value.light : "";
|
|
48
|
+
const dark = typeof value.dark === "string" ? value.dark : undefined;
|
|
49
|
+
|
|
50
|
+
return { light, dark };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const normalizedSource = normalizeImageSource(src);
|
|
54
|
+
if (!normalizedSource.light.trim()) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`[USER_ERROR]: <Image>: Invalid prop "src": object form requires a non-empty "light" string (in ${Astro.url.pathname})`,
|
|
57
|
+
);
|
|
43
58
|
}
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
|
|
60
|
+
const resolvedLightSrc = resolveStaticAssetUrl(normalizedSource.light);
|
|
61
|
+
const resolvedDarkSrc =
|
|
62
|
+
typeof normalizedSource.dark === "string" && normalizedSource.dark.trim()
|
|
63
|
+
? resolveStaticAssetUrl(normalizedSource.dark)
|
|
64
|
+
: undefined;
|
|
65
|
+
const hasDarkSource =
|
|
66
|
+
typeof resolvedDarkSrc === "string" && resolvedDarkSrc !== resolvedLightSrc;
|
|
67
|
+
|
|
68
|
+
function createImageAttrs(source: string): Record<string, unknown> {
|
|
69
|
+
const attrs: Record<string, unknown> = { src: source };
|
|
70
|
+
if (typeof alt === "string") {
|
|
71
|
+
attrs.alt = alt;
|
|
72
|
+
}
|
|
73
|
+
if (width !== undefined) {
|
|
74
|
+
attrs.width = width;
|
|
75
|
+
}
|
|
76
|
+
return attrs;
|
|
46
77
|
}
|
|
47
78
|
|
|
48
|
-
const
|
|
79
|
+
const lightImageAttrs = createImageAttrs(resolvedLightSrc);
|
|
80
|
+
const darkImageAttrs = hasDarkSource ? createImageAttrs(resolvedDarkSrc!) : null;
|
|
81
|
+
const zoomAttrs: Record<string, unknown> = { src: resolvedLightSrc };
|
|
49
82
|
if (typeof alt === "string") {
|
|
50
83
|
zoomAttrs.alt = alt;
|
|
51
84
|
}
|
|
@@ -83,15 +116,15 @@ function isConstrainedWidthValue(value: unknown): boolean {
|
|
|
83
116
|
|
|
84
117
|
const hasCustomImageWidth = isConstrainedWidthValue(rawWidth);
|
|
85
118
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
119
|
+
const slotCaptionHtml = Astro.slots.has("default")
|
|
120
|
+
? (await Astro.slots.render("default")).trim()
|
|
121
|
+
: "";
|
|
122
|
+
const hasCaption = slotCaptionHtml.length > 0;
|
|
90
123
|
---
|
|
91
124
|
|
|
92
125
|
<figure
|
|
93
126
|
class:list={[
|
|
94
|
-
"rd-prose-block p-1.5 pb-1 xs:pb-1.5 group border border-neutral-
|
|
127
|
+
"rd-prose-block p-1.5 pb-1 xs:pb-1.5 group border-[0.5px] border-neutral-900/8 dark:border-white/6 bg-(--rd-code-surface) rounded-2xl shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-8px_rgba(0,0,0,0.08)] dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-8px_rgba(0,0,0,0.2)]",
|
|
95
128
|
hasCustomImageWidth ? "w-fit max-w-full mx-auto" : "w-full",
|
|
96
129
|
]}
|
|
97
130
|
x-data="{
|
|
@@ -102,8 +135,16 @@ const captionHtml =
|
|
|
102
135
|
style: 'visibility: hidden;',
|
|
103
136
|
fullShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
|
104
137
|
noShadow: '0 0 0 rgba(0, 0, 0, 0)',
|
|
138
|
+
zoomSrc: '',
|
|
139
|
+
activeImage() {
|
|
140
|
+
const darkImg = this.$refs.darkImg;
|
|
141
|
+
if (darkImg && window.getComputedStyle(darkImg).display !== 'none') {
|
|
142
|
+
return darkImg;
|
|
143
|
+
}
|
|
144
|
+
return this.$refs.lightImg;
|
|
145
|
+
},
|
|
105
146
|
computeZoomSize() {
|
|
106
|
-
const img = this
|
|
147
|
+
const img = this.activeImage();
|
|
107
148
|
const zoomContainer = this.$refs.zoomViewport;
|
|
108
149
|
if (!img || !zoomContainer) {
|
|
109
150
|
this.zoomSize = `width: min(92vw, ${this.zoomMaxWidth}px);`;
|
|
@@ -138,9 +179,12 @@ const captionHtml =
|
|
|
138
179
|
return this.zoomSize;
|
|
139
180
|
},
|
|
140
181
|
async zoom() {
|
|
182
|
+
const img = this.activeImage();
|
|
183
|
+
if (!img) return;
|
|
141
184
|
// 1. Lock scroll and measure
|
|
142
185
|
document.body.style.overflow = 'hidden';
|
|
143
|
-
|
|
186
|
+
this.zoomSrc = img.currentSrc || img.src;
|
|
187
|
+
const rect = img.getBoundingClientRect();
|
|
144
188
|
|
|
145
189
|
// 2. Prepare the zoomed image (hidden but in DOM)
|
|
146
190
|
this.style = 'opacity: 0; transition: none;';
|
|
@@ -176,7 +220,9 @@ const captionHtml =
|
|
|
176
220
|
},
|
|
177
221
|
close() {
|
|
178
222
|
document.body.style.overflow = 'auto';
|
|
179
|
-
const
|
|
223
|
+
const img = this.activeImage();
|
|
224
|
+
if (!img) return;
|
|
225
|
+
const rect = img.getBoundingClientRect();
|
|
180
226
|
const zRect = this.$refs.zoomedImg.getBoundingClientRect();
|
|
181
227
|
|
|
182
228
|
const scale = rect.width / zRect.width;
|
|
@@ -194,25 +240,42 @@ const captionHtml =
|
|
|
194
240
|
}"
|
|
195
241
|
>
|
|
196
242
|
<div
|
|
197
|
-
class="overflow-hidden rounded-
|
|
243
|
+
class="overflow-hidden rounded-[11px]"
|
|
198
244
|
>
|
|
199
245
|
<img
|
|
200
|
-
{...
|
|
201
|
-
x-ref="
|
|
246
|
+
{...lightImageAttrs}
|
|
247
|
+
x-ref="lightImg"
|
|
202
248
|
title={title}
|
|
203
249
|
class:list={[
|
|
204
250
|
"h-auto my-0! block transition-opacity",
|
|
251
|
+
hasDarkSource && "dark:hidden",
|
|
205
252
|
zoomEnabled && "cursor-zoom-in",
|
|
206
253
|
hasCustomImageWidth ? "max-w-full" : "w-full",
|
|
207
254
|
]}
|
|
208
255
|
:class="showZoomed ? 'opacity-0 duration-0' : 'opacity-100 duration-300'"
|
|
209
256
|
@click={zoomEnabled ? "zoom()" : undefined}
|
|
210
257
|
/>
|
|
258
|
+
{
|
|
259
|
+
darkImageAttrs && (
|
|
260
|
+
<img
|
|
261
|
+
{...darkImageAttrs}
|
|
262
|
+
x-ref="darkImg"
|
|
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
|
+
/>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
211
274
|
</div>
|
|
212
275
|
{
|
|
213
|
-
|
|
214
|
-
<figcaption class="mt-1! xs:mt-1.5! text-center text-xs! xs:text-sm! text-neutral-500 dark:text-neutral-400
|
|
215
|
-
<
|
|
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} />
|
|
216
279
|
</figcaption>
|
|
217
280
|
)
|
|
218
281
|
}
|
|
@@ -243,6 +306,7 @@ const captionHtml =
|
|
|
243
306
|
<img
|
|
244
307
|
{...zoomAttrs}
|
|
245
308
|
x-ref="zoomedImg"
|
|
309
|
+
:src="zoomSrc || $el.getAttribute('src')"
|
|
246
310
|
class="relative z-10 max-w-full max-h-full object-contain rounded-2xl shadow-none will-change-transform pointer-events-none"
|
|
247
311
|
:style="style"
|
|
248
312
|
/>
|
|
@@ -21,14 +21,14 @@ validateProps(
|
|
|
21
21
|
<div
|
|
22
22
|
class:list={[
|
|
23
23
|
"relative pl-10 step-item pb-4 last:pb-0 space-y-4",
|
|
24
|
-
"before:absolute before:left-[
|
|
24
|
+
"before:mask-b-from-90% before:absolute before:left-[13.25px] before:top-[28px] before:bottom-0 before:w-[1.5px] before:bg-neutral-900/8 dark:before:bg-neutral-50/8",
|
|
25
25
|
]}
|
|
26
26
|
data-step-panel
|
|
27
27
|
>
|
|
28
28
|
<div
|
|
29
29
|
class:list={[
|
|
30
30
|
"flex items-center gap-1.5 not-prose",
|
|
31
|
-
"step-number before:
|
|
31
|
+
"step-number before:leading-none before:size-7 before:bg-neutral-900/6 dark:before:bg-neutral-50/6 before:rounded-full before:text-neutral-700 dark:before:text-neutral-100 before:flex before:items-center before:justify-center before:text-[13px] before:font-medium before:absolute before:left-0 before:top-0",
|
|
32
32
|
]}
|
|
33
33
|
>
|
|
34
34
|
<h3 class="text-lg font-semibold text-neutral-900 dark:text-neutral-100">
|
|
@@ -30,12 +30,33 @@ if (labels.length === 0) {
|
|
|
30
30
|
previousTab: null,
|
|
31
31
|
transitionDirection: 1,
|
|
32
32
|
isTransitioning: false,
|
|
33
|
-
transitionDurationMs:
|
|
33
|
+
transitionDurationMs: 400,
|
|
34
|
+
transitionEasing: 'cubic-bezier(0.22, 1, 0.36, 1)',
|
|
34
35
|
transitionTimeout: null,
|
|
35
36
|
containerHeight: 'auto',
|
|
36
37
|
markerStyle: { left: null, width: null },
|
|
37
38
|
resizeHandler: null,
|
|
39
|
+
readMotionTokens() {
|
|
40
|
+
const styles = window.getComputedStyle(document.documentElement);
|
|
41
|
+
const configuredDurationMs = Number.parseFloat(
|
|
42
|
+
styles.getPropertyValue('--rd-panel-transition-duration-ms'),
|
|
43
|
+
);
|
|
44
|
+
this.transitionDurationMs = Number.isFinite(configuredDurationMs)
|
|
45
|
+
? configuredDurationMs
|
|
46
|
+
: this.transitionDurationMs;
|
|
47
|
+
this.transitionEasing =
|
|
48
|
+
styles.getPropertyValue('--rd-panel-transition-easing').trim() ||
|
|
49
|
+
this.transitionEasing;
|
|
50
|
+
|
|
51
|
+
if (
|
|
52
|
+
typeof window.matchMedia === 'function' &&
|
|
53
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
54
|
+
) {
|
|
55
|
+
this.transitionDurationMs = 0;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
38
58
|
init() {
|
|
59
|
+
this.readMotionTokens();
|
|
39
60
|
this.resizeHandler = () => {
|
|
40
61
|
this.updateMarker(this.activeTab);
|
|
41
62
|
this.updateHeight(this.isTransitioning);
|
|
@@ -68,7 +89,7 @@ if (labels.length === 0) {
|
|
|
68
89
|
this.isTransitioning = true;
|
|
69
90
|
this.activeTab = index;
|
|
70
91
|
this.updateMarker(this.activeTab);
|
|
71
|
-
this.updateHeight(
|
|
92
|
+
this.updateHeight();
|
|
72
93
|
|
|
73
94
|
this.transitionTimeout = window.setTimeout(() => {
|
|
74
95
|
this.isTransitioning = false;
|
|
@@ -87,7 +108,7 @@ if (labels.length === 0) {
|
|
|
87
108
|
}
|
|
88
109
|
},
|
|
89
110
|
getPanelStyle(index) {
|
|
90
|
-
const base = 'position: absolute;
|
|
111
|
+
const base = 'position: absolute; top: 0; left: 0; right: 0; width: 100%;';
|
|
91
112
|
|
|
92
113
|
const isActive = index === this.activeTab;
|
|
93
114
|
const isPrevious = this.isTransitioning && index === this.previousTab;
|
|
@@ -105,29 +126,23 @@ if (labels.length === 0) {
|
|
|
105
126
|
this.transitionDirection === 1
|
|
106
127
|
? 'rd-tabs-slide-in-from-right'
|
|
107
128
|
: 'rd-tabs-slide-in-from-left';
|
|
108
|
-
return `${base} opacity: 1; pointer-events: auto; visibility: visible; z-index: 2; animation: ${animationName} ${this.transitionDurationMs}ms
|
|
129
|
+
return `${base} opacity: 1; pointer-events: auto; visibility: visible; z-index: 2; animation: ${animationName} ${this.transitionDurationMs}ms ${this.transitionEasing} both;`;
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
const animationName =
|
|
112
133
|
this.transitionDirection === 1
|
|
113
134
|
? 'rd-tabs-slide-out-to-left'
|
|
114
135
|
: 'rd-tabs-slide-out-to-right';
|
|
115
|
-
return `${base} opacity: 1; pointer-events: none; visibility: visible; z-index: 1; animation: ${animationName} ${this.transitionDurationMs}ms
|
|
136
|
+
return `${base} opacity: 1; pointer-events: none; visibility: visible; z-index: 1; animation: ${animationName} ${this.transitionDurationMs}ms ${this.transitionEasing} both;`;
|
|
116
137
|
},
|
|
117
|
-
updateHeight(
|
|
138
|
+
updateHeight() {
|
|
118
139
|
this.$nextTick(() => {
|
|
119
140
|
const activeSlide = this.$refs['content-' + this.activeTab];
|
|
120
141
|
if (!activeSlide) return;
|
|
142
|
+
const activeContent = activeSlide.querySelector('[data-rd-tabs-panel-content]');
|
|
143
|
+
const measuredElement = activeContent || activeSlide;
|
|
121
144
|
|
|
122
|
-
|
|
123
|
-
if (includePrevious && this.previousTab !== null) {
|
|
124
|
-
const previousSlide = this.$refs['content-' + this.previousTab];
|
|
125
|
-
if (previousSlide) {
|
|
126
|
-
nextHeight = Math.max(nextHeight, previousSlide.scrollHeight);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
this.containerHeight = nextHeight + 'px';
|
|
145
|
+
this.containerHeight = measuredElement.scrollHeight + 'px';
|
|
131
146
|
});
|
|
132
147
|
}
|
|
133
148
|
}"
|
|
@@ -167,19 +182,28 @@ class="rd-prose-block">
|
|
|
167
182
|
</ul>
|
|
168
183
|
|
|
169
184
|
<div
|
|
170
|
-
class="relative mt-4 overflow-
|
|
171
|
-
:style="'height: ' + containerHeight"
|
|
185
|
+
class="relative mt-4 overflow-visible transition-[height] motion-reduce:transition-none"
|
|
186
|
+
:style="'height: ' + containerHeight + '; transition-duration: ' + transitionDurationMs + 'ms; transition-timing-function: ' + transitionEasing + ';'"
|
|
172
187
|
>
|
|
173
|
-
|
|
174
|
-
<div
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
188
|
+
<div class="relative -mx-4 h-full overflow-x-clip overflow-y-visible">
|
|
189
|
+
<div class="relative mx-4 h-full">
|
|
190
|
+
{ tabContents.map((content, index) => (
|
|
191
|
+
<div
|
|
192
|
+
{...(index !== 0 ? { "x-cloak": true } : {})}
|
|
193
|
+
x-ref={`content-${index}`}
|
|
194
|
+
class="w-full"
|
|
195
|
+
:style={`getPanelStyle(${index})`}
|
|
196
|
+
style={index === 0 ? 'position: relative;' : ''}
|
|
197
|
+
>
|
|
198
|
+
<div
|
|
199
|
+
data-rd-tabs-panel-content
|
|
200
|
+
class="prose-rules w-full max-w-none! *:max-w-none!"
|
|
201
|
+
set:html={content}
|
|
202
|
+
/>
|
|
203
|
+
</div>
|
|
204
|
+
)) }
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
183
207
|
</div>
|
|
184
208
|
</div>
|
|
185
209
|
|
|
@@ -187,36 +211,44 @@ class="rd-prose-block">
|
|
|
187
211
|
@keyframes rd-tabs-slide-in-from-right {
|
|
188
212
|
from {
|
|
189
213
|
transform: translateX(100%);
|
|
214
|
+
opacity: 0;
|
|
190
215
|
}
|
|
191
216
|
to {
|
|
192
217
|
transform: translateX(0);
|
|
218
|
+
opacity: 1;
|
|
193
219
|
}
|
|
194
220
|
}
|
|
195
221
|
|
|
196
222
|
@keyframes rd-tabs-slide-in-from-left {
|
|
197
223
|
from {
|
|
198
224
|
transform: translateX(-100%);
|
|
225
|
+
opacity: 0;
|
|
199
226
|
}
|
|
200
227
|
to {
|
|
201
228
|
transform: translateX(0);
|
|
229
|
+
opacity: 1;
|
|
202
230
|
}
|
|
203
231
|
}
|
|
204
232
|
|
|
205
233
|
@keyframes rd-tabs-slide-out-to-left {
|
|
206
234
|
from {
|
|
207
235
|
transform: translateX(0);
|
|
236
|
+
opacity: 1;
|
|
208
237
|
}
|
|
209
238
|
to {
|
|
210
239
|
transform: translateX(-100%);
|
|
240
|
+
opacity: 0;
|
|
211
241
|
}
|
|
212
242
|
}
|
|
213
243
|
|
|
214
244
|
@keyframes rd-tabs-slide-out-to-right {
|
|
215
245
|
from {
|
|
216
246
|
transform: translateX(0);
|
|
247
|
+
opacity: 1;
|
|
217
248
|
}
|
|
218
249
|
to {
|
|
219
250
|
transform: translateX(100%);
|
|
251
|
+
opacity: 0;
|
|
220
252
|
}
|
|
221
253
|
}
|
|
222
254
|
</style>
|
|
@@ -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
|
+
}
|