radiant-docs 0.1.64 → 0.1.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -3
- package/template/package-lock.json +1991 -4096
- package/template/package.json +7 -19
- package/template/src/components/OpenApiPage.astro +107 -18
- package/template/src/components/Sidebar.astro +1 -0
- package/template/src/components/endpoint/PlaygroundBar.astro +1 -1
- package/template/src/components/endpoint/PlaygroundField.astro +433 -71
- package/template/src/components/endpoint/PlaygroundForm.astro +470 -87
- package/template/src/components/endpoint/RequestSnippets.astro +6 -1
- package/template/src/components/endpoint/ResponseDisplay.astro +109 -2
- package/template/src/components/endpoint/ResponseFieldTree.astro +1 -1
- package/template/src/components/endpoint/ResponseFields.astro +97 -28
- package/template/src/components/endpoint/ResponseSnippets.astro +19 -7
- package/template/src/layouts/Layout.astro +94 -0
- package/template/src/lib/openapi/operation-doc.ts +308 -70
- package/template/src/lib/openapi/response-content.ts +133 -0
- package/template/src/lib/openapi/schema-variant-labels.ts +213 -0
- package/template/.vscode/extensions.json +0 -4
- package/template/.vscode/launch.json +0 -11
- package/template/scripts/generate-og-images.mjs +0 -667
- package/template/scripts/generate-og-metadata.mjs +0 -206
- package/template/scripts/generate-proxy-allowed-origins.mjs +0 -48
- package/template/scripts/generate-robots-txt.mjs +0 -47
- package/template/scripts/publish-shiki-platform-assets.mjs +0 -1177
- package/template/scripts/remove-assistant-for-non-pro.mjs +0 -28
- package/template/scripts/stamp-image-versions.mjs +0 -355
- package/template/scripts/stamp-og-image-versions.mjs +0 -199
- package/template/scripts/stamp-pagefind-runtime-version.mjs +0 -140
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type OpenApiRequestFields,
|
|
11
11
|
type OpenApiRequestSectionVariantData,
|
|
12
12
|
} from "../../lib/openapi/operation-doc";
|
|
13
|
-
import
|
|
13
|
+
import ListChevronsToggle from "../ui/ListChevronsToggle.astro";
|
|
14
14
|
import PlaygroundBar from "./PlaygroundBar.astro";
|
|
15
15
|
import ResponseDisplay from "./ResponseDisplay.astro";
|
|
16
16
|
import PlaygroundField from "./PlaygroundField.astro";
|
|
@@ -27,6 +27,7 @@ interface Props {
|
|
|
27
27
|
>;
|
|
28
28
|
bodyDescription?: string;
|
|
29
29
|
bodyDefaultKind?: "object" | "array";
|
|
30
|
+
requestBodyContentType?: string;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const {
|
|
@@ -36,6 +37,7 @@ const {
|
|
|
36
37
|
requestSectionVariants = {},
|
|
37
38
|
bodyDescription = "",
|
|
38
39
|
bodyDefaultKind,
|
|
40
|
+
requestBodyContentType,
|
|
39
41
|
} = Astro.props;
|
|
40
42
|
const headers: Record<string, string> = OPENAPI_REQUEST_SECTION_LABELS;
|
|
41
43
|
const config = await getConfig();
|
|
@@ -70,6 +72,11 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
70
72
|
),
|
|
71
73
|
]),
|
|
72
74
|
);
|
|
75
|
+
const headerValuePrefixes = Object.fromEntries(
|
|
76
|
+
requestFields.header
|
|
77
|
+
.filter((field) => field.valuePrefix)
|
|
78
|
+
.map((field) => [field.name.toLowerCase(), field.valuePrefix]),
|
|
79
|
+
);
|
|
73
80
|
---
|
|
74
81
|
|
|
75
82
|
<div
|
|
@@ -77,8 +84,10 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
77
84
|
loading: false,
|
|
78
85
|
response: null,
|
|
79
86
|
queryFieldMeta: ${JSON.stringify(queryFieldMeta)},
|
|
87
|
+
headerValuePrefixes: ${JSON.stringify(headerValuePrefixes)},
|
|
80
88
|
sectionVariantFieldNames: ${JSON.stringify(sectionVariantFieldNames)},
|
|
81
89
|
bodyDefaultKind: ${JSON.stringify(bodyDefaultKind ?? null)},
|
|
90
|
+
requestBodyContentType: ${JSON.stringify(requestBodyContentType ?? null)},
|
|
82
91
|
shiki: ${JSON.stringify(shikiRuntimeConfig ?? null)},
|
|
83
92
|
selectedSectionVariants: {},
|
|
84
93
|
inputs: {
|
|
@@ -88,6 +97,9 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
88
97
|
cookie: {},
|
|
89
98
|
body: {}
|
|
90
99
|
},
|
|
100
|
+
destroy() {
|
|
101
|
+
this.revokeResponseObjectUrl();
|
|
102
|
+
},
|
|
91
103
|
getSectionVariantFieldNames(section, variantIndex) {
|
|
92
104
|
const sectionVariants = this.sectionVariantFieldNames[section] || [];
|
|
93
105
|
const variantNames = sectionVariants[variantIndex];
|
|
@@ -179,6 +191,10 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
179
191
|
return undefined;
|
|
180
192
|
}
|
|
181
193
|
|
|
194
|
+
if (this.isFileValue(value)) {
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
|
|
182
198
|
if (Array.isArray(value)) {
|
|
183
199
|
const cleaned = value
|
|
184
200
|
.map((item) => this.sanitizeBodyValue(item))
|
|
@@ -201,8 +217,217 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
201
217
|
return (
|
|
202
218
|
value !== null &&
|
|
203
219
|
typeof value === 'object' &&
|
|
204
|
-
!Array.isArray(value)
|
|
220
|
+
!Array.isArray(value) &&
|
|
221
|
+
!this.isFileValue(value)
|
|
222
|
+
);
|
|
223
|
+
},
|
|
224
|
+
isFileValue(value) {
|
|
225
|
+
return (
|
|
226
|
+
(typeof File !== 'undefined' && value instanceof File) ||
|
|
227
|
+
(typeof Blob !== 'undefined' && value instanceof Blob)
|
|
228
|
+
);
|
|
229
|
+
},
|
|
230
|
+
revokeResponseObjectUrl() {
|
|
231
|
+
const objectUrl = this.response?.bodyObjectUrl;
|
|
232
|
+
if (objectUrl && typeof URL !== 'undefined') {
|
|
233
|
+
URL.revokeObjectURL(objectUrl);
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
getBaseContentType(contentType) {
|
|
237
|
+
return String(contentType || '').split(';')[0].trim().toLowerCase();
|
|
238
|
+
},
|
|
239
|
+
getMediaKind(contentType) {
|
|
240
|
+
const baseContentType = this.getBaseContentType(contentType);
|
|
241
|
+
if (baseContentType.startsWith('audio/')) return 'audio';
|
|
242
|
+
if (baseContentType.startsWith('image/')) return 'image';
|
|
243
|
+
if (baseContentType.startsWith('video/')) return 'video';
|
|
244
|
+
return null;
|
|
245
|
+
},
|
|
246
|
+
getTextPreviewLanguage(contentType, filename) {
|
|
247
|
+
const baseContentType = this.getBaseContentType(contentType);
|
|
248
|
+
const normalizedFilename = String(filename || '').toLowerCase();
|
|
249
|
+
if (baseContentType === 'text/event-stream') return 'yaml';
|
|
250
|
+
if (
|
|
251
|
+
baseContentType === 'application/json' ||
|
|
252
|
+
baseContentType.endsWith('+json') ||
|
|
253
|
+
normalizedFilename.endsWith('.json') ||
|
|
254
|
+
normalizedFilename.endsWith('.jsonl')
|
|
255
|
+
) {
|
|
256
|
+
return 'json';
|
|
257
|
+
}
|
|
258
|
+
if (
|
|
259
|
+
baseContentType === 'application/yaml' ||
|
|
260
|
+
baseContentType === 'application/x-yaml' ||
|
|
261
|
+
normalizedFilename.endsWith('.yaml') ||
|
|
262
|
+
normalizedFilename.endsWith('.yml')
|
|
263
|
+
) {
|
|
264
|
+
return 'yaml';
|
|
265
|
+
}
|
|
266
|
+
return 'plaintext';
|
|
267
|
+
},
|
|
268
|
+
isJsonContentType(contentType) {
|
|
269
|
+
const baseContentType = this.getBaseContentType(contentType);
|
|
270
|
+
return baseContentType === 'application/json' || baseContentType.endsWith('+json');
|
|
271
|
+
},
|
|
272
|
+
isTextLikeFilename(filename) {
|
|
273
|
+
const normalizedFilename = String(filename || '').toLowerCase();
|
|
274
|
+
return [
|
|
275
|
+
'.csv',
|
|
276
|
+
'.css',
|
|
277
|
+
'.html',
|
|
278
|
+
'.js',
|
|
279
|
+
'.json',
|
|
280
|
+
'.jsonl',
|
|
281
|
+
'.md',
|
|
282
|
+
'.mjs',
|
|
283
|
+
'.svg',
|
|
284
|
+
'.ts',
|
|
285
|
+
'.txt',
|
|
286
|
+
'.xml',
|
|
287
|
+
'.yaml',
|
|
288
|
+
'.yml',
|
|
289
|
+
].some((extension) => normalizedFilename.endsWith(extension));
|
|
290
|
+
},
|
|
291
|
+
isTextPreviewContentType(contentType, filename) {
|
|
292
|
+
const baseContentType = this.getBaseContentType(contentType);
|
|
293
|
+
if (!baseContentType) return true;
|
|
294
|
+
if (baseContentType.startsWith('text/')) return true;
|
|
295
|
+
if (this.isJsonContentType(baseContentType)) return true;
|
|
296
|
+
if (baseContentType === 'application/x-ndjson') return true;
|
|
297
|
+
if (baseContentType === 'application/xml' || baseContentType.endsWith('+xml')) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (
|
|
301
|
+
baseContentType === 'application/javascript' ||
|
|
302
|
+
baseContentType === 'application/x-javascript'
|
|
303
|
+
) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
return this.isTextLikeFilename(filename);
|
|
307
|
+
},
|
|
308
|
+
getFilenameFromContentDisposition(contentDisposition) {
|
|
309
|
+
const parts = String(contentDisposition || '')
|
|
310
|
+
.split(';')
|
|
311
|
+
.map((part) => part.trim())
|
|
312
|
+
.filter(Boolean);
|
|
313
|
+
const filenameStarPart = parts.find((part) =>
|
|
314
|
+
part.toLowerCase().startsWith('filename*='),
|
|
205
315
|
);
|
|
316
|
+
const filenamePart =
|
|
317
|
+
filenameStarPart ||
|
|
318
|
+
parts.find((part) => part.toLowerCase().startsWith('filename='));
|
|
319
|
+
|
|
320
|
+
if (!filenamePart) return '';
|
|
321
|
+
|
|
322
|
+
let value = filenamePart.slice(filenamePart.indexOf('=') + 1).trim();
|
|
323
|
+
if (
|
|
324
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
325
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
326
|
+
) {
|
|
327
|
+
value = value.slice(1, -1);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (filenameStarPart && value.toLowerCase().startsWith("utf-8''")) {
|
|
331
|
+
value = value.slice(7);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
value = decodeURIComponent(value);
|
|
336
|
+
} catch (_error) {
|
|
337
|
+
// Leave non-percent-encoded filenames as-is.
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return value.split(String.fromCharCode(92)).join('/').split('/').pop() || '';
|
|
341
|
+
},
|
|
342
|
+
getResponseFileExtension(contentType) {
|
|
343
|
+
const baseContentType = this.getBaseContentType(contentType);
|
|
344
|
+
const extensionByType = {
|
|
345
|
+
'application/gzip': '.gz',
|
|
346
|
+
'application/json': '.json',
|
|
347
|
+
'application/octet-stream': '.bin',
|
|
348
|
+
'application/pdf': '.pdf',
|
|
349
|
+
'application/zip': '.zip',
|
|
350
|
+
'audio/aac': '.aac',
|
|
351
|
+
'audio/flac': '.flac',
|
|
352
|
+
'audio/mpeg': '.mp3',
|
|
353
|
+
'audio/ogg': '.ogg',
|
|
354
|
+
'audio/wav': '.wav',
|
|
355
|
+
'audio/webm': '.webm',
|
|
356
|
+
'image/gif': '.gif',
|
|
357
|
+
'image/jpeg': '.jpg',
|
|
358
|
+
'image/png': '.png',
|
|
359
|
+
'image/svg+xml': '.svg',
|
|
360
|
+
'image/webp': '.webp',
|
|
361
|
+
'text/csv': '.csv',
|
|
362
|
+
'text/plain': '.txt',
|
|
363
|
+
'video/mp4': '.mp4',
|
|
364
|
+
'video/mpeg': '.mpeg',
|
|
365
|
+
'video/quicktime': '.mov',
|
|
366
|
+
'video/webm': '.webm',
|
|
367
|
+
};
|
|
368
|
+
return extensionByType[baseContentType] || '';
|
|
369
|
+
},
|
|
370
|
+
getResponseFilename(headers, contentType) {
|
|
371
|
+
const disposition =
|
|
372
|
+
headers['content-disposition'] || headers['Content-Disposition'] || '';
|
|
373
|
+
const filename = this.getFilenameFromContentDisposition(disposition);
|
|
374
|
+
if (filename) return filename;
|
|
375
|
+
return 'response' + this.getResponseFileExtension(contentType);
|
|
376
|
+
},
|
|
377
|
+
formatByteSize(bytes) {
|
|
378
|
+
if (!Number.isFinite(bytes)) return '';
|
|
379
|
+
if (bytes < 1024) return bytes + ' B';
|
|
380
|
+
const units = ['KB', 'MB', 'GB'];
|
|
381
|
+
let size = bytes / 1024;
|
|
382
|
+
let unitIndex = 0;
|
|
383
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
384
|
+
size = size / 1024;
|
|
385
|
+
unitIndex += 1;
|
|
386
|
+
}
|
|
387
|
+
return size.toFixed(size >= 10 ? 1 : 2) + ' ' + units[unitIndex];
|
|
388
|
+
},
|
|
389
|
+
buildFormData(rawValue) {
|
|
390
|
+
const cleanedValue = this.sanitizeBodyValue(rawValue);
|
|
391
|
+
if (cleanedValue === undefined) return undefined;
|
|
392
|
+
|
|
393
|
+
const formData = new FormData();
|
|
394
|
+
let hasEntries = false;
|
|
395
|
+
|
|
396
|
+
const appendValue = (key, value) => {
|
|
397
|
+
const cleaned = this.sanitizeBodyValue(value);
|
|
398
|
+
if (cleaned === undefined) return;
|
|
399
|
+
|
|
400
|
+
if (this.isFileValue(cleaned)) {
|
|
401
|
+
formData.append(key, cleaned);
|
|
402
|
+
hasEntries = true;
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (Array.isArray(cleaned)) {
|
|
407
|
+
cleaned.forEach((item) => appendValue(key, item));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (this.isPlainObject(cleaned)) {
|
|
412
|
+
Object.entries(cleaned).forEach(([nestedKey, nestedValue]) => {
|
|
413
|
+
appendValue(key + '[' + nestedKey + ']', nestedValue);
|
|
414
|
+
});
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
formData.append(key, String(cleaned));
|
|
419
|
+
hasEntries = true;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
if (this.isPlainObject(cleanedValue)) {
|
|
423
|
+
Object.entries(cleanedValue).forEach(([key, value]) => {
|
|
424
|
+
appendValue(key, value);
|
|
425
|
+
});
|
|
426
|
+
} else {
|
|
427
|
+
appendValue('body', cleanedValue);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return hasEntries ? formData : undefined;
|
|
206
431
|
},
|
|
207
432
|
serializeQueryObject(params, key, rawValue, meta = {}) {
|
|
208
433
|
const cleanedObject = this.sanitizeBodyValue(rawValue);
|
|
@@ -334,6 +559,37 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
334
559
|
|
|
335
560
|
return cookieParts.join('; ');
|
|
336
561
|
},
|
|
562
|
+
formatHeaderValue(name, value) {
|
|
563
|
+
const valueText =
|
|
564
|
+
value === undefined || value === null ? '' : String(value).trim();
|
|
565
|
+
if (!valueText) return '';
|
|
566
|
+
|
|
567
|
+
const prefix = this.headerValuePrefixes[String(name).toLowerCase()];
|
|
568
|
+
if (!prefix) return valueText;
|
|
569
|
+
|
|
570
|
+
const prefixText = String(prefix).trim();
|
|
571
|
+
if (!prefixText) return valueText;
|
|
572
|
+
|
|
573
|
+
return valueText.toLowerCase().startsWith(prefixText.toLowerCase() + ' ')
|
|
574
|
+
? valueText
|
|
575
|
+
: prefixText + ' ' + valueText;
|
|
576
|
+
},
|
|
577
|
+
buildRequestHeaders() {
|
|
578
|
+
const headers = {};
|
|
579
|
+
const contentType = String(this.requestBodyContentType || '').toLowerCase();
|
|
580
|
+
if (!contentType.includes('multipart/form-data')) {
|
|
581
|
+
headers['Content-Type'] = this.requestBodyContentType || 'application/json';
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
Object.entries(this.inputs.header || {}).forEach(([key, value]) => {
|
|
585
|
+
const headerName = String(key).trim();
|
|
586
|
+
const headerValue = this.formatHeaderValue(headerName, value);
|
|
587
|
+
if (!headerName || !headerValue) return;
|
|
588
|
+
headers[headerName] = headerValue;
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
return headers;
|
|
592
|
+
},
|
|
337
593
|
buildQueryParams() {
|
|
338
594
|
const params = new URLSearchParams();
|
|
339
595
|
|
|
@@ -420,6 +676,7 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
420
676
|
async sendRequest(event) {
|
|
421
677
|
if (!this.validateInputs(event?.currentTarget)) return;
|
|
422
678
|
|
|
679
|
+
this.revokeResponseObjectUrl();
|
|
423
680
|
this.loading = true;
|
|
424
681
|
try {
|
|
425
682
|
// 1. Construct URL with Path Parameters
|
|
@@ -433,22 +690,30 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
433
690
|
// 3. Prepare Fetch Options
|
|
434
691
|
const options = {
|
|
435
692
|
method: ${JSON.stringify(route.openApiMethod.toUpperCase())},
|
|
436
|
-
headers:
|
|
437
|
-
'Content-Type': 'application/json',
|
|
438
|
-
...this.inputs.header
|
|
439
|
-
}
|
|
693
|
+
headers: this.buildRequestHeaders()
|
|
440
694
|
};
|
|
441
695
|
const cookieHeader = this.buildCookieHeader();
|
|
442
696
|
|
|
443
697
|
// 4. Add Body if needed
|
|
444
698
|
if (['POST', 'PUT', 'PATCH'].includes(options.method)) {
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
699
|
+
const isMultipartBody = String(this.requestBodyContentType || '')
|
|
700
|
+
.toLowerCase()
|
|
701
|
+
.includes('multipart/form-data');
|
|
702
|
+
|
|
703
|
+
if (isMultipartBody) {
|
|
704
|
+
const formData = this.buildFormData(this.inputs.body);
|
|
705
|
+
if (formData !== undefined) {
|
|
706
|
+
options.body = formData;
|
|
707
|
+
}
|
|
448
708
|
} else {
|
|
449
|
-
const
|
|
450
|
-
if (
|
|
451
|
-
options.body = JSON.stringify(
|
|
709
|
+
const sanitizedBody = this.sanitizeBodyValue(this.inputs.body);
|
|
710
|
+
if (sanitizedBody !== undefined) {
|
|
711
|
+
options.body = JSON.stringify(sanitizedBody);
|
|
712
|
+
} else {
|
|
713
|
+
const defaultBody = this.getDefaultRequestBody();
|
|
714
|
+
if (defaultBody !== undefined) {
|
|
715
|
+
options.body = JSON.stringify(defaultBody);
|
|
716
|
+
}
|
|
452
717
|
}
|
|
453
718
|
}
|
|
454
719
|
}
|
|
@@ -498,9 +763,54 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
498
763
|
this.shiki,
|
|
499
764
|
);
|
|
500
765
|
const contentType = res.headers.get('content-type') || '';
|
|
766
|
+
const responseFilename = this.getResponseFilename(responseHeaders, contentType);
|
|
767
|
+
const mediaKind = this.getMediaKind(contentType);
|
|
768
|
+
const shouldPreviewAsText = this.isTextPreviewContentType(
|
|
769
|
+
contentType,
|
|
770
|
+
responseFilename,
|
|
771
|
+
);
|
|
772
|
+
|
|
773
|
+
if (mediaKind) {
|
|
774
|
+
const blob = await res.blob();
|
|
775
|
+
const bodyObjectUrl = URL.createObjectURL(blob);
|
|
776
|
+
this.response = {
|
|
777
|
+
status,
|
|
778
|
+
headers: responseHeaders,
|
|
779
|
+
bodyKind: 'media',
|
|
780
|
+
mediaKind,
|
|
781
|
+
bodyObjectUrl,
|
|
782
|
+
bodyContentType: contentType,
|
|
783
|
+
bodyFilename: responseFilename,
|
|
784
|
+
bodySize: blob.size,
|
|
785
|
+
bodySizeLabel: this.formatByteSize(blob.size),
|
|
786
|
+
highlightedHeaders
|
|
787
|
+
};
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (!shouldPreviewAsText) {
|
|
792
|
+
const blob = await res.blob();
|
|
793
|
+
const bodyObjectUrl = URL.createObjectURL(blob);
|
|
794
|
+
this.response = {
|
|
795
|
+
status,
|
|
796
|
+
headers: responseHeaders,
|
|
797
|
+
bodyKind: 'binary',
|
|
798
|
+
bodyObjectUrl,
|
|
799
|
+
bodyContentType: contentType,
|
|
800
|
+
bodyFilename: responseFilename,
|
|
801
|
+
bodySize: blob.size,
|
|
802
|
+
bodySizeLabel: this.formatByteSize(blob.size),
|
|
803
|
+
highlightedHeaders
|
|
804
|
+
};
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
|
|
501
808
|
const responseText = await res.text();
|
|
502
809
|
|
|
503
|
-
if (contentType.includes('
|
|
810
|
+
if (contentType.toLowerCase().includes('text/event-stream')) {
|
|
811
|
+
responseDataText = responseText || '(empty response)';
|
|
812
|
+
responseDataLanguage = 'yaml';
|
|
813
|
+
} else if (this.isJsonContentType(contentType)) {
|
|
504
814
|
try {
|
|
505
815
|
const parsed =
|
|
506
816
|
responseText.trim().length > 0 ? JSON.parse(responseText) : null;
|
|
@@ -508,9 +818,17 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
508
818
|
responseDataLanguage = 'json';
|
|
509
819
|
} catch (parseError) {
|
|
510
820
|
responseDataText = responseText || '(empty response)';
|
|
821
|
+
responseDataLanguage = this.getTextPreviewLanguage(
|
|
822
|
+
contentType,
|
|
823
|
+
responseFilename,
|
|
824
|
+
);
|
|
511
825
|
}
|
|
512
826
|
} else {
|
|
513
827
|
responseDataText = responseText || '(empty response)';
|
|
828
|
+
responseDataLanguage = this.getTextPreviewLanguage(
|
|
829
|
+
contentType,
|
|
830
|
+
responseFilename,
|
|
831
|
+
);
|
|
514
832
|
}
|
|
515
833
|
const highlightedData = await window.RadiantPlaygroundHighlightCode(
|
|
516
834
|
responseDataText,
|
|
@@ -521,6 +839,9 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
521
839
|
this.response = {
|
|
522
840
|
status,
|
|
523
841
|
headers: responseHeaders,
|
|
842
|
+
bodyKind: 'code',
|
|
843
|
+
bodyContentType: contentType,
|
|
844
|
+
bodyFilename: responseFilename,
|
|
524
845
|
highlightedData,
|
|
525
846
|
highlightedHeaders
|
|
526
847
|
};
|
|
@@ -538,7 +859,7 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
538
859
|
data-playground-form-root
|
|
539
860
|
class="flex w-full h-full min-h-0 flex-col"
|
|
540
861
|
>
|
|
541
|
-
|
|
862
|
+
<div>
|
|
542
863
|
<PlaygroundBar route={route} serverUrl={serverUrl}>
|
|
543
864
|
<button
|
|
544
865
|
@click="sendRequest($event)"
|
|
@@ -561,15 +882,14 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
561
882
|
</span>
|
|
562
883
|
</button>
|
|
563
884
|
</PlaygroundBar>
|
|
564
|
-
|
|
885
|
+
</div>
|
|
565
886
|
|
|
566
887
|
<div class="min-h-0 flex-1 flex flex-col lg:flex-row-reverse">
|
|
567
888
|
<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">
|
|
568
889
|
<ResponseDisplay />
|
|
569
890
|
</div>
|
|
570
891
|
<div class="lg:flex-1 relative">
|
|
571
|
-
<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 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90">
|
|
572
|
-
<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 dark:via-neutral-900/70"></div>
|
|
892
|
+
<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 mask-[linear-gradient(to_bottom,transparent_0,black_16px,black_100%)] [-webkit-mask-image:linear-gradient(to_bottom,transparent_0,black_16px,black_100%)] [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 dark:[scrollbar-color:var(--color-neutral-700)_transparent] dark:[&::-webkit-scrollbar-thumb]:bg-neutral-700/70 dark:hover:[&::-webkit-scrollbar-thumb]:bg-neutral-700/90">
|
|
573
893
|
<div class="space-y-4 lg:pr-4 pt-4 pb-6">
|
|
574
894
|
{
|
|
575
895
|
Object.keys(requestFields).map(
|
|
@@ -578,97 +898,160 @@ const sectionVariantFieldNames = Object.fromEntries(
|
|
|
578
898
|
const sectionVariantData =
|
|
579
899
|
requestSectionVariants[key as keyof RequestFields];
|
|
580
900
|
const sectionVariants = sectionVariantData?.variants || [];
|
|
901
|
+
const sectionVariantCount = sectionVariants.reduce(
|
|
902
|
+
(sum, variant) => sum + variant.fields.length,
|
|
903
|
+
0,
|
|
904
|
+
);
|
|
905
|
+
const hasCommonFields = sectionFields.length > 0;
|
|
906
|
+
const hasVariants = sectionVariantCount > 0;
|
|
581
907
|
const keyLiteral = JSON.stringify(key);
|
|
582
908
|
|
|
583
|
-
if (
|
|
909
|
+
if (!hasCommonFields && !hasVariants) {
|
|
584
910
|
return null;
|
|
585
911
|
}
|
|
586
912
|
|
|
913
|
+
const noun =
|
|
914
|
+
key === "body"
|
|
915
|
+
? sectionFields.length === 1
|
|
916
|
+
? "field"
|
|
917
|
+
: "fields"
|
|
918
|
+
: sectionFields.length === 1
|
|
919
|
+
? "parameter"
|
|
920
|
+
: "parameters";
|
|
921
|
+
const variantNoun = key === "body" ? "field" : "parameter";
|
|
922
|
+
|
|
587
923
|
return (
|
|
588
|
-
<
|
|
589
|
-
<
|
|
590
|
-
{key
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
924
|
+
<section>
|
|
925
|
+
<h4 class="rd-document-heading text-base font-semibold text-neutral-900 dark:text-neutral-100">
|
|
926
|
+
{headers[key]}
|
|
927
|
+
</h4>
|
|
928
|
+
{key === "body" && formattedBodyDescription && (
|
|
929
|
+
<div
|
|
930
|
+
class="mt-2 prose-rules prose-sm! text-neutral-500 **:text-neutral-500 dark:text-neutral-400 dark:**:text-neutral-400"
|
|
931
|
+
set:html={formattedBodyDescription}
|
|
932
|
+
/>
|
|
933
|
+
)}
|
|
934
|
+
{hasCommonFields && (
|
|
935
|
+
<div x-data="{ expanded: true }" class="mt-3">
|
|
936
|
+
<div class="w-full overflow-hidden rounded-xl border-[0.5px] border-neutral-900/12 bg-neutral-50/50 shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] transition-colors duration-200 dark:border-white/6 dark:bg-(--rd-code-surface) dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)]">
|
|
937
|
+
<button
|
|
938
|
+
type="button"
|
|
939
|
+
x-on:click="expanded = !expanded"
|
|
940
|
+
x-bind:aria-expanded="expanded"
|
|
941
|
+
class="group flex w-full cursor-pointer items-center justify-between gap-3 px-4 py-2.5 text-left text-sm font-medium text-neutral-700 transition-colors duration-200 hover:bg-neutral-50 dark:text-neutral-300 dark:hover:bg-neutral-800/60"
|
|
942
|
+
>
|
|
943
|
+
<span class="inline-flex items-center gap-2">
|
|
944
|
+
<ListChevronsToggle class="size-4 shrink-0 text-neutral-400 transition duration-200 group-hover:text-neutral-600 dark:text-neutral-500 dark:group-hover:text-neutral-300" />
|
|
945
|
+
<span>
|
|
946
|
+
{sectionFields.length} {noun}
|
|
947
|
+
</span>
|
|
948
|
+
</span>
|
|
949
|
+
</button>
|
|
950
|
+
<div x-show="expanded" x-collapse x-cloak>
|
|
951
|
+
<div class="border-t border-neutral-100 p-4 dark:border-neutral-800">
|
|
952
|
+
{sectionFields.map((field) => (
|
|
953
|
+
<div class="border-b border-b-neutral-100 pb-4 mb-4 last:border-none last:pb-0 last:mb-0 dark:border-b-neutral-800">
|
|
954
|
+
<PlaygroundField field={field} requestPart={key} />
|
|
955
|
+
</div>
|
|
956
|
+
))}
|
|
957
|
+
</div>
|
|
958
|
+
</div>
|
|
599
959
|
</div>
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
960
|
+
</div>
|
|
961
|
+
)}
|
|
962
|
+
{hasVariants && (
|
|
963
|
+
<div
|
|
964
|
+
class:list={[
|
|
965
|
+
"space-y-2",
|
|
966
|
+
hasCommonFields ? "mt-3" : "mt-4",
|
|
967
|
+
]}
|
|
968
|
+
>
|
|
969
|
+
<p class="text-xs text-neutral-500 dark:text-neutral-400">
|
|
970
|
+
{sectionVariantData?.variantType === "anyOf"
|
|
971
|
+
? "One or more variants may apply."
|
|
972
|
+
: "One of these variants applies."}
|
|
973
|
+
</p>
|
|
974
|
+
{sectionVariantData?.variantType === "oneOf" && (
|
|
975
|
+
<div class="inline-flex max-w-full flex-wrap items-center gap-1 rounded-lg bg-neutral-100 p-1 dark:bg-neutral-900/60">
|
|
976
|
+
{sectionVariants.map((variant, variantIndex) => (
|
|
977
|
+
<button
|
|
978
|
+
type="button"
|
|
979
|
+
@click={`selectSectionVariant(${keyLiteral}, ${variantIndex})`}
|
|
980
|
+
:class={`{
|
|
981
|
+
'bg-white text-neutral-900 shadow-xs ring-1 ring-neutral-200 dark:bg-(--rd-code-surface) dark:text-neutral-100 dark:ring-neutral-700/70': getSelectedSectionVariant(${keyLiteral}) === ${variantIndex},
|
|
982
|
+
'text-neutral-600 hover:bg-neutral-50 hover:text-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-800/70 dark:hover:text-neutral-200': getSelectedSectionVariant(${keyLiteral}) !== ${variantIndex}
|
|
983
|
+
}`}
|
|
984
|
+
class="inline-flex h-7 cursor-pointer items-center justify-center whitespace-nowrap rounded-md px-2.5 text-[11px] font-medium transition-all duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-300 dark:focus-visible:ring-neutral-700"
|
|
985
|
+
>
|
|
986
|
+
{variant.label}
|
|
987
|
+
</button>
|
|
988
|
+
))}
|
|
989
|
+
</div>
|
|
990
|
+
)}
|
|
991
|
+
{sectionVariants.map((variant, variantIndex) => (
|
|
992
|
+
<>
|
|
993
|
+
<div
|
|
994
|
+
class="rounded-lg border border-neutral-200 bg-white p-3 dark:border-neutral-800 dark:bg-neutral-900/60"
|
|
995
|
+
x-show={
|
|
996
|
+
sectionVariantData?.variantType === "oneOf"
|
|
997
|
+
? `getSelectedSectionVariant(${keyLiteral}) === ${variantIndex}`
|
|
998
|
+
: undefined
|
|
999
|
+
}
|
|
1000
|
+
x-cloak={sectionVariantData?.variantType === "oneOf"}
|
|
1001
|
+
>
|
|
1002
|
+
<div class="mb-2 text-xs font-medium text-neutral-600 dark:text-neutral-400">
|
|
1003
|
+
{variant.label}
|
|
1004
|
+
</div>
|
|
1005
|
+
<div x-data="{ expanded: true }">
|
|
1006
|
+
<div class="w-full overflow-hidden rounded-lg border-[0.5px] border-neutral-900/8 bg-white shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-6px_rgba(0,0,0,0.08)] transition-colors duration-200 dark:border-white/6 dark:bg-(--rd-code-surface) dark:shadow-[0_-.5px_1px_rgba(255,255,255,0.15),0_5px_12px_-6px_rgba(0,0,0,0.2)]">
|
|
610
1007
|
<button
|
|
611
1008
|
type="button"
|
|
612
|
-
|
|
613
|
-
:
|
|
614
|
-
|
|
615
|
-
'text-neutral-600 hover:bg-neutral-50 hover:text-neutral-800 dark:text-neutral-400 dark:hover:bg-neutral-800/70 dark:hover:text-neutral-200': getSelectedSectionVariant(${keyLiteral}) !== ${variantIndex}
|
|
616
|
-
}`}
|
|
617
|
-
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 dark:focus-visible:ring-neutral-700"
|
|
1009
|
+
x-on:click="expanded = !expanded"
|
|
1010
|
+
x-bind:aria-expanded="expanded"
|
|
1011
|
+
class="group flex w-full cursor-pointer items-center justify-between gap-3 px-3 py-2 text-left text-xs font-medium text-neutral-700 transition-colors duration-200 hover:bg-neutral-50 dark:text-neutral-300 dark:hover:bg-neutral-800/60"
|
|
618
1012
|
>
|
|
619
|
-
|
|
1013
|
+
<span class="inline-flex items-center gap-2">
|
|
1014
|
+
<ListChevronsToggle class="size-4 shrink-0 text-neutral-400 transition duration-200 group-hover:text-neutral-600 dark:text-neutral-500 dark:group-hover:text-neutral-300" />
|
|
1015
|
+
<span>
|
|
1016
|
+
{variant.fields.length}{" "}
|
|
1017
|
+
{variant.fields.length === 1
|
|
1018
|
+
? variantNoun
|
|
1019
|
+
: `${variantNoun}s`}
|
|
1020
|
+
</span>
|
|
1021
|
+
</span>
|
|
620
1022
|
</button>
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
{sectionVariants.map((variant, variantIndex) => (
|
|
624
|
-
<div
|
|
625
|
-
x-show={`getSelectedSectionVariant(${keyLiteral}) === ${variantIndex}`}
|
|
626
|
-
x-cloak
|
|
627
|
-
>
|
|
628
|
-
<div class="rounded-md border border-neutral-200 bg-white p-2.5 dark:border-neutral-800 dark:bg-neutral-900/60">
|
|
629
|
-
<div class="mb-2 text-xs font-medium text-neutral-600 dark:text-neutral-400">
|
|
630
|
-
{variant.label}
|
|
631
|
-
</div>
|
|
632
|
-
<div class="space-y-3">
|
|
1023
|
+
<div x-show="expanded" x-collapse x-cloak>
|
|
1024
|
+
<div class="border-t border-neutral-100 px-3 py-3 dark:border-neutral-800">
|
|
633
1025
|
{variant.fields.map((field) => (
|
|
634
|
-
<div class="border-b border-b-neutral-100
|
|
1026
|
+
<div class="border-b border-b-neutral-100 pb-3 mb-3 last:border-none last:pb-0 last:mb-0 dark:border-b-neutral-800">
|
|
635
1027
|
<PlaygroundField
|
|
636
1028
|
field={field}
|
|
637
1029
|
requestPart={key}
|
|
638
|
-
defaultsEnabledExpr={
|
|
1030
|
+
defaultsEnabledExpr={
|
|
1031
|
+
sectionVariantData?.variantType ===
|
|
1032
|
+
"oneOf"
|
|
1033
|
+
? `getSelectedSectionVariant(${keyLiteral}) === ${variantIndex}`
|
|
1034
|
+
: undefined
|
|
1035
|
+
}
|
|
1036
|
+
requiredEnabledExpr={
|
|
1037
|
+
sectionVariantData?.variantType ===
|
|
1038
|
+
"oneOf"
|
|
1039
|
+
? `getSelectedSectionVariant(${keyLiteral}) === ${variantIndex}`
|
|
1040
|
+
: undefined
|
|
1041
|
+
}
|
|
639
1042
|
/>
|
|
640
1043
|
</div>
|
|
641
1044
|
))}
|
|
642
1045
|
</div>
|
|
643
1046
|
</div>
|
|
644
1047
|
</div>
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
{sectionVariants.map((variant) => (
|
|
653
|
-
<div class="rounded-md border border-neutral-200 bg-white p-2.5 dark:border-neutral-800 dark:bg-neutral-900/60">
|
|
654
|
-
<div class="mb-2 text-xs font-medium text-neutral-600 dark:text-neutral-400">
|
|
655
|
-
{variant.label}
|
|
656
|
-
</div>
|
|
657
|
-
<div class="space-y-3">
|
|
658
|
-
{variant.fields.map((field) => (
|
|
659
|
-
<div class="border-b border-b-neutral-100 dark:border-b-neutral-800 last:border-none pb-3 last:pb-0">
|
|
660
|
-
<PlaygroundField field={field} requestPart={key} />
|
|
661
|
-
</div>
|
|
662
|
-
))}
|
|
663
|
-
</div>
|
|
664
|
-
</div>
|
|
665
|
-
))}
|
|
666
|
-
</>
|
|
667
|
-
)}
|
|
668
|
-
</div>
|
|
669
|
-
)}
|
|
670
|
-
</Accordion>
|
|
671
|
-
</div>
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
</>
|
|
1051
|
+
))}
|
|
1052
|
+
</div>
|
|
1053
|
+
)}
|
|
1054
|
+
</section>
|
|
672
1055
|
);
|
|
673
1056
|
},
|
|
674
1057
|
)
|