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
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { Code } from "astro-expressive-code/components";
|
|
3
2
|
import { Icon } from "astro-icon/components";
|
|
4
|
-
import { methodColors } from "../../lib/utils";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { resolve, dirname } from "node:path";
|
|
8
3
|
import oasToSnippet from "@readme/oas-to-snippet";
|
|
9
4
|
import type { Language } from "@readme/oas-to-snippet/languages";
|
|
10
5
|
import type { DataForHAR, HttpMethods, MediaTypeObject } from "oas/types";
|
|
11
6
|
import * as Sampler from "openapi-sampler";
|
|
12
7
|
import type { JSONSchema7 } from "json-schema";
|
|
13
8
|
import type Oas from "oas";
|
|
9
|
+
import { methodColors } from "../../lib/utils";
|
|
10
|
+
import CodeTabEdge from "../ui/CodeTabEdge.astro";
|
|
11
|
+
import {
|
|
12
|
+
buildDefaultCodeFileName,
|
|
13
|
+
getCodeLineTokens,
|
|
14
|
+
getLanguageIconSvg,
|
|
15
|
+
normalizeCodeLanguageValue,
|
|
16
|
+
} from "../../lib/code/code-block";
|
|
14
17
|
|
|
15
18
|
interface Props {
|
|
16
19
|
api: Oas;
|
|
@@ -22,16 +25,12 @@ const { method, path, api } = Astro.props;
|
|
|
22
25
|
|
|
23
26
|
const operation = api.operation(path, method as HttpMethods);
|
|
24
27
|
|
|
25
|
-
// Identify and build the Auth object
|
|
26
28
|
const auth: Record<string, string | number | { user?: string; pass?: string }> =
|
|
27
29
|
{};
|
|
28
30
|
const securityByType = operation.prepareSecurity();
|
|
29
31
|
Object.values(securityByType).forEach((schemes) => {
|
|
30
|
-
// schemes is an array of security scheme objects
|
|
31
32
|
(schemes as any[]).forEach((scheme) => {
|
|
32
33
|
const schemeName = scheme._key || scheme.name;
|
|
33
|
-
|
|
34
|
-
// Keep the original conditionals - they're correct!
|
|
35
34
|
if (scheme?.type === "http" && scheme?.scheme === "bearer") {
|
|
36
35
|
auth[schemeName] = "<BEARER_TOKEN>";
|
|
37
36
|
} else if (scheme?.type === "apiKey") {
|
|
@@ -42,37 +41,30 @@ Object.values(securityByType).forEach((schemes) => {
|
|
|
42
41
|
});
|
|
43
42
|
});
|
|
44
43
|
|
|
45
|
-
// 1. Initialize the values object
|
|
46
44
|
const values: DataForHAR = {
|
|
47
45
|
path: {},
|
|
48
46
|
query: {},
|
|
49
47
|
body: undefined,
|
|
50
48
|
};
|
|
51
49
|
|
|
52
|
-
// 2. Process Parameters (Path and Query)
|
|
53
50
|
const parameters = operation.getParameters();
|
|
54
51
|
parameters.forEach((param) => {
|
|
55
52
|
const name = param.name;
|
|
56
|
-
|
|
57
|
-
// Get example if it exists
|
|
58
53
|
const example =
|
|
59
54
|
param.example ||
|
|
60
55
|
(param.schema as any)?.example ||
|
|
61
56
|
(param.schema as any)?.default;
|
|
62
57
|
|
|
63
58
|
if (param.in === "path") {
|
|
64
|
-
// Per your request: Maintain curly braces for path
|
|
65
59
|
values.path![name] = `{${name}}`;
|
|
66
60
|
} else if (param.in === "query") {
|
|
67
|
-
// Precedence: Example -> Placeholder
|
|
68
61
|
values.query![name] = example ? String(example) : `<${name.toUpperCase()}>`;
|
|
69
62
|
}
|
|
70
63
|
});
|
|
71
64
|
|
|
72
|
-
// Process Request Body
|
|
73
65
|
if (operation.hasRequestBody()) {
|
|
74
66
|
const requestBody = operation.getRequestBody(
|
|
75
|
-
"application/json"
|
|
67
|
+
"application/json",
|
|
76
68
|
) as MediaTypeObject;
|
|
77
69
|
const schema = requestBody?.schema;
|
|
78
70
|
|
|
@@ -81,61 +73,53 @@ if (operation.hasRequestBody()) {
|
|
|
81
73
|
}
|
|
82
74
|
}
|
|
83
75
|
|
|
84
|
-
|
|
76
|
+
const generatedSnippets: Array<{
|
|
85
77
|
code: string | false;
|
|
86
78
|
highlightMode: string | false;
|
|
87
79
|
install: string | false;
|
|
88
|
-
|
|
80
|
+
label?: string;
|
|
81
|
+
}> = [];
|
|
89
82
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
83
|
+
const snippetTargets: Array<{
|
|
84
|
+
lang: string;
|
|
85
|
+
target: string;
|
|
86
|
+
label: string;
|
|
87
|
+
}> = [
|
|
88
|
+
{ lang: "shell", target: "curl", label: "cURL" },
|
|
89
|
+
{ lang: "javascript", target: "fetch", label: "JavaScript" },
|
|
90
|
+
{ lang: "python", target: "requests", label: "Python" },
|
|
91
|
+
{ lang: "go", target: "native", label: "Go" },
|
|
92
|
+
{ lang: "java", target: "okhttp", label: "Java" },
|
|
93
|
+
{ lang: "php", target: "guzzle", label: "PHP" },
|
|
95
94
|
];
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
snippetTargets.forEach(({ lang, target, label }) => {
|
|
98
97
|
try {
|
|
99
|
-
|
|
100
|
-
// formData and auth can be empty objects for generic examples
|
|
101
|
-
const res = oasToSnippet(api, operation, values, auth, [
|
|
98
|
+
const result = oasToSnippet(api, operation, values, auth, [
|
|
102
99
|
lang,
|
|
103
100
|
target,
|
|
104
101
|
] as Language);
|
|
105
102
|
|
|
106
|
-
if (
|
|
107
|
-
|
|
103
|
+
if (result && result.code) {
|
|
104
|
+
result.code = result.code
|
|
108
105
|
.replace(/%7B/g, "{")
|
|
109
106
|
.replace(/%7D/g, "}")
|
|
110
107
|
.replace(/%3C/g, "<")
|
|
111
108
|
.replace(/%3E/g, ">");
|
|
109
|
+
|
|
110
|
+
result.code = result.code
|
|
111
|
+
.replace(/\r\n?/g, "\n")
|
|
112
|
+
.replace(/^[ \t]*\n/gm, "")
|
|
113
|
+
.trimEnd();
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
|
|
115
|
-
} catch (
|
|
116
|
-
const
|
|
117
|
-
console.error(`Failed to generate ${lang} snippet:`,
|
|
116
|
+
generatedSnippets.push({ ...result, label });
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
119
|
+
console.error(`Failed to generate ${lang}/${target} snippet:`, message);
|
|
118
120
|
}
|
|
119
121
|
});
|
|
120
122
|
|
|
121
|
-
// Resolve the path to node_modules
|
|
122
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
123
|
-
const __dirname = dirname(__filename);
|
|
124
|
-
const iconsPath = resolve(
|
|
125
|
-
__dirname,
|
|
126
|
-
"../../../node_modules/@xt0rted/expressive-code-file-icons/dist/icons"
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
// Helper function to read SVG file
|
|
130
|
-
function getIconSvg(iconName: string): string {
|
|
131
|
-
try {
|
|
132
|
-
return readFileSync(resolve(iconsPath, `${iconName}.svg`), "utf-8");
|
|
133
|
-
} catch {
|
|
134
|
-
return "";
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Map language to display label
|
|
139
123
|
const displayLabelMap: Record<string, string> = {
|
|
140
124
|
shell: "cURL",
|
|
141
125
|
javascript: "JavaScript",
|
|
@@ -143,166 +127,331 @@ const displayLabelMap: Record<string, string> = {
|
|
|
143
127
|
go: "Go",
|
|
144
128
|
};
|
|
145
129
|
|
|
146
|
-
|
|
147
|
-
const iconFilenameMap: Record<string, string> = {
|
|
148
|
-
shell: "file_type_shell",
|
|
149
|
-
javascript: "file_type_js_official",
|
|
150
|
-
python: "file_type_python",
|
|
151
|
-
go: "file_type_go",
|
|
152
|
-
};
|
|
130
|
+
const CURL_ICON_SVG = `<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" class="size-3.5 shrink-0 self-center rounded-[4px]" xmlns="http://www.w3.org/2000/svg"><path d="M432 32H80a64.07 64.07 0 0 0-64 64v320a64.07 64.07 0 0 0 64 64h352a64.07 64.07 0 0 0 64-64V96a64.07 64.07 0 0 0-64-64zM96 256a16 16 0 0 1-10-28.49L150.39 176 86 124.49a16 16 0 1 1 20-25l80 64a16 16 0 0 1 0 25l-80 64A16 16 0 0 1 96 256zm160 0h-64a16 16 0 0 1 0-32h64a16 16 0 0 1 0 32z"></path></svg>`;
|
|
153
131
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
132
|
+
function buildTokenStyle(token: {
|
|
133
|
+
color?: string;
|
|
134
|
+
bgColor?: string;
|
|
135
|
+
fontStyle?: number;
|
|
136
|
+
htmlStyle?: Record<string, string>;
|
|
137
|
+
}): string | undefined {
|
|
138
|
+
const styleSegments: string[] = [];
|
|
159
139
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
140
|
+
if (token.color) styleSegments.push(`color:${token.color}`);
|
|
141
|
+
if (token.bgColor) styleSegments.push(`background-color:${token.bgColor}`);
|
|
142
|
+
|
|
143
|
+
const fontStyle = typeof token.fontStyle === "number" ? token.fontStyle : 0;
|
|
144
|
+
if ((fontStyle & 1) === 1) styleSegments.push("font-style:italic");
|
|
145
|
+
if ((fontStyle & 2) === 2) styleSegments.push("font-weight:600");
|
|
146
|
+
if ((fontStyle & 4) === 4) styleSegments.push("text-decoration:underline");
|
|
147
|
+
|
|
148
|
+
if (token.htmlStyle && typeof token.htmlStyle === "object") {
|
|
149
|
+
for (const [property, value] of Object.entries(token.htmlStyle)) {
|
|
150
|
+
styleSegments.push(`${property}:${value}`);
|
|
151
|
+
}
|
|
164
152
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
153
|
+
|
|
154
|
+
if (!styleSegments.length) return undefined;
|
|
155
|
+
return styleSegments.join(";");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function escapeHtml(value: string): string {
|
|
159
|
+
return value
|
|
160
|
+
.replaceAll("&", "&")
|
|
161
|
+
.replaceAll("<", "<")
|
|
162
|
+
.replaceAll(">", ">");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function escapeAttribute(value: string): string {
|
|
166
|
+
return escapeHtml(value).replaceAll('"', """);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function renderCodeLinesHtml(
|
|
170
|
+
code: string,
|
|
171
|
+
language: string,
|
|
172
|
+
): Promise<string> {
|
|
173
|
+
const normalizedCode = code.replace(/\r\n?/g, "\n");
|
|
174
|
+
const { lines: tokenLines } = await getCodeLineTokens({
|
|
175
|
+
code: normalizedCode,
|
|
176
|
+
language,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const rawLines = normalizedCode.split("\n");
|
|
180
|
+
const normalizedRawLines = rawLines.length > 0 ? rawLines : [""];
|
|
181
|
+
const lineCount = Math.max(1, normalizedRawLines.length, tokenLines.length);
|
|
182
|
+
const normalizedTokenLines = Array.from({ length: lineCount }, (_, index) => {
|
|
183
|
+
return tokenLines[index] ?? [];
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
return normalizedTokenLines
|
|
187
|
+
.map((lineTokens, lineIndex) => {
|
|
188
|
+
const fallbackLineContent = normalizedRawLines[lineIndex] ?? "";
|
|
189
|
+
const tokenHtml =
|
|
190
|
+
lineTokens.length > 0
|
|
191
|
+
? lineTokens
|
|
192
|
+
.map((token) => {
|
|
193
|
+
const tokenStyle = buildTokenStyle(token);
|
|
194
|
+
const tokenStyleAttribute = tokenStyle
|
|
195
|
+
? ` style="${escapeAttribute(tokenStyle)}"`
|
|
196
|
+
: "";
|
|
197
|
+
return `<span${tokenStyleAttribute}>${escapeHtml(token.content)}</span>`;
|
|
198
|
+
})
|
|
199
|
+
.join("")
|
|
200
|
+
: fallbackLineContent.length > 0
|
|
201
|
+
? escapeHtml(fallbackLineContent)
|
|
202
|
+
: " ";
|
|
203
|
+
|
|
204
|
+
return `<span class="flex min-w-full"><span class="flex-1 whitespace-pre pr-4 pl-4">${tokenHtml}</span></span>`;
|
|
205
|
+
})
|
|
206
|
+
.join("");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const requestSnippetItems = await Promise.all(
|
|
210
|
+
generatedSnippets
|
|
211
|
+
.filter(
|
|
212
|
+
(
|
|
213
|
+
snippet,
|
|
214
|
+
): snippet is {
|
|
215
|
+
code: string;
|
|
216
|
+
highlightMode: string;
|
|
217
|
+
install: string | false;
|
|
218
|
+
} => {
|
|
219
|
+
return (
|
|
220
|
+
typeof snippet.code === "string" &&
|
|
221
|
+
typeof snippet.highlightMode === "string"
|
|
222
|
+
);
|
|
223
|
+
},
|
|
224
|
+
)
|
|
225
|
+
.map(async (snippet) => {
|
|
226
|
+
const normalizedLanguage = normalizeCodeLanguageValue(
|
|
227
|
+
snippet.highlightMode,
|
|
228
|
+
);
|
|
229
|
+
const displayLabel =
|
|
230
|
+
snippet.label ??
|
|
231
|
+
displayLabelMap[normalizedLanguage] ??
|
|
232
|
+
snippet.highlightMode;
|
|
233
|
+
const iconSvg =
|
|
234
|
+
displayLabel === "cURL"
|
|
235
|
+
? CURL_ICON_SVG
|
|
236
|
+
: await getLanguageIconSvg({
|
|
237
|
+
language: normalizedLanguage,
|
|
238
|
+
fileName: buildDefaultCodeFileName(normalizedLanguage),
|
|
239
|
+
className: "size-3.5 shrink-0 self-center rounded-[4px]",
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
label: displayLabel,
|
|
244
|
+
code: snippet.code,
|
|
245
|
+
iconSvg,
|
|
246
|
+
renderedCodeLinesHtml: await renderCodeLinesHtml(
|
|
247
|
+
snippet.code,
|
|
248
|
+
normalizedLanguage,
|
|
249
|
+
),
|
|
250
|
+
};
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
if (requestSnippetItems.length === 0) {
|
|
255
|
+
const fallbackLanguage = "plaintext";
|
|
256
|
+
const fallbackCode = "Unable to generate request snippets.";
|
|
257
|
+
requestSnippetItems.push({
|
|
258
|
+
label: "Plaintext",
|
|
259
|
+
code: fallbackCode,
|
|
260
|
+
iconSvg: await getLanguageIconSvg({
|
|
261
|
+
language: fallbackLanguage,
|
|
262
|
+
fileName: buildDefaultCodeFileName(fallbackLanguage),
|
|
263
|
+
className: "size-3.5 shrink-0 self-center rounded-[4px]",
|
|
264
|
+
}),
|
|
265
|
+
renderedCodeLinesHtml: await renderCodeLinesHtml(
|
|
266
|
+
fallbackCode,
|
|
267
|
+
fallbackLanguage,
|
|
268
|
+
),
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const clientSnippets = requestSnippetItems.map((snippet) => ({
|
|
273
|
+
label: snippet.label,
|
|
274
|
+
code: snippet.code,
|
|
275
|
+
iconSvg: snippet.iconSvg,
|
|
276
|
+
}));
|
|
277
|
+
const snippetsData = JSON.stringify(clientSnippets).replaceAll("`", "\\`");
|
|
278
|
+
const firstSnippet = requestSnippetItems[0];
|
|
279
|
+
const hasMultipleRequests = requestSnippetItems.length > 1;
|
|
171
280
|
---
|
|
172
281
|
|
|
173
282
|
<div
|
|
174
283
|
x-data={`{
|
|
175
|
-
snippets: ${
|
|
284
|
+
snippets: ${snippetsData},
|
|
176
285
|
selected: 0,
|
|
286
|
+
hasMultiple: ${hasMultipleRequests ? "true" : "false"},
|
|
287
|
+
copied: false,
|
|
288
|
+
pillLeft: 0,
|
|
289
|
+
pillWidth: 0,
|
|
290
|
+
pillVisible: false,
|
|
291
|
+
tabSyncHandler: null,
|
|
292
|
+
copyTimeoutId: null,
|
|
293
|
+
init() {
|
|
294
|
+
if (!this.hasMultiple) return;
|
|
295
|
+
const sync = () => this.syncPill();
|
|
296
|
+
this.tabSyncHandler = sync;
|
|
297
|
+
this.$nextTick(() => {
|
|
298
|
+
this.syncPill();
|
|
299
|
+
this.$refs.tabList?.addEventListener("scroll", sync, {
|
|
300
|
+
passive: true,
|
|
301
|
+
});
|
|
302
|
+
window.addEventListener("resize", sync);
|
|
303
|
+
});
|
|
304
|
+
},
|
|
305
|
+
syncPill() {
|
|
306
|
+
if (!this.hasMultiple) return;
|
|
307
|
+
const tabs = this.$refs.tabList;
|
|
308
|
+
if (!tabs) return;
|
|
309
|
+
const activeTab = tabs.querySelector(
|
|
310
|
+
'[data-rd-snippet-tab="' + this.selected + '"]',
|
|
311
|
+
);
|
|
312
|
+
if (!activeTab) {
|
|
313
|
+
this.pillVisible = false;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const tabsRect = tabs.getBoundingClientRect();
|
|
317
|
+
const activeRect = activeTab.getBoundingClientRect();
|
|
318
|
+
this.pillLeft = activeRect.left - tabsRect.left + tabs.scrollLeft;
|
|
319
|
+
this.pillWidth = activeRect.width;
|
|
320
|
+
this.pillVisible = true;
|
|
321
|
+
},
|
|
177
322
|
select(index) {
|
|
178
323
|
this.selected = index;
|
|
179
|
-
this.$
|
|
180
|
-
|
|
324
|
+
this.$nextTick(() => {
|
|
325
|
+
this.syncPill();
|
|
326
|
+
window.dispatchEvent(new CustomEvent("rd:snippet-content-change"));
|
|
327
|
+
});
|
|
181
328
|
},
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
329
|
+
async copySelected() {
|
|
330
|
+
const snippet = this.snippets[this.selected];
|
|
331
|
+
if (!snippet) return;
|
|
332
|
+
if (!navigator?.clipboard?.writeText) {
|
|
333
|
+
this.copied = false;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
try {
|
|
337
|
+
await navigator.clipboard.writeText(snippet.code ?? "");
|
|
338
|
+
this.copied = true;
|
|
339
|
+
if (this.copyTimeoutId) window.clearTimeout(this.copyTimeoutId);
|
|
340
|
+
this.copyTimeoutId = window.setTimeout(() => {
|
|
341
|
+
this.copied = false;
|
|
342
|
+
this.copyTimeoutId = null;
|
|
343
|
+
}, 1200);
|
|
344
|
+
} catch {
|
|
345
|
+
this.copied = false;
|
|
346
|
+
}
|
|
186
347
|
}
|
|
187
348
|
}`}
|
|
188
|
-
class="request-code-snippets
|
|
349
|
+
class="request-code-snippets h-full min-h-0 w-full min-w-0"
|
|
189
350
|
>
|
|
190
|
-
<div class="
|
|
191
|
-
<div class="font-medium ml-2 text-neutral-700 text-sm truncate">
|
|
192
|
-
<span
|
|
193
|
-
class:list={[
|
|
194
|
-
"text-xs uppercase font-semibold px-1.5 border py-px rounded-md mr-1.5",
|
|
195
|
-
methodColors[method],
|
|
196
|
-
]}>{method}</span
|
|
197
|
-
>{path}
|
|
198
|
-
</div>
|
|
351
|
+
<div class="group/prose-code not-prose relative h-full min-h-0 w-full max-w-full min-w-0">
|
|
199
352
|
<div
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
353
|
+
class="flex h-full min-h-0 w-full max-w-full min-w-0 flex-col overflow-hidden rounded-xl border border-neutral-200 bg-white shadow-xs"
|
|
354
|
+
>
|
|
355
|
+
<div
|
|
356
|
+
class="flex items-center justify-between gap-2 border-b border-neutral-200 bg-neutral-50 rounded-t-xl inset-shadow-sm inset-shadow-neutral-100/80"
|
|
357
|
+
>
|
|
358
|
+
{
|
|
359
|
+
hasMultipleRequests ? (
|
|
360
|
+
<div class="min-w-0 flex-1 overflow-hidden rounded-t-xl">
|
|
361
|
+
<div
|
|
362
|
+
x-ref="tabList"
|
|
363
|
+
class="relative flex min-w-0 items-end gap-1 overflow-x-auto pl-1 pr-8 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
364
|
+
>
|
|
365
|
+
<div
|
|
366
|
+
aria-hidden="true"
|
|
367
|
+
class="pointer-events-none absolute top-1/2 z-0 h-[28px] -translate-y-1/2 rounded-lg border-[0.5px] border-neutral-200 bg-white shadow-xs transition-[left,width,opacity] duration-200 ease-out"
|
|
368
|
+
x-bind:class="pillVisible ? 'opacity-100' : 'opacity-0'"
|
|
369
|
+
x-bind:style="'left:' + pillLeft + 'px;width:' + pillWidth + 'px;'"
|
|
370
|
+
/>
|
|
208
371
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
372
|
+
<template
|
|
373
|
+
x-for="(snippet, index) in snippets"
|
|
374
|
+
:key="snippet.label + index"
|
|
375
|
+
>
|
|
376
|
+
<button
|
|
377
|
+
type="button"
|
|
378
|
+
x-bind:data-rd-snippet-tab="index"
|
|
379
|
+
x-on:click="select(index)"
|
|
380
|
+
class="relative z-10 inline-flex h-9 items-center gap-2 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"
|
|
381
|
+
x-bind:class="selected === index ? 'text-neutral-900' : 'text-neutral-600'"
|
|
382
|
+
>
|
|
383
|
+
<span
|
|
384
|
+
x-show="snippet.iconSvg"
|
|
385
|
+
x-html="snippet.iconSvg"
|
|
386
|
+
class="pointer-events-none inline-flex size-3.5 shrink-0 items-center rounded-[4px] transition-opacity duration-150"
|
|
387
|
+
x-bind:class="selected === index ? 'opacity-100' : 'opacity-70'"></span>
|
|
388
|
+
<span class="whitespace-pre leading-none" x-text="snippet.label"></span>
|
|
389
|
+
</button>
|
|
390
|
+
</template>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
) : (
|
|
394
|
+
<div class="min-w-0 flex-1">
|
|
395
|
+
<div class="relative h-9 w-fit max-w-full rounded-tl-xl bg-white">
|
|
396
|
+
<div class="absolute inset-x-0 -bottom-px h-px bg-white"></div>
|
|
397
|
+
<CodeTabEdge
|
|
398
|
+
className="pointer-events-none absolute -top-px left-full z-10 h-[calc(100%+2px)]"
|
|
399
|
+
/>
|
|
213
400
|
|
|
214
|
-
|
|
401
|
+
<div class="relative z-20 inline-flex h-9 max-w-full items-center gap-2 pl-5 pr-3 py-1.5 text-xs font-medium text-neutral-700">
|
|
402
|
+
<span
|
|
403
|
+
class="size-3.5 shrink-0 self-center rounded-[4px]"
|
|
404
|
+
set:html={firstSnippet.iconSvg}
|
|
405
|
+
/>
|
|
406
|
+
<span class="truncate leading-none">{firstSnippet.label}</span>
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
</div>
|
|
410
|
+
)
|
|
411
|
+
}
|
|
215
412
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
x-on:focusin.window="! $refs.panel.contains($event.target) && close()"
|
|
221
|
-
x-id="['dropdown-button']"
|
|
222
|
-
class="shrink-0 relative max-w-36 w-full"
|
|
223
|
-
>
|
|
224
|
-
<button
|
|
225
|
-
x-ref="button"
|
|
226
|
-
x-on:click="toggle()"
|
|
227
|
-
:aria-expanded="open"
|
|
228
|
-
:aria-controls="$id('dropdown-button')"
|
|
229
|
-
type="button"
|
|
230
|
-
class="flex items-center px-3 pt-2 pb-1.5 relative border-x border-t border-neutral-200/70 rounded-t-xl w-full text-sm font-medium bg-white shadow-[-1px_0px_2px_0px_rgba(0,0,0,0.01)]. shadow-sm cursor-pointer after:absolute after:-bottom-[1.5px] after:inset-x-0 after:z-10 after:h-[3px] after:bg-white"
|
|
231
|
-
>
|
|
232
|
-
<span class="flex items-center gap-2">
|
|
233
|
-
<span
|
|
234
|
-
x-show="snippets[selected].iconSvg"
|
|
235
|
-
x-html="snippets[selected].iconSvg"
|
|
236
|
-
class="size-4 [&>svg]:w-full [&>svg]:h-full rounded overflow-hidden"
|
|
237
|
-
set:html={snippetsWithIcons[0]?.iconSvg || ""}
|
|
238
|
-
/>
|
|
239
|
-
<span
|
|
240
|
-
x-text="snippets[selected].displayLabel"
|
|
241
|
-
set:html={snippetsWithIcons[0]?.displayLabel || ""}
|
|
413
|
+
<div class="relative h-9 w-5 shrink-0 rounded-tr-xl bg-white">
|
|
414
|
+
<div class="absolute inset-x-0 -bottom-px h-px bg-white"></div>
|
|
415
|
+
<CodeTabEdge
|
|
416
|
+
className="pointer-events-none absolute -top-px right-full z-10 h-[calc(100%+2px)] rotate-y-180"
|
|
242
417
|
/>
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
<
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
418
|
+
<button
|
|
419
|
+
x-on:click="copySelected()"
|
|
420
|
+
type="button"
|
|
421
|
+
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"
|
|
422
|
+
aria-label="Copy code"
|
|
423
|
+
>
|
|
424
|
+
<Icon
|
|
425
|
+
name="lucide:copy"
|
|
426
|
+
class="size-3.5 origin-center transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
427
|
+
x-bind:class="copied ? 'scale-50 opacity-0 -rotate-6' : 'scale-100 opacity-100 rotate-0'"
|
|
428
|
+
aria-hidden="true"
|
|
429
|
+
/>
|
|
430
|
+
<Icon
|
|
431
|
+
name="lucide:check"
|
|
432
|
+
class="absolute size-3.5 stroke-3 origin-center text-green-700/80 transition-all duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-transform motion-reduce:transition-none"
|
|
433
|
+
x-bind:class="copied ? 'scale-110 opacity-100 rotate-0' : 'scale-25 opacity-0 rotate-6'"
|
|
434
|
+
aria-hidden="true"
|
|
435
|
+
/>
|
|
436
|
+
</button>
|
|
437
|
+
</div>
|
|
438
|
+
</div>
|
|
439
|
+
|
|
440
|
+
<div class="relative min-h-0 min-w-0 flex-1 overflow-hidden rounded-b-xl">
|
|
441
|
+
<div class="relative h-full overflow-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">
|
|
442
|
+
<div class="pointer-events-none sticky top-0 z-10 -mb-4 h-4 w-full bg-linear-to-b from-white to-transparent"></div>
|
|
443
|
+
{
|
|
444
|
+
requestSnippetItems.map((snippet, index) => (
|
|
445
|
+
<pre
|
|
446
|
+
class="relative m-0 min-w-full bg-white p-0 text-[13px] leading-6"
|
|
447
|
+
x-show={`selected === ${index}`}
|
|
448
|
+
{...(index !== 0 ? { "x-cloak": true } : {})}
|
|
449
|
+
data-snippet-index={index}
|
|
450
|
+
><code class="block min-w-full py-2.5 font-mono text-neutral-800"><Fragment set:html={snippet.renderedCodeLinesHtml} /></code></pre>
|
|
451
|
+
))
|
|
452
|
+
}
|
|
453
|
+
</div>
|
|
454
|
+
</div>
|
|
275
455
|
</div>
|
|
276
456
|
</div>
|
|
277
|
-
{
|
|
278
|
-
snippets.map((snippet, index) => (
|
|
279
|
-
<div
|
|
280
|
-
{...(index !== 0 ? { "x-cloak": true } : {})}
|
|
281
|
-
x-show={`selected === ${index}`}
|
|
282
|
-
data-snippet-index={index}
|
|
283
|
-
>
|
|
284
|
-
{snippet.code && snippet.highlightMode && (
|
|
285
|
-
<Code
|
|
286
|
-
code={snippet.code as string}
|
|
287
|
-
lang={snippet.highlightMode as string}
|
|
288
|
-
frame="code"
|
|
289
|
-
/>
|
|
290
|
-
)}
|
|
291
|
-
</div>
|
|
292
|
-
))
|
|
293
|
-
}
|
|
294
457
|
</div>
|
|
295
|
-
|
|
296
|
-
<style>
|
|
297
|
-
@reference "../../styles/global.css";
|
|
298
|
-
:global(.request-code-snippets .expressive-code .frame) {
|
|
299
|
-
@apply shadow-sm;
|
|
300
|
-
}
|
|
301
|
-
:global(.request-code-snippets .expressive-code pre) {
|
|
302
|
-
@apply rounded-xl! rounded-tr-none! border-neutral-200/70!;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
:global(.request-code-snippets .expressive-code pre code) {
|
|
306
|
-
@apply max-h-64 overflow-y-auto;
|
|
307
|
-
}
|
|
308
|
-
</style>
|