radiant-docs 0.1.6 → 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 +32 -6
- 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,11 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { Icon } from "astro-icon/components";
|
|
3
3
|
import type { OpenApiRoute } from "../../lib/routes";
|
|
4
|
+
import { getConfig } from "../../lib/validation";
|
|
4
5
|
import { renderMarkdown } from "../../lib/utils";
|
|
5
|
-
import {
|
|
6
|
-
|
|
6
|
+
import {
|
|
7
|
+
headers,
|
|
8
|
+
type RequestFields,
|
|
9
|
+
type RequestSectionVariantData,
|
|
10
|
+
} from "../OpenApiPage.astro";
|
|
11
|
+
import Accordion from "../user/Accordion.astro";
|
|
7
12
|
import PlaygroundBar from "./PlaygroundBar.astro";
|
|
8
|
-
import Field from "../ui/Field.astro";
|
|
9
13
|
import ResponseDisplay from "./ResponseDisplay.astro";
|
|
10
14
|
import PlaygroundField from "./PlaygroundField.astro";
|
|
11
15
|
|
|
@@ -13,37 +17,410 @@ interface Props {
|
|
|
13
17
|
route: OpenApiRoute;
|
|
14
18
|
serverUrl?: string;
|
|
15
19
|
requestFields: RequestFields;
|
|
20
|
+
requestSectionVariants?: Partial<
|
|
21
|
+
Record<keyof RequestFields, RequestSectionVariantData>
|
|
22
|
+
>;
|
|
23
|
+
bodyDescription?: string;
|
|
24
|
+
bodyDefaultKind?: "object" | "array";
|
|
16
25
|
}
|
|
17
26
|
|
|
18
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
route,
|
|
29
|
+
serverUrl,
|
|
30
|
+
requestFields,
|
|
31
|
+
requestSectionVariants = {},
|
|
32
|
+
bodyDescription = "",
|
|
33
|
+
bodyDefaultKind,
|
|
34
|
+
} = Astro.props;
|
|
35
|
+
const config = await getConfig();
|
|
36
|
+
const proxyEnabled = config.playground?.proxy !== false;
|
|
37
|
+
const proxyUrl =
|
|
38
|
+
import.meta.env.PUBLIC_PROXY_URL ||
|
|
39
|
+
"https://docs-proxy.stefanjoseph-dev.workers.dev";
|
|
40
|
+
const formattedBodyDescription = bodyDescription
|
|
41
|
+
? await renderMarkdown(bodyDescription)
|
|
42
|
+
: null;
|
|
43
|
+
const queryFieldMeta = Object.fromEntries(
|
|
44
|
+
requestFields.query.map((field) => [
|
|
45
|
+
field.name,
|
|
46
|
+
{
|
|
47
|
+
isArray: field.isArray === true,
|
|
48
|
+
isObject:
|
|
49
|
+
(field.nested && field.nested.length > 0) ||
|
|
50
|
+
/\bobject\b/.test(field.type),
|
|
51
|
+
style: field.style || "form",
|
|
52
|
+
explode: field.explode,
|
|
53
|
+
},
|
|
54
|
+
]),
|
|
55
|
+
);
|
|
56
|
+
const sectionVariantFieldNames = Object.fromEntries(
|
|
57
|
+
Object.entries(requestSectionVariants).map(([section, data]) => [
|
|
58
|
+
section,
|
|
59
|
+
(data?.variants || []).map((variant) =>
|
|
60
|
+
variant.fields.map((field) => field.name),
|
|
61
|
+
),
|
|
62
|
+
]),
|
|
63
|
+
);
|
|
19
64
|
---
|
|
20
65
|
|
|
21
66
|
<div
|
|
22
67
|
x-data=`{
|
|
23
68
|
loading: false,
|
|
24
69
|
response: null,
|
|
70
|
+
queryFieldMeta: ${JSON.stringify(queryFieldMeta)},
|
|
71
|
+
sectionVariantFieldNames: ${JSON.stringify(sectionVariantFieldNames)},
|
|
72
|
+
bodyDefaultKind: ${JSON.stringify(bodyDefaultKind ?? null)},
|
|
73
|
+
selectedSectionVariants: {},
|
|
25
74
|
inputs: {
|
|
26
75
|
header: {},
|
|
27
76
|
path: {},
|
|
28
77
|
query: {},
|
|
78
|
+
cookie: {},
|
|
29
79
|
body: {}
|
|
30
80
|
},
|
|
31
|
-
|
|
81
|
+
getSectionVariantFieldNames(section, variantIndex) {
|
|
82
|
+
const sectionVariants = this.sectionVariantFieldNames[section] || [];
|
|
83
|
+
const variantNames = sectionVariants[variantIndex];
|
|
84
|
+
return Array.isArray(variantNames) ? variantNames : [];
|
|
85
|
+
},
|
|
86
|
+
detectSectionVariant(section) {
|
|
87
|
+
const target = this.inputs[section];
|
|
88
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const sectionVariants = this.sectionVariantFieldNames[section] || [];
|
|
93
|
+
let selectedIndex = 0;
|
|
94
|
+
let bestScore = -1;
|
|
95
|
+
|
|
96
|
+
sectionVariants.forEach((fieldNames, variantIndex) => {
|
|
97
|
+
if (!Array.isArray(fieldNames) || fieldNames.length === 0) return;
|
|
98
|
+
const score = fieldNames.reduce((total, fieldName) => {
|
|
99
|
+
return total + (Object.prototype.hasOwnProperty.call(target, fieldName) ? 1 : 0);
|
|
100
|
+
}, 0);
|
|
101
|
+
if (score > bestScore) {
|
|
102
|
+
bestScore = score;
|
|
103
|
+
selectedIndex = variantIndex;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return selectedIndex;
|
|
108
|
+
},
|
|
109
|
+
getSelectedSectionVariant(section) {
|
|
110
|
+
if (Object.prototype.hasOwnProperty.call(this.selectedSectionVariants, section)) {
|
|
111
|
+
return this.selectedSectionVariants[section];
|
|
112
|
+
}
|
|
113
|
+
const detected = this.detectSectionVariant(section);
|
|
114
|
+
this.selectedSectionVariants[section] = detected;
|
|
115
|
+
return detected;
|
|
116
|
+
},
|
|
117
|
+
pruneSectionForVariant(section, selectedVariantIndex) {
|
|
118
|
+
if (
|
|
119
|
+
!this.inputs[section] ||
|
|
120
|
+
typeof this.inputs[section] !== 'object' ||
|
|
121
|
+
Array.isArray(this.inputs[section])
|
|
122
|
+
) {
|
|
123
|
+
this.inputs[section] = {};
|
|
124
|
+
}
|
|
125
|
+
const target = this.inputs[section];
|
|
126
|
+
const selectedNames = new Set(
|
|
127
|
+
this.getSectionVariantFieldNames(section, selectedVariantIndex),
|
|
128
|
+
);
|
|
129
|
+
const namesToRemove = new Set();
|
|
130
|
+
const sectionVariants = this.sectionVariantFieldNames[section] || [];
|
|
131
|
+
|
|
132
|
+
sectionVariants.forEach((fieldNames, variantIndex) => {
|
|
133
|
+
if (!Array.isArray(fieldNames) || variantIndex === selectedVariantIndex) return;
|
|
134
|
+
fieldNames.forEach((fieldName) => {
|
|
135
|
+
if (!selectedNames.has(fieldName)) namesToRemove.add(fieldName);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
namesToRemove.forEach((fieldName) => {
|
|
140
|
+
delete target[fieldName];
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
selectSectionVariant(section, variantIndex) {
|
|
144
|
+
this.selectedSectionVariants[section] = variantIndex;
|
|
145
|
+
this.pruneSectionForVariant(section, variantIndex);
|
|
146
|
+
},
|
|
147
|
+
resolvePathTokenValue(tokenName) {
|
|
148
|
+
const rawValue = this.inputs?.path?.[tokenName];
|
|
149
|
+
if (rawValue !== undefined && rawValue !== null) {
|
|
150
|
+
const value = String(rawValue).trim();
|
|
151
|
+
if (value.length > 0) return encodeURIComponent(value);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Keep request URL valid even when server variables (e.g. {apiKey}) are unset,
|
|
155
|
+
// so the target API can return its own auth/validation error response.
|
|
156
|
+
if (/(^|[_-])(api[_-]?key|token|secret|key)$/i.test(String(tokenName))) {
|
|
157
|
+
return "invalid";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return "invalid";
|
|
161
|
+
},
|
|
162
|
+
resolveUrlPlaceholders(urlTemplate) {
|
|
163
|
+
return String(urlTemplate).replace(/\{([^}]+)\}/g, (_match, tokenName) =>
|
|
164
|
+
this.resolvePathTokenValue(String(tokenName)),
|
|
165
|
+
);
|
|
166
|
+
},
|
|
167
|
+
sanitizeBodyValue(value) {
|
|
168
|
+
if (value === undefined || value === null || value === '') {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (Array.isArray(value)) {
|
|
173
|
+
const cleaned = value
|
|
174
|
+
.map((item) => this.sanitizeBodyValue(item))
|
|
175
|
+
.filter((item) => item !== undefined);
|
|
176
|
+
return cleaned.length > 0 ? cleaned : undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (typeof value === 'object') {
|
|
180
|
+
const cleanedEntries = Object.entries(value)
|
|
181
|
+
.map(([key, item]) => [key, this.sanitizeBodyValue(item)])
|
|
182
|
+
.filter(([, item]) => item !== undefined);
|
|
183
|
+
|
|
184
|
+
if (cleanedEntries.length === 0) return undefined;
|
|
185
|
+
return Object.fromEntries(cleanedEntries);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return value;
|
|
189
|
+
},
|
|
190
|
+
isPlainObject(value) {
|
|
191
|
+
return (
|
|
192
|
+
value !== null &&
|
|
193
|
+
typeof value === 'object' &&
|
|
194
|
+
!Array.isArray(value)
|
|
195
|
+
);
|
|
196
|
+
},
|
|
197
|
+
serializeQueryObject(params, key, rawValue, meta = {}) {
|
|
198
|
+
const cleanedObject = this.sanitizeBodyValue(rawValue);
|
|
199
|
+
if (!this.isPlainObject(cleanedObject)) return;
|
|
200
|
+
|
|
201
|
+
const entries = Object.entries(cleanedObject);
|
|
202
|
+
if (entries.length === 0) return;
|
|
203
|
+
|
|
204
|
+
const style = meta.style || 'form';
|
|
205
|
+
const explode =
|
|
206
|
+
typeof meta.explode === 'boolean' ? meta.explode : style === 'form';
|
|
207
|
+
|
|
208
|
+
if (style === 'deepObject') {
|
|
209
|
+
const appendDeepValue = (prefix, value) => {
|
|
210
|
+
if (value === undefined || value === null || value === '') return;
|
|
211
|
+
|
|
212
|
+
if (Array.isArray(value)) {
|
|
213
|
+
value.forEach((item, index) => {
|
|
214
|
+
appendDeepValue(prefix + '[' + index + ']', item);
|
|
215
|
+
});
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (this.isPlainObject(value)) {
|
|
220
|
+
Object.entries(value).forEach(([nestedKey, nestedValue]) => {
|
|
221
|
+
appendDeepValue(prefix + '[' + nestedKey + ']', nestedValue);
|
|
222
|
+
});
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
params.append(prefix, String(value));
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
entries.forEach(([propertyKey, propertyValue]) => {
|
|
230
|
+
appendDeepValue(key + '[' + propertyKey + ']', propertyValue);
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (style === 'form' && explode) {
|
|
236
|
+
const appendExploded = (propertyKey, value) => {
|
|
237
|
+
if (value === undefined || value === null || value === '') return;
|
|
238
|
+
|
|
239
|
+
if (Array.isArray(value)) {
|
|
240
|
+
value.forEach((item) => appendExploded(propertyKey, item));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (this.isPlainObject(value)) {
|
|
245
|
+
params.append(propertyKey, JSON.stringify(value));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
params.append(propertyKey, String(value));
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
entries.forEach(([propertyKey, propertyValue]) => {
|
|
253
|
+
appendExploded(propertyKey, propertyValue);
|
|
254
|
+
});
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const flattenedPairs = [];
|
|
259
|
+
entries.forEach(([propertyKey, propertyValue]) => {
|
|
260
|
+
if (
|
|
261
|
+
propertyValue === undefined ||
|
|
262
|
+
propertyValue === null ||
|
|
263
|
+
propertyValue === ''
|
|
264
|
+
) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let serializedValue = '';
|
|
269
|
+
if (Array.isArray(propertyValue)) {
|
|
270
|
+
serializedValue = propertyValue.map((item) => String(item)).join(',');
|
|
271
|
+
} else if (this.isPlainObject(propertyValue)) {
|
|
272
|
+
serializedValue = JSON.stringify(propertyValue);
|
|
273
|
+
} else {
|
|
274
|
+
serializedValue = String(propertyValue);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (serializedValue === '') return;
|
|
278
|
+
flattenedPairs.push(propertyKey, serializedValue);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
if (flattenedPairs.length === 0) return;
|
|
282
|
+
|
|
283
|
+
const delimiter =
|
|
284
|
+
style === 'spaceDelimited'
|
|
285
|
+
? ' '
|
|
286
|
+
: style === 'pipeDelimited'
|
|
287
|
+
? '|'
|
|
288
|
+
: ',';
|
|
289
|
+
|
|
290
|
+
params.append(key, flattenedPairs.join(delimiter));
|
|
291
|
+
},
|
|
292
|
+
getDefaultRequestBody() {
|
|
293
|
+
if (this.bodyDefaultKind === 'object') return {};
|
|
294
|
+
if (this.bodyDefaultKind === 'array') return [];
|
|
295
|
+
return undefined;
|
|
296
|
+
},
|
|
297
|
+
buildCookieHeader() {
|
|
298
|
+
const cookieInputs = this.inputs?.cookie;
|
|
299
|
+
if (
|
|
300
|
+
!cookieInputs ||
|
|
301
|
+
typeof cookieInputs !== 'object' ||
|
|
302
|
+
Array.isArray(cookieInputs)
|
|
303
|
+
) {
|
|
304
|
+
return '';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const cookieParts = [];
|
|
308
|
+
Object.entries(cookieInputs).forEach(([key, rawValue]) => {
|
|
309
|
+
const cleanedValue = this.sanitizeBodyValue(rawValue);
|
|
310
|
+
if (cleanedValue === undefined) return;
|
|
311
|
+
|
|
312
|
+
let valueString = '';
|
|
313
|
+
if (Array.isArray(cleanedValue)) {
|
|
314
|
+
valueString = cleanedValue.map((item) => String(item)).join(',');
|
|
315
|
+
} else if (typeof cleanedValue === 'object') {
|
|
316
|
+
valueString = JSON.stringify(cleanedValue);
|
|
317
|
+
} else {
|
|
318
|
+
valueString = String(cleanedValue);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (valueString.length === 0) return;
|
|
322
|
+
cookieParts.push(key + '=' + encodeURIComponent(valueString));
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return cookieParts.join('; ');
|
|
326
|
+
},
|
|
327
|
+
buildQueryParams() {
|
|
328
|
+
const params = new URLSearchParams();
|
|
329
|
+
|
|
330
|
+
Object.entries(this.inputs.query || {}).forEach(([key, rawValue]) => {
|
|
331
|
+
const meta = this.queryFieldMeta[key] || {};
|
|
332
|
+
|
|
333
|
+
if (Array.isArray(rawValue) || meta.isArray) {
|
|
334
|
+
const values = (Array.isArray(rawValue) ? rawValue : [rawValue])
|
|
335
|
+
.map((value) => (value === undefined || value === null ? '' : String(value)))
|
|
336
|
+
.filter((value) => value !== '');
|
|
337
|
+
|
|
338
|
+
if (values.length === 0) return;
|
|
339
|
+
|
|
340
|
+
const style = meta.style || 'form';
|
|
341
|
+
const explode =
|
|
342
|
+
typeof meta.explode === 'boolean' ? meta.explode : style === 'form';
|
|
343
|
+
|
|
344
|
+
if (style === 'form') {
|
|
345
|
+
if (explode) {
|
|
346
|
+
values.forEach((value) => params.append(key, value));
|
|
347
|
+
} else {
|
|
348
|
+
params.append(key, values.join(','));
|
|
349
|
+
}
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (style === 'spaceDelimited') {
|
|
354
|
+
params.append(key, values.join(' '));
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (style === 'pipeDelimited') {
|
|
359
|
+
params.append(key, values.join('|'));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
values.forEach((value) => params.append(key, value));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (this.isPlainObject(rawValue) || meta.isObject) {
|
|
368
|
+
this.serializeQueryObject(params, key, rawValue, meta);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (rawValue === undefined || rawValue === null) return;
|
|
373
|
+
const value = String(rawValue);
|
|
374
|
+
if (value === '') return;
|
|
375
|
+
params.append(key, value);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return params.toString();
|
|
379
|
+
},
|
|
380
|
+
validateInputs(triggerEl) {
|
|
381
|
+
const rootFromTrigger =
|
|
382
|
+
triggerEl instanceof HTMLElement
|
|
383
|
+
? triggerEl.closest('[data-playground-form-root]')
|
|
384
|
+
: null;
|
|
385
|
+
const root = rootFromTrigger || this.$root || this.$el;
|
|
386
|
+
if (!(root instanceof HTMLElement)) return true;
|
|
387
|
+
|
|
388
|
+
const controls = root.querySelectorAll('input, select, textarea');
|
|
389
|
+
for (const control of controls) {
|
|
390
|
+
if (
|
|
391
|
+
!(control instanceof HTMLInputElement) &&
|
|
392
|
+
!(control instanceof HTMLSelectElement) &&
|
|
393
|
+
!(control instanceof HTMLTextAreaElement)
|
|
394
|
+
) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
if (control.disabled) continue;
|
|
398
|
+
if (control.type === 'hidden') continue;
|
|
399
|
+
if (control.getClientRects().length === 0) continue;
|
|
400
|
+
|
|
401
|
+
if (!control.reportValidity()) {
|
|
402
|
+
control.focus();
|
|
403
|
+
control.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return true;
|
|
409
|
+
},
|
|
410
|
+
async sendRequest(event) {
|
|
411
|
+
if (!this.validateInputs(event?.currentTarget)) return;
|
|
412
|
+
|
|
32
413
|
this.loading = true;
|
|
33
|
-
await new Promise(resolve => setTimeout(resolve, 2000))
|
|
34
414
|
try {
|
|
35
415
|
// 1. Construct URL with Path Parameters
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
url = url.replace('{' + key + '}', encodeURIComponent(val));
|
|
39
|
-
});
|
|
416
|
+
const urlTemplate = ${JSON.stringify(serverUrl + route.openApiPath)};
|
|
417
|
+
let url = this.resolveUrlPlaceholders(urlTemplate);
|
|
40
418
|
|
|
41
419
|
// 2. Add Query Parameters
|
|
42
|
-
const queryParams =
|
|
420
|
+
const queryParams = this.buildQueryParams();
|
|
43
421
|
if (queryParams) url += '?' + queryParams;
|
|
44
422
|
|
|
45
423
|
// 3. Prepare Fetch Options
|
|
46
|
-
console.log("Hello", this.inputs)
|
|
47
424
|
const options = {
|
|
48
425
|
method: ${JSON.stringify(route.openApiMethod.toUpperCase())},
|
|
49
426
|
headers: {
|
|
@@ -51,87 +428,117 @@ const { route, serverUrl, requestFields } = Astro.props;
|
|
|
51
428
|
...this.inputs.header
|
|
52
429
|
}
|
|
53
430
|
};
|
|
431
|
+
const cookieHeader = this.buildCookieHeader();
|
|
54
432
|
|
|
55
433
|
// 4. Add Body if needed
|
|
56
434
|
if (['POST', 'PUT', 'PATCH'].includes(options.method)) {
|
|
57
|
-
|
|
435
|
+
const sanitizedBody = this.sanitizeBodyValue(this.inputs.body);
|
|
436
|
+
if (sanitizedBody !== undefined) {
|
|
437
|
+
options.body = JSON.stringify(sanitizedBody);
|
|
438
|
+
} else {
|
|
439
|
+
const defaultBody = this.getDefaultRequestBody();
|
|
440
|
+
if (defaultBody !== undefined) {
|
|
441
|
+
options.body = JSON.stringify(defaultBody);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
58
444
|
}
|
|
59
445
|
|
|
60
|
-
// 5.
|
|
61
|
-
|
|
446
|
+
// 5. Proxy Logic
|
|
447
|
+
let finalUrl = url;
|
|
448
|
+
let finalOptions = options;
|
|
62
449
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
450
|
+
const isLocal = (url) => {
|
|
451
|
+
try {
|
|
452
|
+
const hostname = new URL(url).hostname;
|
|
453
|
+
return hostname === 'localhost' ||
|
|
454
|
+
hostname === '127.0.0.1' ||
|
|
455
|
+
hostname.endsWith('.local');
|
|
456
|
+
} catch (e) {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
70
459
|
};
|
|
71
|
-
const statusText = res.statusText || statusTextMap[status] || 'Unknown';
|
|
72
460
|
|
|
73
|
-
|
|
461
|
+
if (${proxyEnabled} && !isLocal(url)) {
|
|
462
|
+
finalUrl = ${JSON.stringify(proxyUrl)};
|
|
463
|
+
finalOptions = {
|
|
464
|
+
...options,
|
|
465
|
+
headers: {
|
|
466
|
+
...options.headers,
|
|
467
|
+
'x-proxy-url': url,
|
|
468
|
+
...(cookieHeader ? { 'x-proxy-cookie': cookieHeader } : {})
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
console.log("SEND", finalUrl, finalOptions)
|
|
473
|
+
// 6. Execute Request
|
|
474
|
+
const res = await fetch(finalUrl, finalOptions);
|
|
475
|
+
|
|
476
|
+
const status = res.status;
|
|
477
|
+
|
|
478
|
+
const responseHeaders = {};
|
|
74
479
|
res.headers.forEach((value, key) => {
|
|
75
|
-
|
|
480
|
+
responseHeaders[key] = value;
|
|
76
481
|
});
|
|
77
482
|
|
|
78
|
-
let data;
|
|
79
483
|
let highlightedData;
|
|
484
|
+
const highlightedHeaders = Prism.highlight(
|
|
485
|
+
JSON.stringify(responseHeaders, null, 2),
|
|
486
|
+
Prism.languages.json,
|
|
487
|
+
"json",
|
|
488
|
+
);
|
|
80
489
|
const contentType = res.headers.get('content-type') || '';
|
|
490
|
+
const responseText = await res.text();
|
|
81
491
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
492
|
+
if (contentType.includes('application/json')) {
|
|
493
|
+
try {
|
|
494
|
+
const parsed =
|
|
495
|
+
responseText.trim().length > 0 ? JSON.parse(responseText) : null;
|
|
85
496
|
highlightedData = Prism.highlight(
|
|
86
|
-
JSON.stringify(
|
|
87
|
-
Prism.languages.json,
|
|
497
|
+
JSON.stringify(parsed, null, 2),
|
|
498
|
+
Prism.languages.json,
|
|
88
499
|
'json'
|
|
89
500
|
);
|
|
90
|
-
}
|
|
91
|
-
// For non-JSON responses, try to get as text
|
|
92
|
-
const text = await res.text();
|
|
93
|
-
console.log("TEXT", text)
|
|
501
|
+
} catch (parseError) {
|
|
94
502
|
highlightedData = Prism.highlight(
|
|
95
|
-
|
|
96
|
-
Prism.languages.plaintext,
|
|
503
|
+
responseText || '(empty response)',
|
|
504
|
+
Prism.languages.plaintext,
|
|
97
505
|
'plaintext'
|
|
98
506
|
);
|
|
99
507
|
}
|
|
100
|
-
}
|
|
101
|
-
// If parsing fails, still show the response with error info
|
|
508
|
+
} else {
|
|
102
509
|
highlightedData = Prism.highlight(
|
|
103
|
-
|
|
104
|
-
Prism.languages.
|
|
105
|
-
'
|
|
510
|
+
responseText || '(empty response)',
|
|
511
|
+
Prism.languages.plaintext,
|
|
512
|
+
'plaintext'
|
|
106
513
|
);
|
|
107
514
|
}
|
|
108
515
|
|
|
109
516
|
this.response = {
|
|
110
517
|
status,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
518
|
+
headers: responseHeaders,
|
|
519
|
+
highlightedData,
|
|
520
|
+
highlightedHeaders
|
|
114
521
|
};
|
|
115
522
|
|
|
116
523
|
} catch (err) {
|
|
117
|
-
console.
|
|
524
|
+
console.error('Playground error:', err);
|
|
118
525
|
this.response = {
|
|
119
526
|
status: err.status || 0,
|
|
120
|
-
statusText: err.statusText || err.message || 'Network Error',
|
|
121
527
|
};
|
|
122
528
|
} finally {
|
|
123
529
|
this.loading = false;
|
|
124
530
|
}
|
|
125
531
|
}
|
|
126
532
|
}`
|
|
127
|
-
|
|
533
|
+
data-playground-form-root
|
|
534
|
+
class="flex w-full h-full min-h-0 flex-col"
|
|
128
535
|
>
|
|
129
|
-
|
|
536
|
+
|
|
130
537
|
<PlaygroundBar route={route} serverUrl={serverUrl}>
|
|
131
538
|
<button
|
|
132
|
-
@click="sendRequest()"
|
|
539
|
+
@click="sendRequest($event)"
|
|
133
540
|
:disabled="loading"
|
|
134
|
-
class="m-px flex items-center gap-1.5 px-4 py-[5px] text-sm font-medium rounded-lg bg-neutral-900 text-white/95 hover:text-white shadow-[inset_0_1px_0_rgb(255,255,255,0.3),0_0_0_1px_var(--color-neutral-800)] duration-200 whitespace-nowrap cursor-pointer relative before:absolute before:inset-0 before:shadow-sm before:rounded-lg before:bg-transparent disabled:
|
|
541
|
+
class="m-px flex items-center gap-1.5 px-4 py-[5px] text-sm font-medium rounded-lg bg-neutral-900 text-white/95 hover:text-white shadow-[inset_0_1px_0_rgb(255,255,255,0.3),0_0_0_1px_var(--color-neutral-800)] duration-200 whitespace-nowrap cursor-pointer relative before:absolute before:inset-0 before:shadow-sm before:rounded-lg before:bg-transparent disabled:opacity-70"
|
|
135
542
|
>
|
|
136
543
|
<span
|
|
137
544
|
class="flex items-center gap-2"
|
|
@@ -147,31 +554,122 @@ const { route, serverUrl, requestFields } = Astro.props;
|
|
|
147
554
|
/>
|
|
148
555
|
</button>
|
|
149
556
|
</PlaygroundBar>
|
|
150
|
-
</div>
|
|
151
557
|
|
|
152
|
-
|
|
153
|
-
|
|
558
|
+
|
|
559
|
+
<div class="min-h-0 flex-1 flex flex-col lg:flex-row-reverse">
|
|
560
|
+
<div class="lg:flex-1 w-full min-h-0 h-[calc(40dvh-2rem-23px-12px)] max-h-[calc(40dvh-2rem-23px-12px)] lg:h-auto lg:max-h-[calc(100dvh-4rem-46px-24px)] overflow-hidden pt-4 lg:pb-6">
|
|
154
561
|
<ResponseDisplay />
|
|
155
562
|
</div>
|
|
156
|
-
<div class="flex-
|
|
563
|
+
<div class="lg:flex-1 relative">
|
|
564
|
+
<div class="flex-3 min-h-0 max-h-[calc(60dvh-2rem-23px-12px)] lg:max-h-[calc(100dvh-4rem-46px-24px)] overflow-y-auto overscroll-contain [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">
|
|
565
|
+
<div class="pointer-events-none sticky top-0 z-10 -mb-4 h-4 bg-linear-to-b from-background via-white/60 to-transparent"></div>
|
|
566
|
+
<div class="space-y-4 lg:pr-4 pt-4 pb-6">
|
|
157
567
|
{
|
|
158
568
|
Object.keys(requestFields).map(
|
|
159
|
-
(key) =>
|
|
160
|
-
requestFields[key as keyof RequestFields]
|
|
569
|
+
(key) => {
|
|
570
|
+
const sectionFields = requestFields[key as keyof RequestFields];
|
|
571
|
+
const sectionVariantData =
|
|
572
|
+
requestSectionVariants[key as keyof RequestFields];
|
|
573
|
+
const sectionVariants = sectionVariantData?.variants || [];
|
|
574
|
+
const keyLiteral = JSON.stringify(key);
|
|
575
|
+
|
|
576
|
+
if (sectionFields.length === 0 && sectionVariants.length === 0) {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return (
|
|
161
581
|
<div class="border border-neutral-200 shadow-xs rounded-xl p-4">
|
|
162
|
-
<
|
|
163
|
-
{
|
|
582
|
+
<Accordion title={headers[key]} defaultOpen titleSize="xl">
|
|
583
|
+
{key === "body" && formattedBodyDescription && (
|
|
584
|
+
<div
|
|
585
|
+
class="mb-4 prose-rules prose-sm! text-neutral-500 **:text-neutral-500"
|
|
586
|
+
set:html={formattedBodyDescription}
|
|
587
|
+
/>
|
|
588
|
+
)}
|
|
589
|
+
{sectionFields.map((field) => (
|
|
164
590
|
<div class="border-b border-b-neutral-100 last:border-none pb-4 mb-4 last:pb-0 last:mb-0 first:pt-2">
|
|
165
591
|
<PlaygroundField field={field} requestPart={key} />
|
|
166
592
|
</div>
|
|
167
593
|
))}
|
|
168
|
-
|
|
594
|
+
{sectionVariants.length > 0 && (
|
|
595
|
+
<div class:list={["space-y-2", sectionFields.length > 0 && "pt-1"]}>
|
|
596
|
+
{sectionVariantData?.variantType === "oneOf" ? (
|
|
597
|
+
<>
|
|
598
|
+
<p class="text-xs text-neutral-500">
|
|
599
|
+
Select one variant.
|
|
600
|
+
</p>
|
|
601
|
+
<div class="inline-flex max-w-full flex-wrap items-center gap-1 rounded-lg bg-neutral-100 p-1">
|
|
602
|
+
{sectionVariants.map((variant, variantIndex) => (
|
|
603
|
+
<button
|
|
604
|
+
type="button"
|
|
605
|
+
@click={`selectSectionVariant(${keyLiteral}, ${variantIndex})`}
|
|
606
|
+
:class={`{
|
|
607
|
+
'bg-white text-neutral-900 shadow-xs ring-1 ring-neutral-200': getSelectedSectionVariant(${keyLiteral}) === ${variantIndex},
|
|
608
|
+
'text-neutral-600 hover:bg-neutral-50 hover:text-neutral-800': getSelectedSectionVariant(${keyLiteral}) !== ${variantIndex}
|
|
609
|
+
}`}
|
|
610
|
+
class="inline-flex h-7 items-center justify-center whitespace-nowrap rounded-md px-2.5 text-[11px] font-medium transition-all duration-200 cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-300"
|
|
611
|
+
>
|
|
612
|
+
{variant.label}
|
|
613
|
+
</button>
|
|
614
|
+
))}
|
|
615
|
+
</div>
|
|
616
|
+
{sectionVariants.map((variant, variantIndex) => (
|
|
617
|
+
<div
|
|
618
|
+
x-show={`getSelectedSectionVariant(${keyLiteral}) === ${variantIndex}`}
|
|
619
|
+
x-cloak
|
|
620
|
+
>
|
|
621
|
+
<div class="rounded-md border border-neutral-200 bg-white p-2.5">
|
|
622
|
+
<div class="mb-2 text-xs font-medium text-neutral-600">
|
|
623
|
+
{variant.label}
|
|
624
|
+
</div>
|
|
625
|
+
<div class="space-y-3">
|
|
626
|
+
{variant.fields.map((field) => (
|
|
627
|
+
<div class="border-b border-b-neutral-100 last:border-none pb-3 last:pb-0">
|
|
628
|
+
<PlaygroundField
|
|
629
|
+
field={field}
|
|
630
|
+
requestPart={key}
|
|
631
|
+
defaultsEnabledExpr={`getSelectedSectionVariant(${keyLiteral}) === ${variantIndex}`}
|
|
632
|
+
/>
|
|
633
|
+
</div>
|
|
634
|
+
))}
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
))}
|
|
639
|
+
</>
|
|
640
|
+
) : (
|
|
641
|
+
<>
|
|
642
|
+
<p class="text-xs text-neutral-500">
|
|
643
|
+
One or more variants may apply.
|
|
644
|
+
</p>
|
|
645
|
+
{sectionVariants.map((variant) => (
|
|
646
|
+
<div class="rounded-md border border-neutral-200 bg-white p-2.5">
|
|
647
|
+
<div class="mb-2 text-xs font-medium text-neutral-600">
|
|
648
|
+
{variant.label}
|
|
649
|
+
</div>
|
|
650
|
+
<div class="space-y-3">
|
|
651
|
+
{variant.fields.map((field) => (
|
|
652
|
+
<div class="border-b border-b-neutral-100 last:border-none pb-3 last:pb-0">
|
|
653
|
+
<PlaygroundField field={field} requestPart={key} />
|
|
654
|
+
</div>
|
|
655
|
+
))}
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
))}
|
|
659
|
+
</>
|
|
660
|
+
)}
|
|
661
|
+
</div>
|
|
662
|
+
)}
|
|
663
|
+
</Accordion>
|
|
169
664
|
</div>
|
|
170
|
-
)
|
|
665
|
+
);
|
|
666
|
+
},
|
|
171
667
|
)
|
|
172
668
|
}
|
|
669
|
+
</div>
|
|
173
670
|
</div>
|
|
174
671
|
</div>
|
|
672
|
+
</div>
|
|
175
673
|
</div>
|
|
176
674
|
|
|
177
675
|
<script>
|