@djangocfg/ui-tools 2.1.287 → 2.1.290

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.
Files changed (105) hide show
  1. package/README.md +14 -3
  2. package/dist/DocsLayout-IKH7BLSU.cjs +3464 -0
  3. package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
  4. package/dist/DocsLayout-JPXFUKAR.mjs +3457 -0
  5. package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
  6. package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
  7. package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
  8. package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
  9. package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
  10. package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
  11. package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
  12. package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
  13. package/dist/chunk-EFWOJPA6.mjs.map +1 -0
  14. package/dist/index.cjs +10 -10
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +34 -0
  17. package/dist/index.d.ts +34 -0
  18. package/dist/index.mjs +5 -5
  19. package/dist/index.mjs.map +1 -1
  20. package/package.json +21 -14
  21. package/src/components/markdown/MarkdownMessage.tsx +46 -0
  22. package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +93 -157
  23. package/src/tools/OpenapiViewer/README.md +114 -6
  24. package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
  25. package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +331 -53
  26. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
  27. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
  28. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
  29. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
  30. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
  31. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
  32. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
  33. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
  34. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
  35. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
  36. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
  37. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
  38. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
  39. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
  40. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
  41. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
  42. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
  43. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
  44. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
  45. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
  46. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
  47. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
  48. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
  49. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
  50. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
  51. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
  52. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
  53. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
  54. package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +40 -11
  55. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
  56. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
  57. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
  58. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
  59. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
  60. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
  61. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
  62. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
  63. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
  64. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
  65. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
  66. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
  67. package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
  68. package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
  69. package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +19 -2
  70. package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +38 -21
  71. package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +168 -50
  72. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
  73. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
  74. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
  75. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
  76. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
  77. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
  78. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
  79. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
  80. package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
  81. package/src/tools/OpenapiViewer/hooks/index.ts +3 -1
  82. package/src/tools/OpenapiViewer/hooks/useDocsUrlSync.ts +119 -0
  83. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +164 -74
  84. package/src/tools/OpenapiViewer/types.ts +46 -1
  85. package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
  86. package/src/tools/OpenapiViewer/utils/index.ts +3 -0
  87. package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
  88. package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
  89. package/src/tools/OpenapiViewer/utils/scrollParent.ts +68 -0
  90. package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
  91. package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
  92. package/src/tools/PrettyCode/index.tsx +13 -0
  93. package/src/tools/PrettyCode/lazy.tsx +5 -0
  94. package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
  95. package/dist/DocsLayout-BCVU6TTX.cjs +0 -2027
  96. package/dist/DocsLayout-BCVU6TTX.cjs.map +0 -1
  97. package/dist/DocsLayout-ERETJLLV.mjs +0 -2020
  98. package/dist/DocsLayout-ERETJLLV.mjs.map +0 -1
  99. package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
  100. package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
  101. package/dist/chunk-IULI4XII.cjs.map +0 -1
  102. package/dist/chunk-VZGQC3NG.mjs.map +0 -1
  103. package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -268
  104. package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -211
  105. package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
@@ -1,2020 +0,0 @@
1
- import { deduplicateEndpoints, dereferenceSchema, resolveBaseUrl, usePlaygroundContext, toMarkdown, toCompactJson, toRawJson, formatBytes, MarkdownMessage, endpointToMarkdown, relativePath, isValidJson, resolveAbsolute, findApiKeyById, parseRequestHeaders, PrettyCode_default, UrlBuilder, joinUrl } from './chunk-VZGQC3NG.mjs';
2
- import { JsonTree_default } from './chunk-LFWQ36LJ.mjs';
3
- import './chunk-SSUOENAZ.mjs';
4
- import { __name } from './chunk-CGILA3WO.mjs';
5
- import React6, { useRef, useMemo, useCallback, useEffect, useState } from 'react';
6
- import { Combobox, Input, Tooltip, TooltipTrigger, TooltipContent, DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, CopyButton, Switch, Textarea, SidePanel, ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, ResponsiveSheetTitle, Skeleton, TooltipProvider } from '@djangocfg/ui-core/components';
7
- import { useMediaQuery } from '@djangocfg/ui-core/hooks';
8
- import consola from 'consola';
9
- import { ChevronRight, Search, Sparkles, ChevronDown, Check, Link2, Play, Minus, Plus, RotateCcw, Send, Key, Terminal, Loader2, WifiOff, AlertCircle } from 'lucide-react';
10
- import { cn } from '@djangocfg/ui-core/lib';
11
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
12
-
13
- function exampleFromSchema(schema, depth = 0) {
14
- if (!schema || depth > 8) return null;
15
- if (schema.example !== void 0) return schema.example;
16
- if (schema.default !== void 0) return schema.default;
17
- if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
18
- switch (schema.type) {
19
- case "object": {
20
- const out = {};
21
- const props = schema.properties ?? {};
22
- for (const [k, v] of Object.entries(props)) {
23
- out[k] = exampleFromSchema(v, depth + 1);
24
- }
25
- return out;
26
- }
27
- case "array":
28
- return [exampleFromSchema(schema.items, depth + 1)];
29
- case "integer":
30
- case "number":
31
- return 0;
32
- case "boolean":
33
- return false;
34
- case "string":
35
- if (schema.format === "date-time") return (/* @__PURE__ */ new Date()).toISOString();
36
- if (schema.format === "date") return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
37
- if (schema.format === "email") return "user@example.com";
38
- if (schema.format === "uri" || schema.format === "url") return "https://example.com";
39
- if (schema.format === "uuid") return "00000000-0000-0000-0000-000000000000";
40
- return "";
41
- default:
42
- if (schema.properties) {
43
- const out = {};
44
- for (const [k, v] of Object.entries(schema.properties)) {
45
- out[k] = exampleFromSchema(v, depth + 1);
46
- }
47
- return out;
48
- }
49
- return null;
50
- }
51
- }
52
- __name(exampleFromSchema, "exampleFromSchema");
53
- var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
54
- var extractEndpoints = /* @__PURE__ */ __name((schema, baseUrl) => {
55
- const endpoints = [];
56
- if (!schema.paths) return [];
57
- for (const [path, methods] of Object.entries(schema.paths)) {
58
- for (const method of HTTP_METHODS) {
59
- const op = methods[method];
60
- if (!op) continue;
61
- const methodUpper = method.toUpperCase();
62
- const summary = (op.summary || "").trim();
63
- const description = op.description || summary || `${methodUpper} ${path}`;
64
- const category = op.tags?.[0] || "Other";
65
- const parameters = [];
66
- const allParams = [...methods.parameters || [], ...op.parameters || []];
67
- for (const param of allParams) {
68
- parameters.push({
69
- name: param.name,
70
- type: param.schema?.type || "string",
71
- required: param.required || false,
72
- description: param.description
73
- });
74
- }
75
- const responses = [];
76
- if (op.responses) {
77
- for (const [code, response] of Object.entries(op.responses)) {
78
- responses.push({
79
- code,
80
- description: response.description || `Response ${code}`
81
- });
82
- }
83
- }
84
- let requestBody;
85
- if (op.requestBody) {
86
- const content = op.requestBody.content;
87
- const mediaType = content?.["application/json"] || content?.[Object.keys(content || {})[0]];
88
- const rawSchema = mediaType?.schema;
89
- requestBody = {
90
- type: rawSchema?.type || "object",
91
- description: op.requestBody.description,
92
- schema: rawSchema,
93
- example: rawSchema ? JSON.stringify(exampleFromSchema(rawSchema), null, 2) : void 0
94
- };
95
- }
96
- const endpoint = {
97
- name: path.split("/").pop() || path,
98
- method: methodUpper,
99
- path: baseUrl ? joinUrl(baseUrl, path) : path,
100
- summary,
101
- description,
102
- category,
103
- parameters: parameters.length > 0 ? parameters : void 0,
104
- requestBody,
105
- responses: responses.length > 0 ? responses : void 0
106
- };
107
- endpoints.push(endpoint);
108
- }
109
- }
110
- return endpoints;
111
- }, "extractEndpoints");
112
- var getCategories = /* @__PURE__ */ __name((endpoints) => {
113
- const categories = /* @__PURE__ */ new Set();
114
- endpoints.forEach((endpoint) => categories.add(endpoint.category));
115
- return Array.from(categories).sort();
116
- }, "getCategories");
117
- var fetchSchema = /* @__PURE__ */ __name(async (url) => {
118
- const response = await fetch(url, {
119
- headers: {
120
- "Accept": "application/json"
121
- }
122
- });
123
- if (!response.ok) {
124
- throw new Error(`Failed to fetch schema: ${response.statusText}`);
125
- }
126
- return response.json();
127
- }, "fetchSchema");
128
- function useOpenApiSchema({
129
- schemas,
130
- defaultSchemaId,
131
- baseUrl: configBaseUrl
132
- }) {
133
- const [loading, setLoading] = useState(true);
134
- const [error, setError] = useState(null);
135
- const [currentSchemaId, setCurrentSchemaId] = useState(
136
- defaultSchemaId || schemas[0]?.id
137
- );
138
- const [loadedSchemas, setLoadedSchemas] = useState(
139
- /* @__PURE__ */ new Map()
140
- );
141
- const currentSchema = useMemo(
142
- () => schemas.find((s) => s.id === currentSchemaId) || null,
143
- [schemas, currentSchemaId]
144
- );
145
- const currentOpenApiSchema = useMemo(
146
- () => currentSchemaId ? loadedSchemas.get(currentSchemaId) : null,
147
- [loadedSchemas, currentSchemaId]
148
- );
149
- const dereferencedSchema = useMemo(
150
- () => currentOpenApiSchema ? dereferenceSchema(currentOpenApiSchema) : null,
151
- [currentOpenApiSchema]
152
- );
153
- const resolvedBaseUrl = useMemo(
154
- () => resolveBaseUrl({
155
- schemaSource: currentSchema?.baseUrl,
156
- config: configBaseUrl,
157
- fromServers: currentOpenApiSchema?.servers?.[0]?.url
158
- }),
159
- [currentSchema?.baseUrl, configBaseUrl, currentOpenApiSchema]
160
- );
161
- const endpoints = useMemo(
162
- () => dereferencedSchema ? extractEndpoints(dereferencedSchema, resolvedBaseUrl) : [],
163
- [dereferencedSchema, resolvedBaseUrl]
164
- );
165
- const categories = useMemo(() => getCategories(endpoints), [endpoints]);
166
- const schemaInfo = useMemo(() => {
167
- if (!currentOpenApiSchema?.info) return null;
168
- const { title, version, description } = currentOpenApiSchema.info;
169
- return {
170
- title,
171
- version,
172
- description,
173
- servers: currentOpenApiSchema.servers
174
- };
175
- }, [currentOpenApiSchema]);
176
- useEffect(() => {
177
- if (!currentSchema) return;
178
- if (loadedSchemas.has(currentSchema.id)) {
179
- setLoading(false);
180
- return;
181
- }
182
- setLoading(true);
183
- setError(null);
184
- fetchSchema(currentSchema.url).then((schema) => {
185
- setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
186
- consola.success(`Schema loaded: ${currentSchema.name}`);
187
- setLoading(false);
188
- }).catch((err) => {
189
- consola.error(`Error loading schema from ${currentSchema.url}:`, err);
190
- setError(err instanceof Error ? err.message : "Failed to load schema");
191
- setLoading(false);
192
- });
193
- }, [currentSchema, loadedSchemas]);
194
- const setCurrentSchema = useCallback((schemaId) => {
195
- setCurrentSchemaId(schemaId);
196
- }, []);
197
- const refresh = useCallback(() => {
198
- if (!currentSchema) return;
199
- setLoading(true);
200
- setError(null);
201
- setLoadedSchemas((prev) => {
202
- const next = new Map(prev);
203
- next.delete(currentSchema.id);
204
- return next;
205
- });
206
- fetchSchema(currentSchema.url).then((schema) => {
207
- setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
208
- consola.success(`Schema refreshed: ${currentSchema.name}`);
209
- setLoading(false);
210
- }).catch((err) => {
211
- consola.error(`Error refreshing schema from ${currentSchema.url}:`, err);
212
- setError(err instanceof Error ? err.message : "Failed to refresh schema");
213
- setLoading(false);
214
- });
215
- }, [currentSchema]);
216
- return {
217
- loading,
218
- error,
219
- endpoints,
220
- categories,
221
- schemas,
222
- currentSchema,
223
- schemaInfo,
224
- rawSchema: currentOpenApiSchema ?? null,
225
- // Consumers expect ``undefined`` when no base URL was resolved (for
226
- // conditional ``{ baseUrl?: … }`` plumbing). Turn the empty-string
227
- // convention from the resolver into undefined at the API boundary.
228
- resolvedBaseUrl: resolvedBaseUrl || void 0,
229
- setCurrentSchema,
230
- refresh
231
- };
232
- }
233
- __name(useOpenApiSchema, "useOpenApiSchema");
234
- var EMPTY_DRAFT = { parameters: {}, requestBody: "" };
235
- function storageKey(schemaId, ep) {
236
- if (!schemaId || !ep) return null;
237
- return `openapi-playground:draft:${schemaId}:${ep.method}:${ep.path}`;
238
- }
239
- __name(storageKey, "storageKey");
240
- function readDraft(key) {
241
- if (!key || typeof window === "undefined") return EMPTY_DRAFT;
242
- try {
243
- const raw = window.localStorage.getItem(key);
244
- if (!raw) return EMPTY_DRAFT;
245
- const parsed = JSON.parse(raw);
246
- return {
247
- parameters: parsed?.parameters ?? {},
248
- requestBody: typeof parsed?.requestBody === "string" ? parsed.requestBody : ""
249
- };
250
- } catch {
251
- return EMPTY_DRAFT;
252
- }
253
- }
254
- __name(readDraft, "readDraft");
255
- function writeDraft(key, value) {
256
- if (!key || typeof window === "undefined") return;
257
- try {
258
- if (Object.keys(value.parameters).length === 0 && !value.requestBody) {
259
- window.localStorage.removeItem(key);
260
- return;
261
- }
262
- window.localStorage.setItem(key, JSON.stringify(value));
263
- } catch {
264
- }
265
- }
266
- __name(writeDraft, "writeDraft");
267
- function useEndpointDraft(schemaId, endpoint) {
268
- const key = storageKey(schemaId, endpoint);
269
- const [draft, setDraftSnapshot] = useState(() => readDraft(key));
270
- const loadedKeyRef = useRef(key);
271
- useEffect(() => {
272
- if (loadedKeyRef.current === key) return;
273
- loadedKeyRef.current = key;
274
- setDraftSnapshot(readDraft(key));
275
- }, [key]);
276
- const keyRef = useRef(key);
277
- useEffect(() => {
278
- keyRef.current = key;
279
- }, [key]);
280
- const latestRef = useRef(draft);
281
- useEffect(() => {
282
- latestRef.current = draft;
283
- }, [draft]);
284
- const setParameters = useCallback((params) => {
285
- const next = {
286
- parameters: params,
287
- requestBody: latestRef.current.requestBody
288
- };
289
- latestRef.current = next;
290
- writeDraft(keyRef.current, next);
291
- }, []);
292
- const setRequestBody = useCallback((body) => {
293
- const next = {
294
- parameters: latestRef.current.parameters,
295
- requestBody: body
296
- };
297
- latestRef.current = next;
298
- writeDraft(keyRef.current, next);
299
- }, []);
300
- const reset = useCallback(() => {
301
- latestRef.current = EMPTY_DRAFT;
302
- if (keyRef.current && typeof window !== "undefined") {
303
- try {
304
- window.localStorage.removeItem(keyRef.current);
305
- } catch {
306
- }
307
- }
308
- setDraftSnapshot(EMPTY_DRAFT);
309
- }, []);
310
- return { draft, setParameters, setRequestBody, reset };
311
- }
312
- __name(useEndpointDraft, "useEndpointDraft");
313
-
314
- // src/tools/OpenapiViewer/components/shared/EndpointDraftSync.tsx
315
- function EndpointDraftSync({ schemaId }) {
316
- const { state, setParameters, setRequestBody, setActiveSchemaId } = usePlaygroundContext();
317
- const ep = state.selectedEndpoint;
318
- useEffect(() => {
319
- setActiveSchemaId(schemaId);
320
- }, [schemaId, setActiveSchemaId]);
321
- const { draft, setParameters: persistParams, setRequestBody: persistBody } = useEndpointDraft(schemaId, ep);
322
- const lastLoadedKeyRef = useRef(null);
323
- const lastPersistedParamsRef = useRef("");
324
- const lastPersistedBodyRef = useRef("");
325
- const currentKey = ep ? `${ep.method}|${ep.path}` : null;
326
- useEffect(() => {
327
- if (!ep || !currentKey) {
328
- lastLoadedKeyRef.current = null;
329
- return;
330
- }
331
- if (lastLoadedKeyRef.current === currentKey) return;
332
- lastLoadedKeyRef.current = currentKey;
333
- const hasStoredParams = draft.parameters && Object.keys(draft.parameters).length > 0;
334
- const hasStoredBody = typeof draft.requestBody === "string" && draft.requestBody !== "";
335
- if (hasStoredParams) {
336
- setParameters(draft.parameters);
337
- lastPersistedParamsRef.current = JSON.stringify(draft.parameters);
338
- } else {
339
- lastPersistedParamsRef.current = JSON.stringify(state.parameters);
340
- }
341
- if (hasStoredBody) {
342
- setRequestBody(draft.requestBody);
343
- lastPersistedBodyRef.current = draft.requestBody;
344
- } else {
345
- lastPersistedBodyRef.current = state.requestBody;
346
- }
347
- }, [currentKey]);
348
- useEffect(() => {
349
- if (!ep || lastLoadedKeyRef.current !== currentKey) return;
350
- const serialised = JSON.stringify(state.parameters);
351
- if (serialised === lastPersistedParamsRef.current) return;
352
- lastPersistedParamsRef.current = serialised;
353
- persistParams(state.parameters);
354
- }, [state.parameters, ep, currentKey, persistParams]);
355
- useEffect(() => {
356
- if (!ep || lastLoadedKeyRef.current !== currentKey) return;
357
- if (state.requestBody === lastPersistedBodyRef.current) return;
358
- lastPersistedBodyRef.current = state.requestBody;
359
- persistBody(state.requestBody);
360
- }, [state.requestBody, ep, currentKey, persistBody]);
361
- return null;
362
- }
363
- __name(EndpointDraftSync, "EndpointDraftSync");
364
- var METHOD_STYLES = {
365
- GET: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25",
366
- POST: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25",
367
- PUT: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25",
368
- PATCH: "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/25",
369
- DELETE: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25"
370
- };
371
- var METHOD_FALLBACK = "bg-muted text-muted-foreground border-border";
372
- function getMethodStyle(method) {
373
- return METHOD_STYLES[method.toUpperCase()] ?? METHOD_FALLBACK;
374
- }
375
- __name(getMethodStyle, "getMethodStyle");
376
- function getStatusStyle(status) {
377
- if (status >= 500) return "bg-red-500/10 text-red-500 dark:text-red-400 border-red-500/25";
378
- if (status >= 400) return "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25";
379
- if (status >= 300) return "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25";
380
- return "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
381
- }
382
- __name(getStatusStyle, "getStatusStyle");
383
- function MethodBadge({ method }) {
384
- return /* @__PURE__ */ jsx("span", { className: cn(
385
- "inline-flex shrink-0 items-center rounded border px-1.5 py-px",
386
- "font-mono text-[10px] font-bold uppercase tracking-wider leading-none",
387
- getMethodStyle(method)
388
- ), children: method });
389
- }
390
- __name(MethodBadge, "MethodBadge");
391
- function StatusBadge({ status }) {
392
- return /* @__PURE__ */ jsx("span", { className: cn(
393
- "inline-flex items-center rounded border px-1.5 py-px",
394
- "font-mono text-[11px] font-bold leading-none",
395
- getStatusStyle(status)
396
- ), children: status });
397
- }
398
- __name(StatusBadge, "StatusBadge");
399
- function SectionLabel({ children }) {
400
- return /* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 select-none", children });
401
- }
402
- __name(SectionLabel, "SectionLabel");
403
- function Panel({ children, className }) {
404
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col min-h-0 overflow-hidden", className), children });
405
- }
406
- __name(Panel, "Panel");
407
- function ScrollArea({ children, className }) {
408
- return /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-y-auto min-h-0", className), children });
409
- }
410
- __name(ScrollArea, "ScrollArea");
411
- function EmptyState({
412
- icon: Icon,
413
- text,
414
- className
415
- }) {
416
- return /* @__PURE__ */ jsxs(
417
- "div",
418
- {
419
- className: cn(
420
- "flex flex-col items-center justify-center h-full gap-3 px-6 text-center",
421
- className
422
- ),
423
- children: [
424
- /* @__PURE__ */ jsx(Icon, { className: "h-7 w-7 text-muted-foreground/25" }),
425
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: text })
426
- ]
427
- }
428
- );
429
- }
430
- __name(EmptyState, "EmptyState");
431
- function CollapsibleSection({
432
- label,
433
- action,
434
- children,
435
- defaultOpen = false
436
- }) {
437
- const [open, setOpen] = React6.useState(defaultOpen);
438
- return /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
439
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
440
- /* @__PURE__ */ jsxs(
441
- "button",
442
- {
443
- type: "button",
444
- onClick: () => setOpen((v) => !v),
445
- className: "flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 hover:text-muted-foreground transition-colors py-1",
446
- children: [
447
- /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3 w-3 transition-transform", open && "rotate-90") }),
448
- label
449
- ]
450
- }
451
- ),
452
- action && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: action })
453
- ] }),
454
- open && /* @__PURE__ */ jsx("div", { children })
455
- ] });
456
- }
457
- __name(CollapsibleSection, "CollapsibleSection");
458
-
459
- // src/tools/OpenapiViewer/components/DocsLayout/anchor.ts
460
- function endpointAnchor(ep) {
461
- const slug = ep.path.replace(/^https?:\/\/[^/]+/, "").replace(/[{}]/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
462
- return `ep-${ep.method.toLowerCase()}-${slug}`;
463
- }
464
- __name(endpointAnchor, "endpointAnchor");
465
-
466
- // src/tools/OpenapiViewer/components/DocsLayout/sidebarLabel.ts
467
- function longestCommonPrefix(paths) {
468
- if (paths.length === 0) return "";
469
- if (paths.length === 1) return "";
470
- const segments = paths.map((p) => p.split("/"));
471
- const minLen = Math.min(...segments.map((s) => s.length));
472
- const shared = [];
473
- for (let i = 0; i < minLen; i++) {
474
- const first = segments[0][i];
475
- if (segments.every((s) => s[i] === first)) {
476
- shared.push(first);
477
- } else {
478
- break;
479
- }
480
- }
481
- const joined = shared.join("/");
482
- return joined;
483
- }
484
- __name(longestCommonPrefix, "longestCommonPrefix");
485
- function sidebarLabel(ep, groupCommonPrefix) {
486
- if (ep.summary) return ep.summary;
487
- if (groupCommonPrefix && ep.path.startsWith(groupCommonPrefix)) {
488
- const tail = ep.path.slice(groupCommonPrefix.length) || "/";
489
- return tail;
490
- }
491
- return relativePath2(ep.path);
492
- }
493
- __name(sidebarLabel, "sidebarLabel");
494
- function relativePath2(full) {
495
- try {
496
- return new URL(full).pathname;
497
- } catch {
498
- return full;
499
- }
500
- }
501
- __name(relativePath2, "relativePath");
502
- function sidebarTooltip(ep) {
503
- return `${ep.method} ${relativePath2(ep.path)}`;
504
- }
505
- __name(sidebarTooltip, "sidebarTooltip");
506
- var METHOD_ORDER = {
507
- GET: 0,
508
- POST: 1,
509
- PUT: 2,
510
- PATCH: 3,
511
- DELETE: 4
512
- };
513
- function groupEndpoints(list) {
514
- const map = /* @__PURE__ */ new Map();
515
- for (const ep of list) {
516
- const arr = map.get(ep.category) ?? [];
517
- arr.push(ep);
518
- map.set(ep.category, arr);
519
- }
520
- const groups = Array.from(map.entries()).map(([category, endpoints]) => ({
521
- category,
522
- endpoints: [...endpoints].sort((a, b) => {
523
- const byPath = a.path.localeCompare(b.path);
524
- if (byPath !== 0) return byPath;
525
- return (METHOD_ORDER[a.method] ?? 99) - (METHOD_ORDER[b.method] ?? 99);
526
- }),
527
- commonPrefix: longestCommonPrefix(endpoints.map((e) => e.path))
528
- }));
529
- groups.sort((a, b) => {
530
- if (a.category === "Other") return 1;
531
- if (b.category === "Other") return -1;
532
- return a.category.localeCompare(b.category);
533
- });
534
- return groups;
535
- }
536
- __name(groupEndpoints, "groupEndpoints");
537
- function DocsSidebar({
538
- info,
539
- endpoints,
540
- schemas,
541
- currentSchemaId,
542
- onSchemaChange,
543
- activeEndpointId,
544
- selectedVersion,
545
- onNavigate
546
- }) {
547
- const [search, setSearch] = useState("");
548
- const [debounced, setDebounced] = useState("");
549
- useEffect(() => {
550
- const id = setTimeout(() => setDebounced(search), 120);
551
- return () => clearTimeout(id);
552
- }, [search]);
553
- const filteredGroups = useMemo(() => {
554
- let list = deduplicateEndpoints(endpoints, selectedVersion);
555
- if (debounced) {
556
- const q = debounced.toLowerCase();
557
- list = list.filter(
558
- (e) => e.summary.toLowerCase().includes(q) || e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
559
- );
560
- }
561
- return groupEndpoints(list);
562
- }, [endpoints, debounced, selectedVersion]);
563
- const schemaOptions = useMemo(
564
- () => schemas.map((s) => ({ value: s.id, label: s.name })),
565
- [schemas]
566
- );
567
- const hasMultipleSchemas = schemas.length > 1;
568
- const apiTitle = info?.title ?? "API Reference";
569
- return /* @__PURE__ */ jsxs("aside", { className: "flex flex-col min-h-0 border-r bg-muted/10", children: [
570
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 h-12 flex items-center gap-2", children: [
571
- /* @__PURE__ */ jsx("span", { className: "text-[13px] font-semibold text-foreground truncate", children: apiTitle }),
572
- info?.version && /* @__PURE__ */ jsxs("span", { className: "font-mono text-[10px] text-muted-foreground/70 shrink-0", children: [
573
- "v",
574
- info.version
575
- ] })
576
- ] }),
577
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 py-3 space-y-2", children: [
578
- hasMultipleSchemas && /* @__PURE__ */ jsx(
579
- Combobox,
580
- {
581
- options: schemaOptions,
582
- value: currentSchemaId ?? "",
583
- onValueChange: (id) => id && onSchemaChange(id),
584
- placeholder: "Select API",
585
- searchPlaceholder: "Search APIs\u2026",
586
- emptyText: "No APIs found",
587
- className: "w-full h-8 text-xs"
588
- }
589
- ),
590
- /* @__PURE__ */ jsxs("div", { className: "relative", children: [
591
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground/50 pointer-events-none" }),
592
- /* @__PURE__ */ jsx(
593
- Input,
594
- {
595
- placeholder: "Search endpoints\u2026",
596
- value: search,
597
- onChange: (e) => setSearch(e.target.value),
598
- className: "pl-8 h-8 text-xs"
599
- }
600
- )
601
- ] })
602
- ] }),
603
- /* @__PURE__ */ jsx(ScrollArea, { children: filteredGroups.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: debounced ? `No endpoints match "${debounced}"` : "No endpoints in this schema" }) : /* @__PURE__ */ jsx("nav", { className: "py-2", children: filteredGroups.map((group) => /* @__PURE__ */ jsxs("div", { className: "mb-4 last:mb-2", children: [
604
- /* @__PURE__ */ jsx("div", { className: "px-4 py-1.5 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/50 select-none", children: group.category }),
605
- /* @__PURE__ */ jsx("div", { children: group.endpoints.map((ep) => {
606
- const anchor = endpointAnchor(ep);
607
- const isActive = activeEndpointId === anchor;
608
- const label = sidebarLabel(ep, group.commonPrefix);
609
- const tooltip = sidebarTooltip(ep);
610
- const useMono = !ep.summary;
611
- return /* @__PURE__ */ jsxs(Tooltip, { delayDuration: 350, children: [
612
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
613
- "button",
614
- {
615
- onClick: () => onNavigate(anchor),
616
- "aria-current": isActive ? "location" : void 0,
617
- className: cn(
618
- "relative group w-full text-left flex items-center gap-2 pl-4 pr-3 py-1.5 transition-colors",
619
- isActive ? "bg-primary/10 text-foreground" : "hover:bg-muted/40 text-foreground/75 hover:text-foreground"
620
- ),
621
- children: [
622
- isActive && /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded-r bg-primary" }),
623
- /* @__PURE__ */ jsx(MethodBadge, { method: ep.method }),
624
- /* @__PURE__ */ jsx(
625
- "span",
626
- {
627
- className: cn(
628
- "truncate leading-tight flex-1 min-w-0",
629
- useMono ? "font-mono text-[11px]" : "text-[12px]",
630
- isActive && "text-foreground font-medium"
631
- ),
632
- children: label
633
- }
634
- )
635
- ]
636
- }
637
- ) }),
638
- /* @__PURE__ */ jsx(TooltipContent, { side: "right", align: "center", className: "font-mono text-[11px]", children: tooltip })
639
- ] }, `${ep.method}-${ep.path}`);
640
- }) })
641
- ] }, group.category)) }) })
642
- ] });
643
- }
644
- __name(DocsSidebar, "DocsSidebar");
645
- var FLAVOUR_LABELS = {
646
- markdown: {
647
- title: "Markdown for LLM",
648
- hint: "Endpoints + params as prose. Smallest."
649
- },
650
- compact: {
651
- title: "Compact JSON",
652
- hint: "Dereferenced, no examples, minified."
653
- },
654
- raw: {
655
- title: "Raw JSON",
656
- hint: "Full OpenAPI document with $refs."
657
- }
658
- };
659
- function SchemaCopyMenu({ schema, endpoints, baseUrl }) {
660
- const [sizeCache, setSizeCache] = useState({});
661
- const [justCopied, setJustCopied] = useState(null);
662
- const [open, setOpen] = useState(false);
663
- const isReady = schema !== null && endpoints.length > 0;
664
- const build = useCallback(
665
- (flavour) => {
666
- if (!schema) return "";
667
- if (flavour === "markdown") return toMarkdown(schema, endpoints, baseUrl);
668
- if (flavour === "compact") return toCompactJson(schema, baseUrl);
669
- return toRawJson(schema, baseUrl);
670
- },
671
- [schema, endpoints, baseUrl]
672
- );
673
- const handleCopy = useCallback(
674
- async (flavour) => {
675
- if (!isReady) return;
676
- const text = build(flavour);
677
- try {
678
- await navigator.clipboard.writeText(text);
679
- setSizeCache((prev) => ({ ...prev, [flavour]: formatBytes(text) }));
680
- setJustCopied(flavour);
681
- setTimeout(() => setJustCopied(null), 1500);
682
- setOpen(false);
683
- } catch {
684
- }
685
- },
686
- [build, isReady]
687
- );
688
- const flavours = useMemo(() => ["markdown", "compact", "raw"], []);
689
- return /* @__PURE__ */ jsxs(DropdownMenu, { open, onOpenChange: setOpen, children: [
690
- /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: !isReady, children: [
691
- /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
692
- "Copy for AI",
693
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 opacity-60" })
694
- ] }) }),
695
- /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-72", children: [
696
- /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-[10px] uppercase tracking-wider text-muted-foreground/70", children: "Copy schema" }),
697
- /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
698
- flavours.map((f) => {
699
- const label = FLAVOUR_LABELS[f];
700
- const size = sizeCache[f];
701
- const isDone = justCopied === f;
702
- return /* @__PURE__ */ jsxs(
703
- DropdownMenuItem,
704
- {
705
- onClick: (e) => {
706
- e.preventDefault();
707
- void handleCopy(f);
708
- },
709
- className: "flex flex-col items-start gap-0.5 py-2 cursor-pointer",
710
- children: [
711
- /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
712
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium flex-1", children: label.title }),
713
- isDone ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-emerald-500", children: [
714
- /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
715
- " Copied"
716
- ] }) : size ? /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground/70 tabular-nums", children: size }) : null
717
- ] }),
718
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/70 leading-snug", children: label.hint })
719
- ]
720
- },
721
- f
722
- );
723
- })
724
- ] })
725
- ] });
726
- }
727
- __name(SchemaCopyMenu, "SchemaCopyMenu");
728
- function ApiIntroSection({ info, schema, endpoints, resolvedBaseUrl }) {
729
- return /* @__PURE__ */ jsxs("section", { className: "pb-10 mb-10 border-b", children: [
730
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [
731
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap min-w-0", children: [
732
- /* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold tracking-tight text-foreground leading-tight", children: info.title }),
733
- /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded-full bg-muted px-2 py-0.5 font-mono text-[11px] text-muted-foreground", children: [
734
- "v",
735
- info.version
736
- ] })
737
- ] }),
738
- /* @__PURE__ */ jsx(
739
- SchemaCopyMenu,
740
- {
741
- schema,
742
- endpoints,
743
- baseUrl: resolvedBaseUrl
744
- }
745
- )
746
- ] }),
747
- info.description && /* @__PURE__ */ jsx("div", { className: "mt-4 text-muted-foreground", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: info.description }) }),
748
- info.servers && info.servers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-2", children: [
749
- /* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60", children: "Base URL" }),
750
- /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: info.servers.map((s, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
751
- /* @__PURE__ */ jsx("code", { className: "font-mono text-xs px-2 py-1 rounded bg-muted border", children: s.url }),
752
- s.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: s.description })
753
- ] }, `${s.url}-${i}`)) })
754
- ] })
755
- ] });
756
- }
757
- __name(ApiIntroSection, "ApiIntroSection");
758
-
759
- // src/tools/OpenapiViewer/components/DocsLayout/schemaFields.ts
760
- var MAX_DEPTH = 2;
761
- function describeType(node) {
762
- if (!node.type && node.properties) return "object";
763
- const base = node.type || "any";
764
- if (base === "array") {
765
- const itemType = node.items ? describeType(node.items) : "any";
766
- return `array<${itemType}>`;
767
- }
768
- if (Array.isArray(node.enum) && node.enum.length > 0) {
769
- return `${base} enum`;
770
- }
771
- if (node.format) return `${base} (${node.format})`;
772
- return base;
773
- }
774
- __name(describeType, "describeType");
775
- function schemaToFields(schema, prefix = "", depth = 0) {
776
- if (!schema || depth > MAX_DEPTH) return [];
777
- if (schema.type === "array") {
778
- if (!schema.items) {
779
- return [{ name: prefix || "[]", type: "array", required: false }];
780
- }
781
- const inner = schemaToFields(schema.items, prefix ? `${prefix}[]` : "[]", depth);
782
- if (inner.length === 0) {
783
- return [
784
- {
785
- name: prefix || "[]",
786
- type: describeType(schema),
787
- required: false,
788
- description: schema.description
789
- }
790
- ];
791
- }
792
- return inner;
793
- }
794
- if (schema.type !== "object" && !schema.properties) {
795
- return [
796
- {
797
- name: prefix || "(body)",
798
- type: describeType(schema),
799
- required: false,
800
- description: schema.description
801
- }
802
- ];
803
- }
804
- const required = new Set(schema.required ?? []);
805
- const rows = [];
806
- const props = schema.properties ?? {};
807
- for (const [key, node] of Object.entries(props)) {
808
- const fullName = prefix ? `${prefix}.${key}` : key;
809
- const isRequired = required.has(key);
810
- const isNestedExpandable = node.type === "object" && node.properties || node.type === "array" && node.items;
811
- if (isNestedExpandable && depth < MAX_DEPTH) {
812
- rows.push({
813
- name: fullName,
814
- type: describeType(node),
815
- required: isRequired,
816
- description: node.description
817
- });
818
- rows.push(...schemaToFields(node, fullName, depth + 1));
819
- } else {
820
- rows.push({
821
- name: fullName,
822
- type: describeType(node),
823
- required: isRequired,
824
- description: node.description
825
- });
826
- }
827
- }
828
- return rows;
829
- }
830
- __name(schemaToFields, "schemaToFields");
831
- function EndpointDoc({ endpoint, isLoadedInPlayground, onTryIt }) {
832
- const anchor = endpointAnchor(endpoint);
833
- const pathParams = endpoint.parameters?.filter((p) => endpoint.path.includes(`{${p.name}}`)) ?? [];
834
- const queryParams = endpoint.parameters?.filter((p) => !endpoint.path.includes(`{${p.name}}`)) ?? [];
835
- const [copied, setCopied] = useState(false);
836
- const copyAnchor = useCallback(() => {
837
- if (typeof window === "undefined") return;
838
- const url = `${window.location.origin}${window.location.pathname}#${anchor}`;
839
- void navigator.clipboard?.writeText(url).then(() => {
840
- setCopied(true);
841
- setTimeout(() => setCopied(false), 1200);
842
- });
843
- }, [anchor]);
844
- const endpointMd = useMemo(() => endpointToMarkdown(endpoint), [endpoint]);
845
- return /* @__PURE__ */ jsxs(
846
- "section",
847
- {
848
- id: anchor,
849
- "data-endpoint-anchor": anchor,
850
- className: "scroll-mt-24 py-10 first:pt-0",
851
- children: [
852
- /* @__PURE__ */ jsxs("header", { className: "space-y-4", children: [
853
- /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 flex-wrap", children: [
854
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2.5 min-w-0 flex-1 group/header", children: [
855
- /* @__PURE__ */ jsx("span", { className: "shrink-0 translate-y-[2px]", children: /* @__PURE__ */ jsx(MethodBadge, { method: endpoint.method }) }),
856
- /* @__PURE__ */ jsx(
857
- "code",
858
- {
859
- className: "font-mono text-sm md:text-[15px] font-medium text-foreground leading-snug min-w-0",
860
- style: { overflowWrap: "anywhere", wordBreak: "break-word" },
861
- children: relativePath(endpoint.path)
862
- }
863
- ),
864
- /* @__PURE__ */ jsx(
865
- "button",
866
- {
867
- type: "button",
868
- onClick: copyAnchor,
869
- title: "Copy link to this section",
870
- className: cn(
871
- "shrink-0 p-1 rounded text-muted-foreground/40 hover:text-foreground hover:bg-muted transition-all",
872
- "opacity-0 group-hover/header:opacity-100",
873
- copied && "opacity-100 text-emerald-500"
874
- ),
875
- children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5" })
876
- }
877
- ),
878
- /* @__PURE__ */ jsx(
879
- CopyButton,
880
- {
881
- value: endpointMd,
882
- title: "Copy endpoint as markdown (for AI)",
883
- variant: "ghost",
884
- size: "icon",
885
- iconClassName: "h-3.5 w-3.5",
886
- className: cn(
887
- "shrink-0 h-6 w-6 text-muted-foreground/40 hover:text-foreground",
888
- "opacity-0 group-hover/header:opacity-100 focus-visible:opacity-100"
889
- )
890
- }
891
- )
892
- ] }),
893
- /* @__PURE__ */ jsxs(
894
- Button,
895
- {
896
- size: "sm",
897
- variant: isLoadedInPlayground ? "secondary" : "default",
898
- onClick: onTryIt,
899
- className: "shrink-0 h-8 text-xs gap-1.5 lg:flex hidden",
900
- children: [
901
- /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
902
- isLoadedInPlayground ? "Loaded" : "Try it"
903
- ]
904
- }
905
- )
906
- ] }),
907
- endpoint.description && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: endpoint.description }) }),
908
- /* @__PURE__ */ jsxs(
909
- Button,
910
- {
911
- size: "sm",
912
- variant: isLoadedInPlayground ? "secondary" : "default",
913
- onClick: onTryIt,
914
- className: "lg:hidden h-8 text-xs gap-1.5",
915
- children: [
916
- /* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
917
- isLoadedInPlayground ? "Loaded in playground" : "Try it"
918
- ]
919
- }
920
- )
921
- ] }),
922
- (pathParams.length > 0 || queryParams.length > 0 || endpoint.requestBody || endpoint.responses?.length) && /* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-8", children: [
923
- pathParams.length > 0 && /* @__PURE__ */ jsx(ParamTable, { title: "Path parameters", params: pathParams }),
924
- queryParams.length > 0 && /* @__PURE__ */ jsx(ParamTable, { title: "Query parameters", params: queryParams }),
925
- endpoint.requestBody && /* @__PURE__ */ jsx(RequestBodySection, { body: endpoint.requestBody }),
926
- endpoint.responses && endpoint.responses.length > 0 && /* @__PURE__ */ jsx(Subsection, { title: "Responses", children: /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: endpoint.responses.map((r) => /* @__PURE__ */ jsxs(
927
- "div",
928
- {
929
- className: "grid grid-cols-[72px_minmax(0,1fr)] items-center gap-3 px-3 py-2.5 bg-background",
930
- children: [
931
- /* @__PURE__ */ jsx("div", { className: "flex justify-start", children: /* @__PURE__ */ jsx(StatusTag, { code: r.code }) }),
932
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground leading-relaxed break-words", children: r.description })
933
- ]
934
- },
935
- r.code
936
- )) }) })
937
- ] })
938
- ]
939
- }
940
- );
941
- }
942
- __name(EndpointDoc, "EndpointDoc");
943
- function RequestBodySection({ body }) {
944
- const fields = useMemo(() => schemaToFields(body.schema), [body.schema]);
945
- const typeLabel = body.schema ? body.type === "array" ? `array<${body.schema.items?.type ?? "object"}>` : body.type : body.type;
946
- return /* @__PURE__ */ jsx(Subsection, { title: "Request body", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
947
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
948
- /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/80", children: typeLabel }),
949
- body.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: body.description })
950
- ] }),
951
- fields.length > 0 && /* @__PURE__ */ jsx(FieldsTable, { fields })
952
- ] }) });
953
- }
954
- __name(RequestBodySection, "RequestBodySection");
955
- function FieldsTable({ fields }) {
956
- return /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: fields.map((f) => /* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 bg-background space-y-1", children: [
957
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
958
- /* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: f.name }),
959
- f.required && /* @__PURE__ */ jsx(
960
- "span",
961
- {
962
- title: "Required",
963
- className: "text-[9px] text-destructive font-bold leading-none",
964
- children: "*"
965
- }
966
- ),
967
- /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: f.type })
968
- ] }),
969
- f.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: f.description })
970
- ] }, f.name)) });
971
- }
972
- __name(FieldsTable, "FieldsTable");
973
- function Subsection({ title, children }) {
974
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
975
- /* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-[0.12em] text-muted-foreground/70", children: title }),
976
- children
977
- ] });
978
- }
979
- __name(Subsection, "Subsection");
980
- function ParamTable({
981
- title,
982
- params
983
- }) {
984
- return /* @__PURE__ */ jsx(Subsection, { title, children: /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: params.map((p) => /* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 bg-background space-y-1", children: [
985
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
986
- /* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: p.name }),
987
- p.required && /* @__PURE__ */ jsx(
988
- "span",
989
- {
990
- title: "Required",
991
- className: "text-[9px] text-destructive font-bold leading-none",
992
- children: "*"
993
- }
994
- ),
995
- /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: p.type })
996
- ] }),
997
- p.description ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: p.description }) : null
998
- ] }, p.name)) }) });
999
- }
1000
- __name(ParamTable, "ParamTable");
1001
- function StatusTag({ code }) {
1002
- const numeric = Number.parseInt(code, 10);
1003
- const cls = !Number.isFinite(numeric) ? "bg-muted text-muted-foreground border-border" : numeric >= 500 ? "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25" : numeric >= 400 ? "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25" : numeric >= 300 ? "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25" : "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
1004
- return /* @__PURE__ */ jsx("span", { className: cn(
1005
- "inline-flex items-center justify-center rounded border px-2 py-0.5 font-mono text-[11px] font-bold leading-none shrink-0 tabular-nums",
1006
- cls
1007
- ), children: code });
1008
- }
1009
- __name(StatusTag, "StatusTag");
1010
- var DocsView = React6.forwardRef(/* @__PURE__ */ __name(function DocsView2({
1011
- info,
1012
- rawSchema,
1013
- resolvedBaseUrl,
1014
- endpoints,
1015
- selectedVersion,
1016
- loadedEndpoint,
1017
- onTryEndpoint,
1018
- onActiveChange
1019
- }, ref) {
1020
- const scrollRef = useRef(null);
1021
- const visibleEndpoints = useMemo(
1022
- () => deduplicateEndpoints(endpoints, selectedVersion),
1023
- [endpoints, selectedVersion]
1024
- );
1025
- const scrollToAnchor = useCallback((anchor) => {
1026
- const root = scrollRef.current;
1027
- if (!root) return;
1028
- const el = root.querySelector(`[data-endpoint-anchor="${anchor}"]`);
1029
- if (!el) return;
1030
- el.scrollIntoView({ behavior: "smooth", block: "start" });
1031
- }, []);
1032
- React6.useImperativeHandle(ref, () => ({ scrollToAnchor }), [scrollToAnchor]);
1033
- useEffect(() => {
1034
- const root = scrollRef.current;
1035
- if (!root) return;
1036
- let rafId = 0;
1037
- let lastActive = null;
1038
- const compute = /* @__PURE__ */ __name(() => {
1039
- rafId = 0;
1040
- const sections = root.querySelectorAll("[data-endpoint-anchor]");
1041
- if (sections.length === 0) return;
1042
- const rootTop = root.getBoundingClientRect().top;
1043
- const threshold = rootTop + root.clientHeight * 0.25;
1044
- let active = null;
1045
- for (const s of Array.from(sections)) {
1046
- const top = s.getBoundingClientRect().top;
1047
- if (top <= threshold) {
1048
- active = s.dataset.endpointAnchor ?? null;
1049
- } else {
1050
- break;
1051
- }
1052
- }
1053
- if (active !== lastActive) {
1054
- lastActive = active;
1055
- onActiveChange(active);
1056
- }
1057
- }, "compute");
1058
- const onScroll = /* @__PURE__ */ __name(() => {
1059
- if (rafId) return;
1060
- rafId = requestAnimationFrame(compute);
1061
- }, "onScroll");
1062
- compute();
1063
- root.addEventListener("scroll", onScroll, { passive: true });
1064
- return () => {
1065
- root.removeEventListener("scroll", onScroll);
1066
- if (rafId) cancelAnimationFrame(rafId);
1067
- };
1068
- }, [visibleEndpoints, onActiveChange]);
1069
- return /* @__PURE__ */ jsx("div", { ref: scrollRef, className: "flex-1 overflow-y-auto min-h-0", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-[860px] px-6 md:px-10 lg:px-14 py-12", children: [
1070
- info && /* @__PURE__ */ jsx(
1071
- ApiIntroSection,
1072
- {
1073
- info,
1074
- schema: rawSchema,
1075
- endpoints: visibleEndpoints,
1076
- resolvedBaseUrl
1077
- }
1078
- ),
1079
- visibleEndpoints.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-16 text-center text-sm text-muted-foreground", children: "No endpoints to display." }) : /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60", children: visibleEndpoints.map((ep) => {
1080
- const isLoaded = loadedEndpoint?.method === ep.method && loadedEndpoint?.path === ep.path;
1081
- return /* @__PURE__ */ jsx(
1082
- EndpointDoc,
1083
- {
1084
- endpoint: ep,
1085
- isLoadedInPlayground: isLoaded,
1086
- onTryIt: () => onTryEndpoint(ep)
1087
- },
1088
- `${ep.method}-${ep.path}`
1089
- );
1090
- }) })
1091
- ] }) });
1092
- }, "DocsView"));
1093
- var MAX_DEPTH2 = 6;
1094
- function defaultForSchema(schema) {
1095
- if (!schema) return null;
1096
- if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
1097
- switch (schema.type) {
1098
- case "object": {
1099
- const out = {};
1100
- for (const [k, v] of Object.entries(schema.properties ?? {})) {
1101
- out[k] = defaultForSchema(v);
1102
- }
1103
- return out;
1104
- }
1105
- case "array":
1106
- return [];
1107
- case "integer":
1108
- case "number":
1109
- return 0;
1110
- case "boolean":
1111
- return false;
1112
- case "string":
1113
- return "";
1114
- default:
1115
- if (schema.properties) {
1116
- const out = {};
1117
- for (const [k, v] of Object.entries(schema.properties)) {
1118
- out[k] = defaultForSchema(v);
1119
- }
1120
- return out;
1121
- }
1122
- return "";
1123
- }
1124
- }
1125
- __name(defaultForSchema, "defaultForSchema");
1126
- function BodyFormEditor({ schema, value, onChange }) {
1127
- return /* @__PURE__ */ jsx(
1128
- SchemaField,
1129
- {
1130
- schema,
1131
- value,
1132
- onChange,
1133
- depth: 0,
1134
- required: false
1135
- }
1136
- );
1137
- }
1138
- __name(BodyFormEditor, "BodyFormEditor");
1139
- function SchemaField({ schema, value, onChange, depth, required, label }) {
1140
- if (depth > MAX_DEPTH2) {
1141
- return /* @__PURE__ */ jsx(RawJsonField, { label, value, onChange });
1142
- }
1143
- if (Array.isArray(schema.enum) && schema.enum.length > 0) {
1144
- return /* @__PURE__ */ jsx(EnumField, { schema, value, onChange, label, required });
1145
- }
1146
- switch (schema.type) {
1147
- case "object":
1148
- return /* @__PURE__ */ jsx(ObjectField, { schema, value, onChange, depth, label });
1149
- case "array":
1150
- return /* @__PURE__ */ jsx(ArrayField, { schema, value, onChange, depth, label, required });
1151
- case "boolean":
1152
- return /* @__PURE__ */ jsx(BooleanField, { value, onChange, label, schema, required });
1153
- case "integer":
1154
- case "number":
1155
- return /* @__PURE__ */ jsx(NumberField, { value, onChange, label, schema, required });
1156
- case "string":
1157
- default:
1158
- if (!schema.type && schema.properties) {
1159
- return /* @__PURE__ */ jsx(ObjectField, { schema, value, onChange, depth, label });
1160
- }
1161
- return /* @__PURE__ */ jsx(StringField, { value, onChange, label, schema, required });
1162
- }
1163
- }
1164
- __name(SchemaField, "SchemaField");
1165
- function FieldHeader({
1166
- label,
1167
- type,
1168
- required,
1169
- description
1170
- }) {
1171
- if (!label) return null;
1172
- return /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
1173
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
1174
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
1175
- required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
1176
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: type })
1177
- ] }),
1178
- description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/70 leading-snug", children: description })
1179
- ] });
1180
- }
1181
- __name(FieldHeader, "FieldHeader");
1182
- function StringField({
1183
- value,
1184
- onChange,
1185
- label,
1186
- schema,
1187
- required
1188
- }) {
1189
- const stringValue = typeof value === "string" ? value : value == null ? "" : String(value);
1190
- const placeholder = schema.format ? `${schema.type ?? "string"} (${schema.format})` : schema.description || schema.type || "string";
1191
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1192
- /* @__PURE__ */ jsx(FieldHeader, { label, type: schema.format ? `string (${schema.format})` : "string", required, description: schema.description }),
1193
- /* @__PURE__ */ jsx(
1194
- Input,
1195
- {
1196
- value: stringValue,
1197
- onChange: (e) => onChange(e.target.value),
1198
- placeholder,
1199
- className: "h-8 text-xs font-mono"
1200
- }
1201
- )
1202
- ] });
1203
- }
1204
- __name(StringField, "StringField");
1205
- function NumberField({
1206
- value,
1207
- onChange,
1208
- label,
1209
- schema,
1210
- required
1211
- }) {
1212
- const raw = value == null ? "" : String(value);
1213
- const type = schema.type === "integer" ? "integer" : "number";
1214
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1215
- /* @__PURE__ */ jsx(FieldHeader, { label, type: schema.format ? `${type} (${schema.format})` : type, required, description: schema.description }),
1216
- /* @__PURE__ */ jsx(
1217
- Input,
1218
- {
1219
- type: "number",
1220
- value: raw,
1221
- onChange: (e) => {
1222
- const v = e.target.value;
1223
- if (v === "") return onChange(null);
1224
- const n = schema.type === "integer" ? parseInt(v, 10) : parseFloat(v);
1225
- onChange(Number.isNaN(n) ? null : n);
1226
- },
1227
- placeholder: type,
1228
- className: "h-8 text-xs font-mono"
1229
- }
1230
- )
1231
- ] });
1232
- }
1233
- __name(NumberField, "NumberField");
1234
- function BooleanField({
1235
- value,
1236
- onChange,
1237
- label,
1238
- schema,
1239
- required
1240
- }) {
1241
- const bool = value === true;
1242
- return /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1243
- /* @__PURE__ */ jsx(FieldHeader, { label, type: "boolean", required, description: schema.description }),
1244
- /* @__PURE__ */ jsx(Switch, { checked: bool, onCheckedChange: onChange, className: "mt-0.5 shrink-0" })
1245
- ] });
1246
- }
1247
- __name(BooleanField, "BooleanField");
1248
- function EnumField({
1249
- schema,
1250
- value,
1251
- onChange,
1252
- label,
1253
- required
1254
- }) {
1255
- const options = (schema.enum ?? []).map((v) => ({
1256
- value: String(v),
1257
- label: String(v)
1258
- }));
1259
- const strValue = value == null ? "" : String(value);
1260
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1261
- /* @__PURE__ */ jsx(FieldHeader, { label, type: `${schema.type ?? "enum"} enum`, required, description: schema.description }),
1262
- /* @__PURE__ */ jsx(
1263
- Combobox,
1264
- {
1265
- options,
1266
- value: strValue,
1267
- onValueChange: (v) => {
1268
- if (schema.type === "integer") onChange(parseInt(v, 10));
1269
- else if (schema.type === "number") onChange(parseFloat(v));
1270
- else onChange(v);
1271
- },
1272
- placeholder: "Select\u2026",
1273
- searchPlaceholder: "Search\u2026",
1274
- className: "h-8 text-xs"
1275
- }
1276
- )
1277
- ] });
1278
- }
1279
- __name(EnumField, "EnumField");
1280
- function RawJsonField({
1281
- label,
1282
- value,
1283
- onChange
1284
- }) {
1285
- const [text, setText] = React6.useState(() => JSON.stringify(value ?? null, null, 2));
1286
- React6.useEffect(() => {
1287
- setText(JSON.stringify(value ?? null, null, 2));
1288
- }, [value]);
1289
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1290
- label && /* @__PURE__ */ jsxs(SectionLabel, { children: [
1291
- label,
1292
- " (raw)"
1293
- ] }),
1294
- /* @__PURE__ */ jsx(
1295
- Textarea,
1296
- {
1297
- value: text,
1298
- onChange: (e) => {
1299
- setText(e.target.value);
1300
- try {
1301
- onChange(JSON.parse(e.target.value));
1302
- } catch {
1303
- }
1304
- },
1305
- className: "font-mono text-[11px] min-h-[80px]",
1306
- rows: 4
1307
- }
1308
- )
1309
- ] });
1310
- }
1311
- __name(RawJsonField, "RawJsonField");
1312
- function ObjectField({
1313
- schema,
1314
- value,
1315
- onChange,
1316
- depth,
1317
- label
1318
- }) {
1319
- const obj = value && typeof value === "object" && !Array.isArray(value) ? value : {};
1320
- const required = new Set(schema.required ?? []);
1321
- const entries = Object.entries(schema.properties ?? {});
1322
- const setKey = useCallback(
1323
- (key) => (next) => {
1324
- onChange({ ...obj, [key]: next });
1325
- },
1326
- [obj, onChange]
1327
- );
1328
- if (entries.length === 0) {
1329
- return /* @__PURE__ */ jsx(RawJsonField, { label, value: obj, onChange });
1330
- }
1331
- const wrapperClass = depth === 0 ? "space-y-3" : "space-y-2 border-l-2 border-border/60 pl-3 ml-px";
1332
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1333
- label && depth > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
1334
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
1335
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: "object" })
1336
- ] }),
1337
- /* @__PURE__ */ jsx("div", { className: cn(wrapperClass), children: entries.map(([key, subSchema]) => /* @__PURE__ */ jsx(
1338
- SchemaField,
1339
- {
1340
- schema: subSchema,
1341
- value: obj[key],
1342
- onChange: setKey(key),
1343
- depth: depth + 1,
1344
- required: required.has(key),
1345
- label: key
1346
- },
1347
- key
1348
- )) })
1349
- ] });
1350
- }
1351
- __name(ObjectField, "ObjectField");
1352
- function ArrayField({
1353
- schema,
1354
- value,
1355
- onChange,
1356
- depth,
1357
- label,
1358
- required
1359
- }) {
1360
- const arr = Array.isArray(value) ? value : [];
1361
- const items = schema.items ?? { type: "string" };
1362
- const addItem = /* @__PURE__ */ __name(() => onChange([...arr, defaultForSchema(items)]), "addItem");
1363
- const removeAt = /* @__PURE__ */ __name((i) => onChange(arr.filter((_, idx) => idx !== i)), "removeAt");
1364
- const setAt = /* @__PURE__ */ __name((i) => (next) => onChange(arr.map((v, idx) => idx === i ? next : v)), "setAt");
1365
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1366
- label && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
1367
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
1368
- required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
1369
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: `array<${items.type ?? "any"}>` })
1370
- ] }),
1371
- /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-l-2 border-border/60 pl-3 ml-px", children: [
1372
- arr.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/50 italic", children: "Empty array" }),
1373
- arr.map((v, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
1374
- /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx(
1375
- SchemaField,
1376
- {
1377
- schema: items,
1378
- value: v,
1379
- onChange: setAt(i),
1380
- depth: depth + 1,
1381
- required: false,
1382
- label: `${label ?? ""}[${i}]`
1383
- }
1384
- ) }),
1385
- /* @__PURE__ */ jsx(
1386
- "button",
1387
- {
1388
- type: "button",
1389
- onClick: () => removeAt(i),
1390
- title: "Remove item",
1391
- className: "shrink-0 h-7 w-7 inline-flex items-center justify-center rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors",
1392
- children: /* @__PURE__ */ jsx(Minus, { className: "h-3.5 w-3.5" })
1393
- }
1394
- )
1395
- ] }, i)),
1396
- /* @__PURE__ */ jsxs(
1397
- "button",
1398
- {
1399
- type: "button",
1400
- onClick: addItem,
1401
- className: "inline-flex items-center gap-1.5 text-[10px] text-muted-foreground hover:text-foreground transition-colors py-1",
1402
- children: [
1403
- /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
1404
- "Add item"
1405
- ]
1406
- }
1407
- )
1408
- ] })
1409
- ] });
1410
- }
1411
- __name(ArrayField, "ArrayField");
1412
- function EndpointResetButton() {
1413
- const { state, setParameters, setRequestBody } = usePlaygroundContext();
1414
- const ep = state.selectedEndpoint;
1415
- const { reset } = useEndpointDraft(state.activeSchemaId, ep);
1416
- const hasDraft = Object.keys(state.parameters).length > 0 || state.requestBody.length > 0;
1417
- const onClick = useCallback(() => {
1418
- setParameters({});
1419
- setRequestBody("");
1420
- reset();
1421
- }, [setParameters, setRequestBody, reset]);
1422
- if (!ep || !hasDraft) return null;
1423
- return /* @__PURE__ */ jsxs(
1424
- "button",
1425
- {
1426
- type: "button",
1427
- onClick,
1428
- title: "Reset parameters & body (keeps auth)",
1429
- className: cn(
1430
- "inline-flex items-center gap-1 text-[10px] text-muted-foreground",
1431
- "hover:text-foreground transition-colors"
1432
- ),
1433
- children: [
1434
- /* @__PURE__ */ jsx(RotateCcw, { className: "h-2.5 w-2.5" }),
1435
- "Reset"
1436
- ]
1437
- }
1438
- );
1439
- }
1440
- __name(EndpointResetButton, "EndpointResetButton");
1441
- function ParamFields({ label, params }) {
1442
- const { state, setParameters } = usePlaygroundContext();
1443
- function handleChange(name, value) {
1444
- setParameters({ ...state.parameters, [name]: value });
1445
- }
1446
- __name(handleChange, "handleChange");
1447
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1448
- /* @__PURE__ */ jsx(SectionLabel, { children: label }),
1449
- /* @__PURE__ */ jsx("div", { className: "space-y-2", children: params.map((p) => {
1450
- const value = state.parameters[p.name] ?? "";
1451
- const placeholder = p.description || p.name;
1452
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1453
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
1454
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: p.name }),
1455
- p.required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
1456
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: p.type })
1457
- ] }),
1458
- /* @__PURE__ */ jsx(
1459
- Input,
1460
- {
1461
- value,
1462
- onChange: (e) => handleChange(p.name, e.target.value),
1463
- placeholder,
1464
- className: "h-8 text-xs font-mono"
1465
- }
1466
- )
1467
- ] }, p.name);
1468
- }) })
1469
- ] });
1470
- }
1471
- __name(ParamFields, "ParamFields");
1472
- function RequestPanel() {
1473
- const {
1474
- state,
1475
- apiKeys,
1476
- apiKeysLoading,
1477
- setRequestBody,
1478
- setRequestHeaders,
1479
- setSelectedApiKey,
1480
- setManualApiToken,
1481
- sendRequest
1482
- } = usePlaygroundContext();
1483
- const apiKeyOptions = useMemo(
1484
- () => apiKeys.map((k) => ({
1485
- value: k.id,
1486
- label: k.name || "Unnamed key",
1487
- // Surface the first 8 chars of the secret so the user
1488
- // can tell two similarly-named keys apart at a glance.
1489
- description: k.secret ? `${k.secret.slice(0, 8)}\u2026` : void 0
1490
- })),
1491
- [apiKeys]
1492
- );
1493
- const hasApiKeys = apiKeyOptions.length > 0;
1494
- const ep = state.selectedEndpoint;
1495
- const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
1496
- const curlCommand = useMemo(() => {
1497
- if (!state.requestUrl) return "";
1498
- const absoluteUrl = resolveAbsolute(state.requestUrl);
1499
- const apiKey = state.selectedApiKey ? findApiKeyById(apiKeys, state.selectedApiKey) : null;
1500
- const hdrs = parseRequestHeaders(state.requestHeaders);
1501
- if (apiKey) hdrs["X-API-Key"] = apiKey.secret || apiKey.id;
1502
- let cmd = `curl -X ${state.requestMethod} "${absoluteUrl}"`;
1503
- Object.entries(hdrs).forEach(([k, v]) => {
1504
- cmd += ` \\
1505
- -H "${k}: ${v}"`;
1506
- });
1507
- if (state.requestBody && state.requestMethod !== "GET" && isJsonValid) {
1508
- cmd += ` \\
1509
- -d '${state.requestBody}'`;
1510
- }
1511
- return cmd;
1512
- }, [state, apiKeys, isJsonValid]);
1513
- const pathParams = useMemo(
1514
- () => ep?.parameters?.filter((p) => ep.path.includes(`{${p.name}}`)) ?? [],
1515
- [ep]
1516
- );
1517
- const queryParams = useMemo(
1518
- () => ep?.parameters?.filter((p) => !ep.path.includes(`{${p.name}}`)) ?? [],
1519
- [ep]
1520
- );
1521
- state.loading || !state.requestUrl || !isJsonValid;
1522
- const displayUrl = resolveAbsolute(state.requestUrl || ep?.path || "");
1523
- const hasBody = ep?.method !== "GET";
1524
- const bodyType = ep?.requestBody?.type ?? "";
1525
- const hasPathParams = pathParams.length > 0;
1526
- const hasQueryParams = queryParams.length > 0;
1527
- const hasCurl = Boolean(curlCommand);
1528
- const epPath = ep ? relativePath(ep.path) : "";
1529
- const urlChanged = displayUrl !== "" && displayUrl !== epPath;
1530
- if (!ep) {
1531
- return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: "Select an endpoint to build a request" });
1532
- }
1533
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1534
- (urlChanged || ep) && /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 bg-muted/10 flex items-center gap-2 min-h-[28px]", children: [
1535
- urlChanged ? /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/60 break-all leading-snug truncate min-w-0 flex-1", children: displayUrl }) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
1536
- /* @__PURE__ */ jsx(EndpointResetButton, {})
1537
- ] }),
1538
- /* @__PURE__ */ jsxs(ScrollArea, { className: "px-4 py-3 space-y-3", children: [
1539
- hasPathParams && /* @__PURE__ */ jsx(ParamFields, { label: "Path Parameters", params: pathParams }),
1540
- hasQueryParams && /* @__PURE__ */ jsx(ParamFields, { label: "Query Parameters", params: queryParams }),
1541
- hasBody && /* @__PURE__ */ jsx(
1542
- BodySection,
1543
- {
1544
- schema: ep.requestBody?.schema,
1545
- bodyType,
1546
- bodyDescription: ep.requestBody?.description,
1547
- value: state.requestBody,
1548
- onChange: setRequestBody,
1549
- isJsonValid
1550
- }
1551
- ),
1552
- /* @__PURE__ */ jsx(
1553
- CollapsibleSection,
1554
- {
1555
- label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
1556
- /* @__PURE__ */ jsx(Key, { className: "h-2.5 w-2.5" }),
1557
- "Auth & Headers"
1558
- ] }),
1559
- children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 pt-2", children: [
1560
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1561
- /* @__PURE__ */ jsx(SectionLabel, { children: "API Key" }),
1562
- /* @__PURE__ */ jsx(
1563
- Combobox,
1564
- {
1565
- options: apiKeyOptions,
1566
- value: state.selectedApiKey ?? "",
1567
- onValueChange: (v) => setSelectedApiKey(v || null),
1568
- placeholder: apiKeysLoading ? "Loading keys\u2026" : hasApiKeys ? "Select an API key" : "No API keys yet",
1569
- searchPlaceholder: "Search keys\u2026",
1570
- emptyText: "No matching key",
1571
- disabled: apiKeysLoading || !hasApiKeys,
1572
- className: "h-8"
1573
- }
1574
- ),
1575
- /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
1576
- "Picks are sent via the",
1577
- " ",
1578
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: "X-API-Key" }),
1579
- " header."
1580
- ] })
1581
- ] }),
1582
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1583
- /* @__PURE__ */ jsx(SectionLabel, { children: "Bearer Token" }),
1584
- /* @__PURE__ */ jsx(
1585
- Input,
1586
- {
1587
- type: "password",
1588
- placeholder: "Leave empty to use JWT from localStorage",
1589
- value: state.manualApiToken,
1590
- onChange: (e) => setManualApiToken(e.target.value),
1591
- className: "font-mono text-xs h-8"
1592
- }
1593
- )
1594
- ] }),
1595
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1596
- /* @__PURE__ */ jsx(SectionLabel, { children: "Headers" }),
1597
- /* @__PURE__ */ jsx(
1598
- Textarea,
1599
- {
1600
- value: state.requestHeaders,
1601
- onChange: (e) => setRequestHeaders(e.target.value),
1602
- className: "font-mono text-[11px] min-h-[60px] resize-y",
1603
- rows: 3
1604
- }
1605
- )
1606
- ] })
1607
- ] })
1608
- }
1609
- ),
1610
- hasCurl && /* @__PURE__ */ jsx(
1611
- CollapsibleSection,
1612
- {
1613
- label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
1614
- /* @__PURE__ */ jsx(Terminal, { className: "h-2.5 w-2.5" }),
1615
- "cURL"
1616
- ] }),
1617
- children: /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
1618
- PrettyCode_default,
1619
- {
1620
- data: curlCommand,
1621
- language: "bash",
1622
- isCompact: true,
1623
- maxLines: 50
1624
- }
1625
- ) })
1626
- }
1627
- ),
1628
- /* @__PURE__ */ jsx("div", { className: "h-4" })
1629
- ] })
1630
- ] });
1631
- }
1632
- __name(RequestPanel, "RequestPanel");
1633
- function BodySection({ schema, bodyType, bodyDescription, value, onChange, isJsonValid }) {
1634
- const hasSchema = !!schema;
1635
- const [mode, setMode] = React6.useState(hasSchema ? "form" : "json");
1636
- const parsed = React6.useMemo(() => {
1637
- if (!value) return null;
1638
- try {
1639
- return JSON.parse(value);
1640
- } catch {
1641
- return null;
1642
- }
1643
- }, [value]);
1644
- const handleFormChange = useCallback(
1645
- (next) => {
1646
- onChange(JSON.stringify(next, null, 2));
1647
- },
1648
- [onChange]
1649
- );
1650
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1651
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
1652
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 min-w-0", children: [
1653
- /* @__PURE__ */ jsx(SectionLabel, { children: "Body" }),
1654
- bodyType && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/40 font-mono", children: bodyType }),
1655
- bodyDescription && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60 truncate", children: bodyDescription })
1656
- ] }),
1657
- hasSchema && /* @__PURE__ */ jsxs("div", { className: "inline-flex rounded-md border overflow-hidden text-[10px]", children: [
1658
- /* @__PURE__ */ jsx(ModeButton, { active: mode === "form", onClick: () => setMode("form"), children: "Form" }),
1659
- /* @__PURE__ */ jsx(ModeButton, { active: mode === "json", onClick: () => setMode("json"), children: "JSON" })
1660
- ] }),
1661
- mode === "json" && isJsonValid && value && /* @__PURE__ */ jsxs(
1662
- "button",
1663
- {
1664
- type: "button",
1665
- onClick: () => {
1666
- try {
1667
- onChange(JSON.stringify(JSON.parse(value), null, 2));
1668
- } catch {
1669
- }
1670
- },
1671
- className: "inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors",
1672
- children: [
1673
- /* @__PURE__ */ jsx(Sparkles, { className: "h-2.5 w-2.5" }),
1674
- "Format"
1675
- ]
1676
- }
1677
- )
1678
- ] }),
1679
- mode === "form" && hasSchema ? /* @__PURE__ */ jsx(
1680
- BodyFormEditor,
1681
- {
1682
- schema,
1683
- value: parsed,
1684
- onChange: handleFormChange
1685
- }
1686
- ) : /* @__PURE__ */ jsxs(Fragment, { children: [
1687
- /* @__PURE__ */ jsx(
1688
- Textarea,
1689
- {
1690
- placeholder: '{\n "key": "value"\n}',
1691
- value,
1692
- onChange: (e) => onChange(e.target.value),
1693
- className: cn(
1694
- "font-mono text-[11px] min-h-[90px] resize-y",
1695
- !isJsonValid && "border-destructive focus-visible:ring-destructive/30"
1696
- ),
1697
- rows: 4
1698
- }
1699
- ),
1700
- !isJsonValid && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-destructive", children: "Invalid JSON" })
1701
- ] })
1702
- ] });
1703
- }
1704
- __name(BodySection, "BodySection");
1705
- function ModeButton({
1706
- active,
1707
- onClick,
1708
- children
1709
- }) {
1710
- return /* @__PURE__ */ jsx(
1711
- "button",
1712
- {
1713
- type: "button",
1714
- onClick,
1715
- className: cn(
1716
- "px-2 py-0.5 font-medium transition-colors",
1717
- active ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:text-foreground"
1718
- ),
1719
- children
1720
- }
1721
- );
1722
- }
1723
- __name(ModeButton, "ModeButton");
1724
- var JSON_TREE_CONFIG = {
1725
- maxAutoExpandDepth: 2,
1726
- maxAutoExpandArrayItems: 10,
1727
- maxAutoExpandObjectKeys: 5,
1728
- maxStringLength: 200,
1729
- collectionLimit: 50,
1730
- showCollectionInfo: true,
1731
- showExpandControls: true,
1732
- showActionButtons: false,
1733
- preserveKeyOrder: true,
1734
- className: "border-0 rounded-none"
1735
- };
1736
- function ResponsePanel() {
1737
- const { state } = usePlaygroundContext();
1738
- const { response, loading, selectedEndpoint } = state;
1739
- const { treeData, rawText } = useMemo(() => {
1740
- const d = response?.data;
1741
- if (d == null) return { treeData: null, rawText: "" };
1742
- if (typeof d === "string") {
1743
- try {
1744
- return { treeData: JSON.parse(d), rawText: d };
1745
- } catch {
1746
- return { treeData: null, rawText: d };
1747
- }
1748
- }
1749
- return { treeData: d, rawText: JSON.stringify(d, null, 2) };
1750
- }, [response?.data]);
1751
- const sizeKb = rawText ? `${(rawText.length / 1024).toFixed(1)} KB` : "";
1752
- const duration = response?.duration != null ? `${response.duration}ms` : "";
1753
- const hasError = Boolean(response?.error);
1754
- const hasStatus = response?.status != null;
1755
- const hasCopy = Boolean(rawText);
1756
- if (loading) {
1757
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center h-full gap-2", children: [
1758
- /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }),
1759
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Sending\u2026" })
1760
- ] });
1761
- }
1762
- if (!selectedEndpoint) return /* @__PURE__ */ jsx(EmptyState, { icon: Terminal, text: "Response will appear here" });
1763
- if (!response) return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: 'Press "Send Request" to see the response' });
1764
- if (hasError && !hasStatus) {
1765
- return /* @__PURE__ */ jsx(
1766
- EmptyState,
1767
- {
1768
- icon: WifiOff,
1769
- text: response.error,
1770
- className: "text-destructive [&_svg]:text-destructive"
1771
- }
1772
- );
1773
- }
1774
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1775
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 flex items-center justify-between gap-3 bg-muted/20", children: [
1776
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
1777
- hasStatus && /* @__PURE__ */ jsx(StatusBadge, { status: response.status }),
1778
- response.statusText && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: response.statusText }),
1779
- sizeKb && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: sizeKb }),
1780
- duration && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: duration })
1781
- ] }),
1782
- hasCopy && /* @__PURE__ */ jsx(CopyButton, { value: rawText, variant: "ghost", size: "sm", className: "h-6 px-2 text-[10px] text-muted-foreground shrink-0", children: "Copy" })
1783
- ] }),
1784
- hasError && /* @__PURE__ */ jsx("div", { className: "shrink-0 mx-4 mt-3 rounded border border-destructive/20 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: response.error }) }),
1785
- /* @__PURE__ */ jsx(ScrollArea, { children: treeData != null ? /* @__PURE__ */ jsx(JsonTree_default, { title: "Response Body", data: treeData, config: JSON_TREE_CONFIG }) : rawText ? /* @__PURE__ */ jsx("pre", { className: "p-4 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: rawText }) : /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" }) })
1786
- ] });
1787
- }
1788
- __name(ResponsePanel, "ResponsePanel");
1789
- function SendButton({ className }) {
1790
- const { state, sendRequest } = usePlaygroundContext();
1791
- const ep = state.selectedEndpoint;
1792
- const builder = useMemo(
1793
- () => ep ? new UrlBuilder(ep, state.parameters) : null,
1794
- [ep, state.parameters]
1795
- );
1796
- const missingRequired = builder?.missingRequired() ?? [];
1797
- const unsubstituted = builder?.unfilledPlaceholders() ?? [];
1798
- const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
1799
- const blockers = [];
1800
- if (missingRequired.length > 0) {
1801
- blockers.push(
1802
- `Fill required parameter${missingRequired.length > 1 ? "s" : ""}: ${missingRequired.join(", ")}`
1803
- );
1804
- } else if (unsubstituted.length > 0) {
1805
- blockers.push(`URL still has unfilled placeholder${unsubstituted.length > 1 ? "s" : ""}: ${unsubstituted.map((n) => `{${n}}`).join(", ")}`);
1806
- }
1807
- if (!isJsonValid) blockers.push("Request body is not valid JSON");
1808
- const disabled = state.loading || !state.requestUrl || blockers.length > 0;
1809
- const tooltip = blockers.length > 0 ? blockers.join("\n") : void 0;
1810
- return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
1811
- blockers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/25 bg-amber-500/[0.06] px-3 py-2 text-[11px] text-amber-600 dark:text-amber-400", children: [
1812
- /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 shrink-0 mt-px" }),
1813
- /* @__PURE__ */ jsx("span", { className: "leading-snug", children: blockers[0] })
1814
- ] }),
1815
- /* @__PURE__ */ jsx(
1816
- Button,
1817
- {
1818
- onClick: sendRequest,
1819
- disabled,
1820
- size: "sm",
1821
- title: tooltip,
1822
- className: "w-full gap-2 h-9",
1823
- children: state.loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
1824
- /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
1825
- "Sending\u2026"
1826
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1827
- /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
1828
- "Send Request"
1829
- ] })
1830
- }
1831
- )
1832
- ] });
1833
- }
1834
- __name(SendButton, "SendButton");
1835
- var WIDTH_NARROW = "clamp(380px, 30vw, 480px)";
1836
- var WIDTH_WIDE = "clamp(720px, 60vw, 1280px)";
1837
- function SlideInPlayground({ open, onClose }) {
1838
- const { state } = usePlaygroundContext();
1839
- const ep = state.selectedEndpoint;
1840
- const showResponse = state.response !== null || state.loading;
1841
- const width = showResponse ? WIDTH_WIDE : WIDTH_NARROW;
1842
- return /* @__PURE__ */ jsx(SidePanel, { open, onOpenChange: (v) => !v && onClose(), side: "right", children: /* @__PURE__ */ jsxs(SidePanel.Content, { width, className: "max-w-[95vw]", children: [
1843
- /* @__PURE__ */ jsxs(SidePanel.Header, { children: [
1844
- /* @__PURE__ */ jsx(SidePanel.Title, { children: "Playground" }),
1845
- ep && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [
1846
- /* @__PURE__ */ jsx(MethodBadge, { method: ep.method }),
1847
- /* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground truncate", children: relativePath(ep.path) })
1848
- ] }),
1849
- /* @__PURE__ */ jsx(SidePanel.Close, { className: "ml-auto" })
1850
- ] }),
1851
- /* @__PURE__ */ jsxs(
1852
- SidePanel.Body,
1853
- {
1854
- className: cn(
1855
- "overflow-hidden grid divide-x transition-[grid-template-columns] duration-250",
1856
- showResponse ? "grid-cols-[minmax(0,1fr)_minmax(0,1fr)]" : "grid-cols-1"
1857
- ),
1858
- children: [
1859
- /* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsx(RequestPanel, {}) }),
1860
- showResponse && /* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsx(ResponsePanel, {}) })
1861
- ]
1862
- }
1863
- ),
1864
- ep && /* @__PURE__ */ jsx(SidePanel.Footer, { className: "px-4 py-3", children: /* @__PURE__ */ jsx(SendButton, {}) })
1865
- ] }) });
1866
- }
1867
- __name(SlideInPlayground, "SlideInPlayground");
1868
- function TryItSheet({ open, onOpenChange }) {
1869
- const { state } = usePlaygroundContext();
1870
- const showResponse = state.response !== null || state.loading;
1871
- return /* @__PURE__ */ jsx(ResponsiveSheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ResponsiveSheetContent, { className: "sm:max-w-xl flex flex-col h-full p-0", children: [
1872
- /* @__PURE__ */ jsx(ResponsiveSheetHeader, { className: "px-4 py-3 border-b shrink-0", children: /* @__PURE__ */ jsx(ResponsiveSheetTitle, { className: "text-sm", children: "Playground" }) }),
1873
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 flex flex-col divide-y", children: [
1874
- /* @__PURE__ */ jsx("div", { className: showResponse ? "flex-1 min-h-0 flex flex-col" : "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsx(RequestPanel, {}) }),
1875
- showResponse && /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsx(ResponsePanel, {}) })
1876
- ] }),
1877
- state.selectedEndpoint && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background/95 backdrop-blur-sm", children: /* @__PURE__ */ jsx(SendButton, {}) })
1878
- ] }) });
1879
- }
1880
- __name(TryItSheet, "TryItSheet");
1881
- var DocsLayout = /* @__PURE__ */ __name(() => {
1882
- const { state, config, setSelectedEndpoint } = usePlaygroundContext();
1883
- const isDesktop = useMediaQuery("(min-width: 1024px)");
1884
- const isMobile = !isDesktop;
1885
- const {
1886
- endpoints,
1887
- schemaInfo,
1888
- rawSchema,
1889
- resolvedBaseUrl,
1890
- loading,
1891
- error,
1892
- schemas,
1893
- currentSchema,
1894
- setCurrentSchema
1895
- } = useOpenApiSchema({
1896
- schemas: config.schemas,
1897
- defaultSchemaId: config.defaultSchemaId,
1898
- baseUrl: config.baseUrl
1899
- });
1900
- const [activeAnchor, setActiveAnchor] = useState(null);
1901
- const [sheetOpen, setSheetOpen] = useState(false);
1902
- const docsRef = useRef(null);
1903
- const slideOpen = !isMobile && state.selectedEndpoint !== null;
1904
- const handleTry = useCallback(
1905
- (ep) => {
1906
- setSelectedEndpoint(ep);
1907
- if (isMobile) setSheetOpen(true);
1908
- },
1909
- [isMobile, setSelectedEndpoint]
1910
- );
1911
- const handleCloseSlide = useCallback(() => {
1912
- setSelectedEndpoint(null);
1913
- }, [setSelectedEndpoint]);
1914
- const handleNavigate = useCallback((anchor) => {
1915
- docsRef.current?.scrollToAnchor(anchor);
1916
- }, []);
1917
- if (loading) {
1918
- return /* @__PURE__ */ jsxs(
1919
- "div",
1920
- {
1921
- className: "grid grid-cols-[260px_1fr] min-h-0 overflow-hidden",
1922
- style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
1923
- children: [
1924
- /* @__PURE__ */ jsx("div", { className: "border-r p-3 space-y-1.5", children: Array.from({ length: 12 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-full rounded" }, i)) }),
1925
- /* @__PURE__ */ jsxs("div", { className: "p-8 space-y-4", children: [
1926
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-1/2" }),
1927
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }),
1928
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-3/4" }),
1929
- /* @__PURE__ */ jsx("div", { className: "mt-8 space-y-6", children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1930
- /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-1/3" }),
1931
- /* @__PURE__ */ jsx(Skeleton, { className: "h-20 w-full" })
1932
- ] }, i)) })
1933
- ] })
1934
- ]
1935
- }
1936
- );
1937
- }
1938
- if (error) {
1939
- return /* @__PURE__ */ jsx(
1940
- "div",
1941
- {
1942
- className: "flex items-center justify-center p-8",
1943
- style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
1944
- children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
1945
- "Failed to load schema: ",
1946
- error
1947
- ] })
1948
- }
1949
- );
1950
- }
1951
- if (isMobile) {
1952
- return /* @__PURE__ */ jsxs(
1953
- "div",
1954
- {
1955
- className: "flex flex-col overflow-hidden",
1956
- style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
1957
- children: [
1958
- /* @__PURE__ */ jsx(EndpointDraftSync, { schemaId: currentSchema?.id ?? null }),
1959
- /* @__PURE__ */ jsx(
1960
- DocsView,
1961
- {
1962
- ref: docsRef,
1963
- info: schemaInfo,
1964
- rawSchema,
1965
- resolvedBaseUrl,
1966
- endpoints,
1967
- selectedVersion: state.selectedVersion,
1968
- loadedEndpoint: state.selectedEndpoint,
1969
- onTryEndpoint: handleTry,
1970
- onActiveChange: setActiveAnchor
1971
- }
1972
- ),
1973
- /* @__PURE__ */ jsx(TryItSheet, { open: sheetOpen, onOpenChange: setSheetOpen })
1974
- ]
1975
- }
1976
- );
1977
- }
1978
- return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 350, children: /* @__PURE__ */ jsxs(
1979
- "div",
1980
- {
1981
- className: "grid grid-cols-[260px_minmax(0,1fr)] min-h-0 overflow-hidden",
1982
- style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
1983
- children: [
1984
- /* @__PURE__ */ jsx(EndpointDraftSync, { schemaId: currentSchema?.id ?? null }),
1985
- /* @__PURE__ */ jsx(
1986
- DocsSidebar,
1987
- {
1988
- info: schemaInfo,
1989
- endpoints,
1990
- schemas,
1991
- currentSchemaId: currentSchema?.id ?? null,
1992
- onSchemaChange: setCurrentSchema,
1993
- activeEndpointId: activeAnchor,
1994
- selectedVersion: state.selectedVersion,
1995
- onNavigate: handleNavigate
1996
- }
1997
- ),
1998
- /* @__PURE__ */ jsx(
1999
- DocsView,
2000
- {
2001
- ref: docsRef,
2002
- info: schemaInfo,
2003
- rawSchema,
2004
- resolvedBaseUrl,
2005
- endpoints,
2006
- selectedVersion: state.selectedVersion,
2007
- loadedEndpoint: state.selectedEndpoint,
2008
- onTryEndpoint: handleTry,
2009
- onActiveChange: setActiveAnchor
2010
- }
2011
- ),
2012
- /* @__PURE__ */ jsx(SlideInPlayground, { open: slideOpen, onClose: handleCloseSlide })
2013
- ]
2014
- }
2015
- ) });
2016
- }, "DocsLayout");
2017
-
2018
- export { DocsLayout };
2019
- //# sourceMappingURL=DocsLayout-ERETJLLV.mjs.map
2020
- //# sourceMappingURL=DocsLayout-ERETJLLV.mjs.map