radiant-docs 0.1.7 → 0.1.8
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 +28 -5
- package/package.json +3 -3
- package/template/astro.config.mjs +76 -3
- package/template/package-lock.json +924 -737
- package/template/package.json +7 -5
- package/template/scripts/generate-og-images.mjs +335 -0
- package/template/scripts/generate-og-metadata.mjs +173 -0
- package/template/scripts/rewrite-static-asset-host.mjs +408 -0
- package/template/scripts/stamp-image-versions.mjs +277 -0
- package/template/scripts/stamp-og-image-versions.mjs +199 -0
- package/template/scripts/stamp-pagefind-runtime-version.mjs +140 -0
- package/template/src/assets/fonts/geist-mono/cyrillic.woff2 +0 -0
- package/template/src/assets/fonts/geist-mono/latin-ext.woff2 +0 -0
- package/template/src/assets/fonts/geist-mono/latin.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/canadian-aboriginal.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/cherokee.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/latin-ext.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/latin.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/math.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/nushu.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/symbols.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/syriac.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/tifinagh.woff2 +0 -0
- package/template/src/assets/fonts/google-sans-flex/vietnamese.woff2 +0 -0
- package/template/src/components/Footer.astro +94 -0
- package/template/src/components/Header.astro +11 -66
- package/template/src/components/LogoLink.astro +103 -0
- package/template/src/components/MdxPage.astro +126 -11
- package/template/src/components/OpenApiPage.astro +1036 -69
- package/template/src/components/Search.astro +0 -2
- package/template/src/components/SidebarDropdown.astro +34 -14
- package/template/src/components/SidebarGroup.astro +3 -6
- package/template/src/components/SidebarLink.astro +22 -12
- package/template/src/components/SidebarMenu.astro +19 -16
- package/template/src/components/SidebarSegmented.astro +99 -0
- package/template/src/components/SidebarSubgroup.astro +12 -12
- package/template/src/components/ThemeSwitcher.astro +30 -7
- package/template/src/components/endpoint/PlaygroundBar.astro +32 -36
- package/template/src/components/endpoint/PlaygroundButton.astro +40 -4
- package/template/src/components/endpoint/PlaygroundField.astro +1068 -22
- package/template/src/components/endpoint/PlaygroundForm.astro +559 -61
- package/template/src/components/endpoint/RequestSnippets.astro +342 -193
- package/template/src/components/endpoint/ResponseDisplay.astro +161 -147
- package/template/src/components/endpoint/ResponseFieldTree.astro +134 -0
- package/template/src/components/endpoint/ResponseFields.astro +711 -68
- package/template/src/components/endpoint/ResponseSnippets.astro +299 -173
- package/template/src/components/sidebar/SidebarEndpointLink.astro +1 -1
- package/template/src/components/ui/CodeLanguageIcon.astro +19 -0
- package/template/src/components/ui/CodeTabEdge.astro +79 -0
- package/template/src/components/ui/Field.astro +103 -20
- package/template/src/components/ui/Icon.astro +32 -0
- package/template/src/components/ui/ListChevronsToggle.astro +31 -0
- package/template/src/components/ui/Tag.astro +1 -1
- package/template/src/components/user/{Accordian.astro → Accordion.astro} +6 -6
- package/template/src/components/user/Callout.astro +5 -9
- package/template/src/components/user/CodeBlock.astro +400 -0
- package/template/src/components/user/CodeGroup.astro +225 -0
- package/template/src/components/user/ComponentPreview.astro +1 -0
- package/template/src/components/user/ComponentPreviewBlock.astro +181 -0
- package/template/src/components/user/Image.astro +132 -0
- package/template/src/components/user/Steps.astro +1 -3
- package/template/src/components/user/Tabs.astro +2 -2
- package/template/src/content.config.ts +1 -0
- package/template/src/layouts/Layout.astro +109 -8
- package/template/src/lib/code/code-block.ts +546 -0
- package/template/src/lib/frontmatter-schema.ts +8 -7
- package/template/src/lib/mdx/remark-code-block-component.ts +342 -0
- package/template/src/lib/mdx/remark-demote-h1.ts +16 -0
- package/template/src/lib/pagefind.ts +19 -5
- package/template/src/lib/routes.ts +49 -31
- package/template/src/lib/utils.ts +20 -0
- package/template/src/lib/validation.ts +638 -200
- package/template/src/pages/[...slug].astro +18 -5
- package/template/src/styles/geist-mono.css +33 -0
- package/template/src/styles/global.css +89 -84
- package/template/src/styles/google-sans-flex.css +143 -0
- package/template/ec.config.mjs +0 -51
- /package/template/src/components/user/{AccordianGroup.astro → AccordionGroup.astro} +0 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
---
|
|
2
|
+
import CodeLanguageIcon from "../ui/CodeLanguageIcon.astro";
|
|
3
|
+
import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
4
|
+
import { Icon } from "astro-icon/components";
|
|
5
|
+
import {
|
|
6
|
+
buildDefaultCodeFileName,
|
|
7
|
+
getCodeLineTokens,
|
|
8
|
+
normalizeCodeLanguageValue,
|
|
9
|
+
} from "../../lib/code/code-block";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
language?: string;
|
|
13
|
+
raw?: string;
|
|
14
|
+
filename?: string;
|
|
15
|
+
showFilename?: boolean | string;
|
|
16
|
+
showLineNumbers?: boolean | string;
|
|
17
|
+
hideLanguageIcon?: boolean | string;
|
|
18
|
+
inCodeGroup?: boolean | string;
|
|
19
|
+
highlightedLines?: string;
|
|
20
|
+
collapsedLines?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
language = "plaintext",
|
|
25
|
+
raw = "",
|
|
26
|
+
filename = "",
|
|
27
|
+
showFilename = false,
|
|
28
|
+
showLineNumbers = false,
|
|
29
|
+
hideLanguageIcon = false,
|
|
30
|
+
inCodeGroup = false,
|
|
31
|
+
highlightedLines = "",
|
|
32
|
+
collapsedLines = "",
|
|
33
|
+
} = Astro.props as Props;
|
|
34
|
+
|
|
35
|
+
function toBoolean(value: boolean | string | undefined, defaultValue = false) {
|
|
36
|
+
if (typeof value === "boolean") return value;
|
|
37
|
+
if (typeof value === "string") {
|
|
38
|
+
const normalized = value.trim().toLowerCase();
|
|
39
|
+
if (normalized === "true") return true;
|
|
40
|
+
if (normalized === "false") return false;
|
|
41
|
+
}
|
|
42
|
+
return defaultValue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeHighlightedLinesSpec(value: string): string | null {
|
|
46
|
+
const normalizedValue = value.trim();
|
|
47
|
+
if (!normalizedValue) return null;
|
|
48
|
+
|
|
49
|
+
const segments = normalizedValue.split(",");
|
|
50
|
+
const normalizedSegments: string[] = [];
|
|
51
|
+
|
|
52
|
+
for (const segment of segments) {
|
|
53
|
+
const trimmedSegment = segment.trim();
|
|
54
|
+
if (!trimmedSegment) continue;
|
|
55
|
+
|
|
56
|
+
if (/^\d+$/.test(trimmedSegment)) {
|
|
57
|
+
const lineNumber = Number.parseInt(trimmedSegment, 10);
|
|
58
|
+
if (lineNumber > 0) normalizedSegments.push(String(lineNumber));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const rangeMatch = trimmedSegment.match(/^(\d+)\s*-\s*(\d+)$/);
|
|
63
|
+
if (!rangeMatch) continue;
|
|
64
|
+
|
|
65
|
+
const start = Number.parseInt(rangeMatch[1], 10);
|
|
66
|
+
const end = Number.parseInt(rangeMatch[2], 10);
|
|
67
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) continue;
|
|
68
|
+
if (start <= 0 || end <= 0) continue;
|
|
69
|
+
|
|
70
|
+
const rangeStart = Math.min(start, end);
|
|
71
|
+
const rangeEnd = Math.max(start, end);
|
|
72
|
+
normalizedSegments.push(
|
|
73
|
+
rangeStart === rangeEnd
|
|
74
|
+
? String(rangeStart)
|
|
75
|
+
: `${rangeStart}-${rangeEnd}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!normalizedSegments.length) return null;
|
|
80
|
+
return normalizedSegments.join(",");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseHighlightedLineNumbers(
|
|
84
|
+
lineSpec: string,
|
|
85
|
+
lineCount: number,
|
|
86
|
+
): Set<number> {
|
|
87
|
+
const normalizedSpec = normalizeHighlightedLinesSpec(lineSpec);
|
|
88
|
+
if (!normalizedSpec) return new Set();
|
|
89
|
+
|
|
90
|
+
const highlightedLineSet = new Set<number>();
|
|
91
|
+
for (const segment of normalizedSpec.split(",")) {
|
|
92
|
+
if (/^\d+$/.test(segment)) {
|
|
93
|
+
const lineNumber = Number.parseInt(segment, 10);
|
|
94
|
+
if (lineNumber >= 1 && lineNumber <= lineCount) {
|
|
95
|
+
highlightedLineSet.add(lineNumber);
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const rangeMatch = segment.match(/^(\d+)-(\d+)$/);
|
|
101
|
+
if (!rangeMatch) continue;
|
|
102
|
+
|
|
103
|
+
const start = Number.parseInt(rangeMatch[1], 10);
|
|
104
|
+
const end = Number.parseInt(rangeMatch[2], 10);
|
|
105
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) continue;
|
|
106
|
+
|
|
107
|
+
const rangeStart = Math.max(1, Math.min(start, end));
|
|
108
|
+
const rangeEnd = Math.min(lineCount, Math.max(start, end));
|
|
109
|
+
for (let lineNumber = rangeStart; lineNumber <= rangeEnd; lineNumber += 1) {
|
|
110
|
+
highlightedLineSet.add(lineNumber);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return highlightedLineSet;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildTokenStyle(token: {
|
|
118
|
+
color?: string;
|
|
119
|
+
bgColor?: string;
|
|
120
|
+
fontStyle?: number;
|
|
121
|
+
htmlStyle?: Record<string, string>;
|
|
122
|
+
}): string | undefined {
|
|
123
|
+
const styleSegments: string[] = [];
|
|
124
|
+
|
|
125
|
+
if (token.color) styleSegments.push(`color:${token.color}`);
|
|
126
|
+
if (token.bgColor) styleSegments.push(`background-color:${token.bgColor}`);
|
|
127
|
+
|
|
128
|
+
const fontStyle = typeof token.fontStyle === "number" ? token.fontStyle : 0;
|
|
129
|
+
if ((fontStyle & 1) === 1) styleSegments.push("font-style:italic");
|
|
130
|
+
if ((fontStyle & 2) === 2) styleSegments.push("font-weight:600");
|
|
131
|
+
if ((fontStyle & 4) === 4) styleSegments.push("text-decoration:underline");
|
|
132
|
+
|
|
133
|
+
if (token.htmlStyle && typeof token.htmlStyle === "object") {
|
|
134
|
+
for (const [property, value] of Object.entries(token.htmlStyle)) {
|
|
135
|
+
styleSegments.push(`${property}:${value}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!styleSegments.length) return undefined;
|
|
140
|
+
return styleSegments.join(";");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function escapeHtml(value: string): string {
|
|
144
|
+
return value
|
|
145
|
+
.replaceAll("&", "&")
|
|
146
|
+
.replaceAll("<", "<")
|
|
147
|
+
.replaceAll(">", ">");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function escapeAttribute(value: string): string {
|
|
151
|
+
return escapeHtml(value).replaceAll('"', """);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const normalizedLanguage = normalizeCodeLanguageValue(language);
|
|
155
|
+
const parsedInCodeGroup = toBoolean(inCodeGroup, false);
|
|
156
|
+
const parsedShowFilename = parsedInCodeGroup
|
|
157
|
+
? true
|
|
158
|
+
: toBoolean(showFilename, false);
|
|
159
|
+
const parsedShowLineNumbers = toBoolean(showLineNumbers, false);
|
|
160
|
+
const parsedHideLanguageIcon = toBoolean(hideLanguageIcon, false);
|
|
161
|
+
const shouldRenderLanguageIcon = !parsedHideLanguageIcon;
|
|
162
|
+
|
|
163
|
+
const trimmedFilename = filename.trim();
|
|
164
|
+
const displayFilename =
|
|
165
|
+
trimmedFilename.length > 0
|
|
166
|
+
? trimmedFilename
|
|
167
|
+
: buildDefaultCodeFileName(normalizedLanguage);
|
|
168
|
+
|
|
169
|
+
const { lines: tokenLines } = await getCodeLineTokens({
|
|
170
|
+
code: raw,
|
|
171
|
+
language: normalizedLanguage,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const rawLines = raw.split("\n");
|
|
175
|
+
const normalizedRawLines = rawLines.length > 0 ? rawLines : [""];
|
|
176
|
+
const lineCount = Math.max(1, normalizedRawLines.length, tokenLines.length);
|
|
177
|
+
const normalizedTokenLines = Array.from(
|
|
178
|
+
{ length: lineCount },
|
|
179
|
+
(_, lineIndex) => {
|
|
180
|
+
return tokenLines[lineIndex] ?? [];
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const highlightSet = parseHighlightedLineNumbers(highlightedLines, lineCount);
|
|
185
|
+
const encodedRaw = encodeURIComponent(raw);
|
|
186
|
+
const collapsedLinesValue = collapsedLines.trim();
|
|
187
|
+
const renderedCodeLinesHtml = normalizedTokenLines
|
|
188
|
+
.map((lineTokens, lineIndex) => {
|
|
189
|
+
const lineNumber = lineIndex + 1;
|
|
190
|
+
const isHighlighted = highlightSet.has(lineNumber);
|
|
191
|
+
const fallbackLineContent = normalizedRawLines[lineIndex] ?? "";
|
|
192
|
+
|
|
193
|
+
const tokenHtml =
|
|
194
|
+
lineTokens.length > 0
|
|
195
|
+
? lineTokens
|
|
196
|
+
.map((token) => {
|
|
197
|
+
const tokenStyle = buildTokenStyle(token);
|
|
198
|
+
const tokenStyleAttribute = tokenStyle
|
|
199
|
+
? ` style="${escapeAttribute(tokenStyle)}"`
|
|
200
|
+
: "";
|
|
201
|
+
return `<span${tokenStyleAttribute}>${escapeHtml(token.content)}</span>`;
|
|
202
|
+
})
|
|
203
|
+
.join("")
|
|
204
|
+
: fallbackLineContent.length > 0
|
|
205
|
+
? escapeHtml(fallbackLineContent)
|
|
206
|
+
: " ";
|
|
207
|
+
|
|
208
|
+
const lineNumberHtml = parsedShowLineNumbers
|
|
209
|
+
? `<span class="w-10 shrink-0 select-none pl-4 pr-3 text-right tabular-nums text-neutral-400">${lineNumber}</span>`
|
|
210
|
+
: "";
|
|
211
|
+
|
|
212
|
+
const lineContentClass = parsedShowLineNumbers
|
|
213
|
+
? "flex-1 whitespace-pre pr-4"
|
|
214
|
+
: "flex-1 whitespace-pre pr-4 pl-4";
|
|
215
|
+
const lineClass = isHighlighted
|
|
216
|
+
? "flex min-w-full bg-neutral-100/80"
|
|
217
|
+
: "flex min-w-full";
|
|
218
|
+
|
|
219
|
+
return `<span class="${lineClass}">${lineNumberHtml}<span class="${lineContentClass}">${tokenHtml}</span></span>`;
|
|
220
|
+
})
|
|
221
|
+
.join("");
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
<div
|
|
225
|
+
class:list={[
|
|
226
|
+
"group/prose-code not-prose relative w-full max-w-full min-w-0",
|
|
227
|
+
parsedInCodeGroup ? "my-0" : "my-6",
|
|
228
|
+
]}
|
|
229
|
+
data-rd-code-block-root="true"
|
|
230
|
+
data-rd-collapsed-lines={collapsedLinesValue}
|
|
231
|
+
data-rd-copy-content={encodedRaw}
|
|
232
|
+
data-rd-code-group-item={parsedInCodeGroup ? "true" : undefined}
|
|
233
|
+
data-rd-tab-filename={parsedInCodeGroup ? displayFilename : undefined}
|
|
234
|
+
data-rd-tab-language={parsedInCodeGroup ? normalizedLanguage : undefined}
|
|
235
|
+
data-rd-tab-hide-icon={parsedInCodeGroup && parsedHideLanguageIcon
|
|
236
|
+
? "true"
|
|
237
|
+
: undefined}
|
|
238
|
+
>
|
|
239
|
+
<div
|
|
240
|
+
class:list={[
|
|
241
|
+
"w-full max-w-full min-w-0 overflow-hidden border border-neutral-200 bg-white shadow-xs",
|
|
242
|
+
parsedInCodeGroup ? "rounded-t-none rounded-b-xl" : "rounded-xl",
|
|
243
|
+
]}
|
|
244
|
+
>
|
|
245
|
+
{
|
|
246
|
+
!parsedInCodeGroup && parsedShowFilename ? (
|
|
247
|
+
<div class="flex items-center justify-between gap-2 border-b border-neutral-200 bg-neutral-50 inset-shadow-sm inset-shadow-neutral-100/80">
|
|
248
|
+
<div class="min-w-0 flex-1">
|
|
249
|
+
<div class="relative h-9 w-fit max-w-full rounded-tl-xl bg-white">
|
|
250
|
+
<div class="absolute inset-x-0 -bottom-px h-px bg-white" />
|
|
251
|
+
<CodeTabEdge className="pointer-events-none absolute -top-px left-full z-10 h-[calc(100%+2px)]" />
|
|
252
|
+
<div class="relative z-20 inline-flex h-9 max-w-full items-center gap-2 pl-5 py-1.5 text-xs font-medium text-neutral-700">
|
|
253
|
+
{shouldRenderLanguageIcon ? (
|
|
254
|
+
<CodeLanguageIcon
|
|
255
|
+
language={normalizedLanguage}
|
|
256
|
+
fileName={displayFilename}
|
|
257
|
+
className="size-3.5 shrink-0 self-center rounded-[4px]"
|
|
258
|
+
/>
|
|
259
|
+
) : null}
|
|
260
|
+
<span class="truncate leading-none">{displayFilename}</span>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
<div class="relative h-9 w-5 shrink-0 rounded-tr-xl bg-white">
|
|
265
|
+
<div class="absolute inset-x-0 -bottom-px h-px bg-white" />
|
|
266
|
+
<CodeTabEdge className="pointer-events-none absolute -top-px right-full z-10 h-[calc(100%+2px)] rotate-y-180" />
|
|
267
|
+
<button
|
|
268
|
+
type="button"
|
|
269
|
+
class="absolute right-2 top-1/2 z-20 inline-flex size-7 -translate-y-1/2 appearance-none items-center justify-center rounded-md border-0 bg-transparent text-neutral-500/80 shadow-none outline-none ring-0 transition-colors duration-150 hover:bg-neutral-50 hover:text-neutral-600 focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0 cursor-pointer"
|
|
270
|
+
data-rd-copy-trigger="true"
|
|
271
|
+
data-rd-copy-content={encodedRaw}
|
|
272
|
+
aria-label="Copy code"
|
|
273
|
+
>
|
|
274
|
+
<Icon
|
|
275
|
+
name="lucide:copy"
|
|
276
|
+
class="size-3.5 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
277
|
+
data-rd-copy-icon
|
|
278
|
+
aria-hidden="true"
|
|
279
|
+
/>
|
|
280
|
+
<Icon
|
|
281
|
+
name="lucide:check"
|
|
282
|
+
class="absolute size-3.5 stroke-3 origin-center scale-25 rotate-6 opacity-0 text-green-700/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
283
|
+
data-rd-copy-check
|
|
284
|
+
aria-hidden="true"
|
|
285
|
+
/>
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
288
|
+
</div>
|
|
289
|
+
) : null
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
<div class="relative min-w-0">
|
|
293
|
+
{
|
|
294
|
+
!parsedInCodeGroup && !parsedShowFilename ? (
|
|
295
|
+
<div class="pointer-events-none absolute right-2 top-2 z-20">
|
|
296
|
+
<button
|
|
297
|
+
type="button"
|
|
298
|
+
class="pointer-events-auto inline-flex size-7 appearance-none items-center justify-center rounded-md border border-neutral-200/80 text-neutral-500/80 bg-white/80 backdrop-blur-xs outline-none ring-0 transition-colors duration-150 hover:bg-neutral-50 hover:text-neutral-600 focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0 cursor-pointer"
|
|
299
|
+
data-rd-copy-trigger="true"
|
|
300
|
+
data-rd-copy-content={encodedRaw}
|
|
301
|
+
aria-label="Copy code"
|
|
302
|
+
>
|
|
303
|
+
<Icon
|
|
304
|
+
name="lucide:copy"
|
|
305
|
+
class="size-3.5 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
306
|
+
data-rd-copy-icon
|
|
307
|
+
aria-hidden="true"
|
|
308
|
+
/>
|
|
309
|
+
<Icon
|
|
310
|
+
name="lucide:check"
|
|
311
|
+
class="absolute size-3.5 stroke-3 origin-center scale-25 rotate-6 opacity-0 text-green-700/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
312
|
+
data-rd-copy-check
|
|
313
|
+
aria-hidden="true"
|
|
314
|
+
/>
|
|
315
|
+
</button>
|
|
316
|
+
</div>
|
|
317
|
+
) : null
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
{
|
|
321
|
+
parsedInCodeGroup ? (
|
|
322
|
+
<template data-rd-code-group-tab-icon>
|
|
323
|
+
{shouldRenderLanguageIcon ? (
|
|
324
|
+
<CodeLanguageIcon
|
|
325
|
+
language={normalizedLanguage}
|
|
326
|
+
fileName={displayFilename}
|
|
327
|
+
className="size-3.5 shrink-0 self-center rounded-[4px]"
|
|
328
|
+
/>
|
|
329
|
+
) : null}
|
|
330
|
+
</template>
|
|
331
|
+
) : null
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
<div
|
|
335
|
+
data-rd-code-scroll-area
|
|
336
|
+
class="relative overflow-x-auto [scrollbar-width:thin] [scrollbar-color:var(--color-neutral-300)_transparent] [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-neutral-300/70 hover:[&::-webkit-scrollbar-thumb]:bg-neutral-300/90"
|
|
337
|
+
>
|
|
338
|
+
<pre
|
|
339
|
+
class="relative m-0 min-w-full bg-white p-0 text-[13px] leading-6"><code class="block min-w-full py-2.5 font-mono text-neutral-800"><Fragment set:html={renderedCodeLinesHtml} /></code></pre>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
<script is:inline>
|
|
346
|
+
(() => {
|
|
347
|
+
const script = document.currentScript;
|
|
348
|
+
if (!(script instanceof HTMLScriptElement)) return;
|
|
349
|
+
|
|
350
|
+
const root = script.previousElementSibling;
|
|
351
|
+
if (!(root instanceof HTMLElement)) return;
|
|
352
|
+
|
|
353
|
+
const copyButtons = root.querySelectorAll("[data-rd-copy-trigger='true']");
|
|
354
|
+
if (copyButtons.length === 0) return;
|
|
355
|
+
|
|
356
|
+
const setCopiedState = (button, copied) => {
|
|
357
|
+
const copyIcon = button.querySelector("[data-rd-copy-icon]");
|
|
358
|
+
const checkIcon = button.querySelector("[data-rd-copy-check]");
|
|
359
|
+
|
|
360
|
+
if (!copyIcon || !checkIcon) return;
|
|
361
|
+
|
|
362
|
+
if (copied) {
|
|
363
|
+
copyIcon.classList.add("scale-50", "opacity-0", "-rotate-6");
|
|
364
|
+
copyIcon.classList.remove("scale-100", "opacity-100", "rotate-0");
|
|
365
|
+
|
|
366
|
+
checkIcon.classList.remove("scale-50", "opacity-0", "rotate-6");
|
|
367
|
+
checkIcon.classList.add("scale-110", "opacity-100", "rotate-0");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
copyIcon.classList.remove("scale-50", "opacity-0", "-rotate-6");
|
|
372
|
+
copyIcon.classList.add("scale-100", "opacity-100", "rotate-0");
|
|
373
|
+
|
|
374
|
+
checkIcon.classList.remove("scale-110", "opacity-100", "rotate-0");
|
|
375
|
+
checkIcon.classList.add("scale-50", "opacity-0", "rotate-6");
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
copyButtons.forEach((button) => {
|
|
379
|
+
let timeoutId = null;
|
|
380
|
+
button.addEventListener("click", async () => {
|
|
381
|
+
const encodedCopyValue =
|
|
382
|
+
button.getAttribute("data-rd-copy-content") ?? "";
|
|
383
|
+
const copyValue = decodeURIComponent(encodedCopyValue);
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
await navigator.clipboard.writeText(copyValue);
|
|
387
|
+
setCopiedState(button, true);
|
|
388
|
+
|
|
389
|
+
if (timeoutId) window.clearTimeout(timeoutId);
|
|
390
|
+
timeoutId = window.setTimeout(() => {
|
|
391
|
+
setCopiedState(button, false);
|
|
392
|
+
timeoutId = null;
|
|
393
|
+
}, 1200);
|
|
394
|
+
} catch {
|
|
395
|
+
setCopiedState(button, false);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
})();
|
|
400
|
+
</script>
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Icon } from "astro-icon/components";
|
|
3
|
+
import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<div
|
|
7
|
+
class="group/prose-code-group not-prose relative my-6 w-full max-w-full min-w-0"
|
|
8
|
+
data-rd-code-group-root="true"
|
|
9
|
+
>
|
|
10
|
+
<div
|
|
11
|
+
class="relative z-10 overflow-visible rounded-t-xl border border-b-0 border-neutral-200 bg-neutral-50 inset-shadow-sm inset-shadow-neutral-100/80"
|
|
12
|
+
>
|
|
13
|
+
<div class="flex min-w-0 items-end justify-between gap-2">
|
|
14
|
+
<div class="min-w-0 flex-1 overflow-hidden rounded-t-xl">
|
|
15
|
+
<div
|
|
16
|
+
data-rd-code-group-tabs
|
|
17
|
+
class="relative flex min-w-0 items-end gap-1 overflow-x-auto pl-1 pr-8 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
18
|
+
>
|
|
19
|
+
<div
|
|
20
|
+
data-rd-code-group-pill
|
|
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-neutral-200 bg-white shadow-xs opacity-0 transition-[left,width,opacity] duration-200 ease-out"
|
|
23
|
+
>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="relative h-9 w-5 shrink-0 rounded-tr-xl bg-white">
|
|
29
|
+
<div class="absolute inset-x-0 -bottom-px h-px bg-white"></div>
|
|
30
|
+
<CodeTabEdge
|
|
31
|
+
className="pointer-events-none absolute -top-px right-full z-10 h-[calc(100%+2px)] rotate-y-180"
|
|
32
|
+
/>
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
35
|
+
class="absolute right-2 top-1/2 z-20 inline-flex size-7 -translate-y-1/2 appearance-none items-center justify-center rounded-md border-0 bg-transparent text-neutral-500/80 shadow-none outline-none ring-0 transition-colors duration-150 hover:bg-neutral-50 hover:text-neutral-600 focus:outline-none focus-visible:outline-none focus:ring-0 focus-visible:ring-0 cursor-pointer"
|
|
36
|
+
data-rd-copy-trigger="true"
|
|
37
|
+
aria-label="Copy code"
|
|
38
|
+
>
|
|
39
|
+
<Icon
|
|
40
|
+
name="lucide:copy"
|
|
41
|
+
class="size-3.5 origin-center scale-100 rotate-0 opacity-100 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
42
|
+
data-rd-copy-icon
|
|
43
|
+
aria-hidden="true"
|
|
44
|
+
/>
|
|
45
|
+
<Icon
|
|
46
|
+
name="lucide:check"
|
|
47
|
+
class="absolute size-3.5 stroke-3 origin-center scale-25 rotate-6 opacity-0 text-green-700/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
48
|
+
data-rd-copy-check
|
|
49
|
+
aria-hidden="true"
|
|
50
|
+
/>
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div data-rd-code-group-content class="min-w-0">
|
|
57
|
+
<slot />
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<script is:inline>
|
|
62
|
+
(() => {
|
|
63
|
+
const script = document.currentScript;
|
|
64
|
+
if (!(script instanceof HTMLScriptElement)) return;
|
|
65
|
+
|
|
66
|
+
const root = script.previousElementSibling;
|
|
67
|
+
if (!(root instanceof HTMLElement)) return;
|
|
68
|
+
|
|
69
|
+
const contentElement = root.querySelector("[data-rd-code-group-content]");
|
|
70
|
+
const tabsElement = root.querySelector("[data-rd-code-group-tabs]");
|
|
71
|
+
const pillElement = root.querySelector("[data-rd-code-group-pill]");
|
|
72
|
+
const copyButton = root.querySelector("[data-rd-copy-trigger='true']");
|
|
73
|
+
|
|
74
|
+
if (!contentElement || !tabsElement || !pillElement) return;
|
|
75
|
+
|
|
76
|
+
const codeItems = Array.from(
|
|
77
|
+
contentElement.querySelectorAll("[data-rd-code-group-item='true']"),
|
|
78
|
+
);
|
|
79
|
+
if (codeItems.length === 0) return;
|
|
80
|
+
|
|
81
|
+
let activeIndex = 0;
|
|
82
|
+
|
|
83
|
+
const setCopiedState = (button, copied) => {
|
|
84
|
+
const copyIcon = button.querySelector("[data-rd-copy-icon]");
|
|
85
|
+
const checkIcon = button.querySelector("[data-rd-copy-check]");
|
|
86
|
+
if (!copyIcon || !checkIcon) return;
|
|
87
|
+
|
|
88
|
+
if (copied) {
|
|
89
|
+
copyIcon.classList.add("scale-50", "opacity-0", "-rotate-6");
|
|
90
|
+
copyIcon.classList.remove("scale-100", "opacity-100", "rotate-0");
|
|
91
|
+
|
|
92
|
+
checkIcon.classList.remove("scale-50", "opacity-0", "rotate-6");
|
|
93
|
+
checkIcon.classList.add("scale-110", "opacity-100", "rotate-0");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
copyIcon.classList.remove("scale-50", "opacity-0", "-rotate-6");
|
|
98
|
+
copyIcon.classList.add("scale-100", "opacity-100", "rotate-0");
|
|
99
|
+
|
|
100
|
+
checkIcon.classList.remove("scale-110", "opacity-100", "rotate-0");
|
|
101
|
+
checkIcon.classList.add("scale-50", "opacity-0", "rotate-6");
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const tabs = codeItems.map((itemElement, index) => {
|
|
105
|
+
const filename =
|
|
106
|
+
itemElement.getAttribute("data-rd-tab-filename") ??
|
|
107
|
+
`file-name-${index + 1}.txt`;
|
|
108
|
+
const tabIconTemplate = itemElement.querySelector(
|
|
109
|
+
"template[data-rd-code-group-tab-icon]",
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const tabWrapper = document.createElement("div");
|
|
113
|
+
tabWrapper.className = "relative z-10 text-xs font-medium";
|
|
114
|
+
|
|
115
|
+
const tabButton = document.createElement("button");
|
|
116
|
+
tabButton.type = "button";
|
|
117
|
+
tabButton.className =
|
|
118
|
+
"relative inline-flex h-9 items-center gap-2 border-0 bg-transparent px-3 py-1.5 text-xs font-medium text-neutral-600 transition-colors duration-150 focus:outline-none focus-visible:outline-none cursor-pointer";
|
|
119
|
+
tabButton.setAttribute("aria-label", filename);
|
|
120
|
+
tabButton.setAttribute("data-rd-code-group-tab", String(index));
|
|
121
|
+
|
|
122
|
+
const iconContainer = document.createElement("span");
|
|
123
|
+
iconContainer.className =
|
|
124
|
+
"pointer-events-none inline-flex shrink-0 items-center rounded-[4px] transition-opacity duration-150";
|
|
125
|
+
|
|
126
|
+
if (tabIconTemplate && tabIconTemplate.innerHTML.trim().length > 0) {
|
|
127
|
+
iconContainer.innerHTML = tabIconTemplate.innerHTML;
|
|
128
|
+
} else {
|
|
129
|
+
iconContainer.classList.add("hidden");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const labelElement = document.createElement("span");
|
|
133
|
+
labelElement.className = "whitespace-pre leading-none";
|
|
134
|
+
labelElement.textContent = filename;
|
|
135
|
+
|
|
136
|
+
tabButton.appendChild(iconContainer);
|
|
137
|
+
tabButton.appendChild(labelElement);
|
|
138
|
+
tabWrapper.appendChild(tabButton);
|
|
139
|
+
tabsElement.appendChild(tabWrapper);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
itemElement,
|
|
143
|
+
tabWrapper,
|
|
144
|
+
tabButton,
|
|
145
|
+
iconContainer,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const syncPill = () => {
|
|
150
|
+
const activeTab = tabs[activeIndex]?.tabWrapper;
|
|
151
|
+
if (!activeTab) {
|
|
152
|
+
pillElement.classList.add("opacity-0");
|
|
153
|
+
pillElement.classList.remove("opacity-100");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const activeRect = activeTab.getBoundingClientRect();
|
|
158
|
+
const tabsRect = tabsElement.getBoundingClientRect();
|
|
159
|
+
const left = activeRect.left - tabsRect.left + tabsElement.scrollLeft;
|
|
160
|
+
const width = activeRect.width;
|
|
161
|
+
pillElement.style.left = `${left}px`;
|
|
162
|
+
pillElement.style.width = `${width}px`;
|
|
163
|
+
pillElement.classList.remove("opacity-0");
|
|
164
|
+
pillElement.classList.add("opacity-100");
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const syncActiveTab = () => {
|
|
168
|
+
tabs.forEach(({ itemElement, tabButton, iconContainer }, index) => {
|
|
169
|
+
const isActive = index === activeIndex;
|
|
170
|
+
itemElement.style.display = isActive ? "" : "none";
|
|
171
|
+
tabButton.classList.toggle("text-neutral-900", isActive);
|
|
172
|
+
tabButton.classList.toggle("text-neutral-600", !isActive);
|
|
173
|
+
|
|
174
|
+
if (iconContainer.classList.contains("hidden")) return;
|
|
175
|
+
iconContainer.classList.toggle("opacity-100", isActive);
|
|
176
|
+
iconContainer.classList.toggle("opacity-80", !isActive);
|
|
177
|
+
});
|
|
178
|
+
syncPill();
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
tabs.forEach(({ tabButton }, index) => {
|
|
182
|
+
tabButton.addEventListener("click", () => {
|
|
183
|
+
if (activeIndex === index) return;
|
|
184
|
+
activeIndex = index;
|
|
185
|
+
syncActiveTab();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (copyButton) {
|
|
190
|
+
let timeoutId = null;
|
|
191
|
+
copyButton.addEventListener("click", async () => {
|
|
192
|
+
const activeItem = tabs[activeIndex]?.itemElement;
|
|
193
|
+
const encodedCopyValue =
|
|
194
|
+
activeItem?.getAttribute("data-rd-copy-content") ?? "";
|
|
195
|
+
const copyValue = decodeURIComponent(encodedCopyValue);
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await navigator.clipboard.writeText(copyValue);
|
|
199
|
+
setCopiedState(copyButton, true);
|
|
200
|
+
|
|
201
|
+
if (timeoutId) window.clearTimeout(timeoutId);
|
|
202
|
+
timeoutId = window.setTimeout(() => {
|
|
203
|
+
setCopiedState(copyButton, false);
|
|
204
|
+
timeoutId = null;
|
|
205
|
+
}, 1200);
|
|
206
|
+
} catch {
|
|
207
|
+
setCopiedState(copyButton, false);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const resizeObserver =
|
|
213
|
+
typeof ResizeObserver !== "undefined"
|
|
214
|
+
? new ResizeObserver(() => syncPill())
|
|
215
|
+
: null;
|
|
216
|
+
if (resizeObserver) {
|
|
217
|
+
resizeObserver.observe(tabsElement);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
tabsElement.addEventListener("scroll", syncPill, { passive: true });
|
|
221
|
+
window.addEventListener("resize", syncPill);
|
|
222
|
+
syncActiveTab();
|
|
223
|
+
requestAnimationFrame(syncPill);
|
|
224
|
+
})();
|
|
225
|
+
</script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<slot />
|