@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.
- package/README.md +14 -3
- package/dist/DocsLayout-IKH7BLSU.cjs +3464 -0
- package/dist/DocsLayout-IKH7BLSU.cjs.map +1 -0
- package/dist/DocsLayout-JPXFUKAR.mjs +3457 -0
- package/dist/DocsLayout-JPXFUKAR.mjs.map +1 -0
- package/dist/{PrettyCode.client-5GABIN2I.cjs → PrettyCode.client-RPDIE5CH.cjs} +104 -3
- package/dist/PrettyCode.client-RPDIE5CH.cjs.map +1 -0
- package/dist/{PrettyCode.client-IZTXXYHG.mjs → PrettyCode.client-SPMTQEG4.mjs} +106 -5
- package/dist/PrettyCode.client-SPMTQEG4.mjs.map +1 -0
- package/dist/{chunk-IULI4XII.cjs → chunk-5Q4UMSWB.cjs} +355 -9
- package/dist/chunk-5Q4UMSWB.cjs.map +1 -0
- package/dist/{chunk-VZGQC3NG.mjs → chunk-EFWOJPA6.mjs} +349 -9
- package/dist/chunk-EFWOJPA6.mjs.map +1 -0
- package/dist/index.cjs +10 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +21 -14
- package/src/components/markdown/MarkdownMessage.tsx +46 -0
- package/src/tools/OpenapiViewer/OpenapiViewer.story.tsx +93 -157
- package/src/tools/OpenapiViewer/README.md +114 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/ApiIntroSection.tsx +20 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/DocsView.tsx +331 -53
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/LanguageTabs.tsx +36 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/index.tsx +56 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/CodeSamples/useCodeSnippet.ts +77 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MetaActions.tsx +146 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/MethodBadge.tsx +6 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/PathDisplay.tsx +26 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Header/index.tsx +87 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamGroup.tsx +30 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/ParamRow.tsx +36 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Parameters/index.tsx +22 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/RequestBody/index.tsx +33 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +76 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseRow.tsx +80 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/StatusTag.tsx +32 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/index.tsx +21 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/FieldRow.tsx +106 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts +127 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/index.tsx +31 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/types.ts +28 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/SectionHeader.tsx +87 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts +27 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/index.tsx +45 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/context.tsx +56 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts +63 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/index.tsx +96 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/index.ts +133 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts +40 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts +17 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SchemaCopyMenu.tsx +40 -11
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/BrandHeader.tsx +48 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/CategoryBlock.tsx +33 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/EndpointRow.tsx +73 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/MethodChips.tsx +43 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SchemaSection.tsx +27 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SearchInput.tsx +45 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/SidebarBody.tsx +50 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/Toolbar.tsx +64 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts +126 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/index.tsx +112 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts +42 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar/useDebouncedValue.ts +14 -0
- package/src/tools/OpenapiViewer/components/DocsLayout/SlideInPlayground.tsx +10 -7
- package/src/tools/OpenapiViewer/components/DocsLayout/TryItSheet.tsx +9 -6
- package/src/tools/OpenapiViewer/components/DocsLayout/anchor.ts +19 -2
- package/src/tools/OpenapiViewer/components/DocsLayout/grouping.ts +38 -21
- package/src/tools/OpenapiViewer/components/DocsLayout/index.tsx +168 -50
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +55 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/PreviewView.tsx +115 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/RawView.tsx +24 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/StatusBar.tsx +63 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/ViewTabs.tsx +45 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts +97 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/index.tsx +93 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/types.ts +26 -0
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts +62 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +3 -1
- package/src/tools/OpenapiViewer/hooks/useDocsUrlSync.ts +119 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +164 -74
- package/src/tools/OpenapiViewer/types.ts +46 -1
- package/src/tools/OpenapiViewer/utils/codeSamples.ts +287 -0
- package/src/tools/OpenapiViewer/utils/index.ts +3 -0
- package/src/tools/OpenapiViewer/utils/operationToHar.ts +119 -0
- package/src/tools/OpenapiViewer/utils/sampler.ts +72 -0
- package/src/tools/OpenapiViewer/utils/scrollParent.ts +68 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +88 -1
- package/src/tools/PrettyCode/PrettyCode.story.tsx +114 -361
- package/src/tools/PrettyCode/index.tsx +13 -0
- package/src/tools/PrettyCode/lazy.tsx +5 -0
- package/src/tools/PrettyCode/registerPrismLanguages.ts +111 -0
- package/dist/DocsLayout-BCVU6TTX.cjs +0 -2027
- package/dist/DocsLayout-BCVU6TTX.cjs.map +0 -1
- package/dist/DocsLayout-ERETJLLV.mjs +0 -2020
- package/dist/DocsLayout-ERETJLLV.mjs.map +0 -1
- package/dist/PrettyCode.client-5GABIN2I.cjs.map +0 -1
- package/dist/PrettyCode.client-IZTXXYHG.mjs.map +0 -1
- package/dist/chunk-IULI4XII.cjs.map +0 -1
- package/dist/chunk-VZGQC3NG.mjs.map +0 -1
- package/src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc.tsx +0 -268
- package/src/tools/OpenapiViewer/components/DocsLayout/Sidebar.tsx +0 -211
- package/src/tools/OpenapiViewer/components/shared/ResponsePanel.tsx +0 -127
|
@@ -0,0 +1,3457 @@
|
|
|
1
|
+
import { deduplicateEndpoints, dereferenceSchema, resolveBaseUrl, usePlaygroundContext, toMarkdown, toCompactJson, toRawJson, formatBytes, MarkdownMessage, CODE_SAMPLE_TARGETS, buildHarRequest, resolveAbsolute, renderSnippet, PrettyCode_default, relativePath, endpointToMarkdown, isValidJson, findApiKeyById, parseRequestHeaders, UrlBuilder, sampleSchemaJson, joinUrl } from './chunk-EFWOJPA6.mjs';
|
|
2
|
+
import { JsonTree_default } from './chunk-LFWQ36LJ.mjs';
|
|
3
|
+
import './chunk-SSUOENAZ.mjs';
|
|
4
|
+
import { __name } from './chunk-CGILA3WO.mjs';
|
|
5
|
+
import React12, { createContext, useRef, useCallback, useEffect, useMemo, useState, useContext } from 'react';
|
|
6
|
+
import { groupBy, orderBy, partition, sortBy, keyBy } from 'lodash-es';
|
|
7
|
+
import { Tooltip, TooltipTrigger, TooltipContent, DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuItem, Input, Combobox, SafeTooltipProvider, CopyButton, Switch, Textarea, SidePanel, ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, ResponsiveSheetTitle, Skeleton, TooltipProvider } from '@djangocfg/ui-core/components';
|
|
8
|
+
import { toast, useMediaQuery } from '@djangocfg/ui-core/hooks';
|
|
9
|
+
import consola from 'consola';
|
|
10
|
+
import { ChevronRight, Sparkles, ChevronDown, Check, Search, X, Link2, FileCode2, ChevronsDownUp, ChevronsUpDown, Play, Minus, Plus, RotateCcw, Send, Key, Terminal, Info, ShieldCheck, Loader2, WifiOff, AlertCircle } from 'lucide-react';
|
|
11
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
12
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
13
|
+
import { create } from 'zustand';
|
|
14
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
15
|
+
|
|
16
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
17
|
+
var extractEndpoints = /* @__PURE__ */ __name((schema, baseUrl, schemaId, specRoot) => {
|
|
18
|
+
const endpoints = [];
|
|
19
|
+
if (!schema.paths) return [];
|
|
20
|
+
for (const [path, methods] of Object.entries(schema.paths)) {
|
|
21
|
+
for (const method of HTTP_METHODS) {
|
|
22
|
+
const op = methods[method];
|
|
23
|
+
if (!op) continue;
|
|
24
|
+
const methodUpper = method.toUpperCase();
|
|
25
|
+
const summary = (op.summary || "").trim();
|
|
26
|
+
const description = op.description || summary || `${methodUpper} ${path}`;
|
|
27
|
+
const category = op.tags?.[0] || "Other";
|
|
28
|
+
const parameters = [];
|
|
29
|
+
const allParams = [...methods.parameters || [], ...op.parameters || []];
|
|
30
|
+
for (const param of allParams) {
|
|
31
|
+
parameters.push({
|
|
32
|
+
name: param.name,
|
|
33
|
+
type: param.schema?.type || "string",
|
|
34
|
+
required: param.required || false,
|
|
35
|
+
description: param.description
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const responses = [];
|
|
39
|
+
if (op.responses) {
|
|
40
|
+
for (const [code, response] of Object.entries(op.responses)) {
|
|
41
|
+
const respContent = response.content;
|
|
42
|
+
const contentKeys = respContent ? Object.keys(respContent) : [];
|
|
43
|
+
const chosenContentType = respContent?.["application/json"] ? "application/json" : contentKeys[0];
|
|
44
|
+
const chosen = chosenContentType ? respContent?.[chosenContentType] : void 0;
|
|
45
|
+
const respSchema = chosen?.schema;
|
|
46
|
+
responses.push({
|
|
47
|
+
code,
|
|
48
|
+
description: response.description || `Response ${code}`,
|
|
49
|
+
contentType: chosenContentType,
|
|
50
|
+
schema: respSchema,
|
|
51
|
+
example: respSchema ? sampleSchemaJson(respSchema, { skipWriteOnly: true }, specRoot) : void 0
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
let requestBody;
|
|
56
|
+
if (op.requestBody) {
|
|
57
|
+
const content = op.requestBody.content;
|
|
58
|
+
const mediaType = content?.["application/json"] || content?.[Object.keys(content || {})[0]];
|
|
59
|
+
const rawSchema = mediaType?.schema;
|
|
60
|
+
requestBody = {
|
|
61
|
+
type: rawSchema?.type || "object",
|
|
62
|
+
description: op.requestBody.description,
|
|
63
|
+
schema: rawSchema,
|
|
64
|
+
example: rawSchema ? sampleSchemaJson(rawSchema, { skipReadOnly: true }, specRoot) : void 0
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const endpoint = {
|
|
68
|
+
name: path.split("/").pop() || path,
|
|
69
|
+
method: methodUpper,
|
|
70
|
+
path: baseUrl ? joinUrl(baseUrl, path) : path,
|
|
71
|
+
summary,
|
|
72
|
+
description,
|
|
73
|
+
category,
|
|
74
|
+
parameters: parameters.length > 0 ? parameters : void 0,
|
|
75
|
+
requestBody,
|
|
76
|
+
responses: responses.length > 0 ? responses : void 0,
|
|
77
|
+
schemaId
|
|
78
|
+
};
|
|
79
|
+
endpoints.push(endpoint);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return endpoints;
|
|
83
|
+
}, "extractEndpoints");
|
|
84
|
+
var getCategories = /* @__PURE__ */ __name((endpoints) => {
|
|
85
|
+
const categories = /* @__PURE__ */ new Set();
|
|
86
|
+
endpoints.forEach((endpoint) => categories.add(endpoint.category));
|
|
87
|
+
return Array.from(categories).sort();
|
|
88
|
+
}, "getCategories");
|
|
89
|
+
var fetchSchema = /* @__PURE__ */ __name(async (url) => {
|
|
90
|
+
const response = await fetch(url, {
|
|
91
|
+
headers: {
|
|
92
|
+
"Accept": "application/json"
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new Error(`Failed to fetch schema: ${response.statusText}`);
|
|
97
|
+
}
|
|
98
|
+
return response.json();
|
|
99
|
+
}, "fetchSchema");
|
|
100
|
+
function useOpenApiSchema({
|
|
101
|
+
schemas,
|
|
102
|
+
defaultSchemaId,
|
|
103
|
+
baseUrl: configBaseUrl,
|
|
104
|
+
preloadAll = false
|
|
105
|
+
}) {
|
|
106
|
+
const [loading, setLoading] = useState(true);
|
|
107
|
+
const [error, setError] = useState(null);
|
|
108
|
+
const [currentSchemaId, setCurrentSchemaId] = useState(
|
|
109
|
+
defaultSchemaId || schemas[0]?.id
|
|
110
|
+
);
|
|
111
|
+
const [loadedSchemas, setLoadedSchemas] = useState(
|
|
112
|
+
/* @__PURE__ */ new Map()
|
|
113
|
+
);
|
|
114
|
+
const [loadStates, setLoadStates] = useState(/* @__PURE__ */ new Map());
|
|
115
|
+
const currentSchema = useMemo(
|
|
116
|
+
() => schemas.find((s) => s.id === currentSchemaId) || null,
|
|
117
|
+
[schemas, currentSchemaId]
|
|
118
|
+
);
|
|
119
|
+
const currentOpenApiSchema = useMemo(
|
|
120
|
+
() => currentSchemaId ? loadedSchemas.get(currentSchemaId) : null,
|
|
121
|
+
[loadedSchemas, currentSchemaId]
|
|
122
|
+
);
|
|
123
|
+
const dereferencedSchema = useMemo(
|
|
124
|
+
() => currentOpenApiSchema ? dereferenceSchema(currentOpenApiSchema) : null,
|
|
125
|
+
[currentOpenApiSchema]
|
|
126
|
+
);
|
|
127
|
+
const resolvedBaseUrl = useMemo(
|
|
128
|
+
() => resolveBaseUrl({
|
|
129
|
+
schemaSource: currentSchema?.baseUrl,
|
|
130
|
+
config: configBaseUrl,
|
|
131
|
+
fromServers: currentOpenApiSchema?.servers?.[0]?.url
|
|
132
|
+
}),
|
|
133
|
+
[currentSchema?.baseUrl, configBaseUrl, currentOpenApiSchema]
|
|
134
|
+
);
|
|
135
|
+
const endpoints = useMemo(
|
|
136
|
+
() => dereferencedSchema ? extractEndpoints(dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema ?? void 0) : [],
|
|
137
|
+
[dereferencedSchema, resolvedBaseUrl, currentSchemaId, currentOpenApiSchema]
|
|
138
|
+
);
|
|
139
|
+
const categories = useMemo(() => getCategories(endpoints), [endpoints]);
|
|
140
|
+
const schemaInfo = useMemo(() => {
|
|
141
|
+
if (!currentOpenApiSchema?.info) return null;
|
|
142
|
+
const { title, version, description } = currentOpenApiSchema.info;
|
|
143
|
+
return {
|
|
144
|
+
title,
|
|
145
|
+
version,
|
|
146
|
+
description,
|
|
147
|
+
servers: currentOpenApiSchema.servers
|
|
148
|
+
};
|
|
149
|
+
}, [currentOpenApiSchema]);
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (preloadAll) return;
|
|
152
|
+
if (!currentSchema) return;
|
|
153
|
+
if (loadedSchemas.has(currentSchema.id)) {
|
|
154
|
+
setLoading(false);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
setLoading(true);
|
|
158
|
+
setError(null);
|
|
159
|
+
fetchSchema(currentSchema.url).then((schema) => {
|
|
160
|
+
setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
|
|
161
|
+
consola.success(`Schema loaded: ${currentSchema.name}`);
|
|
162
|
+
setLoading(false);
|
|
163
|
+
}).catch((err) => {
|
|
164
|
+
consola.error(`Error loading schema from ${currentSchema.url}:`, err);
|
|
165
|
+
setError(err instanceof Error ? err.message : "Failed to load schema");
|
|
166
|
+
setLoading(false);
|
|
167
|
+
});
|
|
168
|
+
}, [currentSchema, loadedSchemas, preloadAll]);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (!preloadAll) return;
|
|
171
|
+
if (schemas.length === 0) {
|
|
172
|
+
setLoading(false);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
let cancelled = false;
|
|
176
|
+
const pending = schemas.filter((s) => !loadedSchemas.has(s.id));
|
|
177
|
+
if (pending.length === 0) {
|
|
178
|
+
setLoading(false);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
setLoading(true);
|
|
182
|
+
setLoadStates((prev) => {
|
|
183
|
+
const next = new Map(prev);
|
|
184
|
+
for (const s of pending) next.set(s.id, { loading: true, error: null });
|
|
185
|
+
return next;
|
|
186
|
+
});
|
|
187
|
+
Promise.allSettled(
|
|
188
|
+
pending.map(
|
|
189
|
+
(s) => fetchSchema(s.url).then((schema) => ({ id: s.id, name: s.name, schema }))
|
|
190
|
+
)
|
|
191
|
+
).then((results) => {
|
|
192
|
+
if (cancelled) return;
|
|
193
|
+
setLoadedSchemas((prev) => {
|
|
194
|
+
const next = new Map(prev);
|
|
195
|
+
for (const r of results) {
|
|
196
|
+
if (r.status === "fulfilled") {
|
|
197
|
+
next.set(r.value.id, r.value.schema);
|
|
198
|
+
consola.success(`Schema loaded: ${r.value.name}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return next;
|
|
202
|
+
});
|
|
203
|
+
setLoadStates((prev) => {
|
|
204
|
+
const next = new Map(prev);
|
|
205
|
+
results.forEach((r, i) => {
|
|
206
|
+
const src = pending[i];
|
|
207
|
+
if (r.status === "fulfilled") {
|
|
208
|
+
next.set(src.id, { loading: false, error: null });
|
|
209
|
+
} else {
|
|
210
|
+
const msg = r.reason instanceof Error ? r.reason.message : "Failed to load schema";
|
|
211
|
+
consola.error(`Error loading schema from ${src.url}:`, r.reason);
|
|
212
|
+
next.set(src.id, { loading: false, error: msg });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
return next;
|
|
216
|
+
});
|
|
217
|
+
setLoading(false);
|
|
218
|
+
});
|
|
219
|
+
return () => {
|
|
220
|
+
cancelled = true;
|
|
221
|
+
};
|
|
222
|
+
}, [preloadAll, schemas, loadedSchemas]);
|
|
223
|
+
const schemasData = useMemo(() => {
|
|
224
|
+
if (!preloadAll) return [];
|
|
225
|
+
return schemas.map((src) => {
|
|
226
|
+
const raw = loadedSchemas.get(src.id) ?? null;
|
|
227
|
+
const deref = raw ? dereferenceSchema(raw) : null;
|
|
228
|
+
const resolved = resolveBaseUrl({
|
|
229
|
+
schemaSource: src.baseUrl,
|
|
230
|
+
config: configBaseUrl,
|
|
231
|
+
fromServers: raw?.servers?.[0]?.url
|
|
232
|
+
});
|
|
233
|
+
const info = raw?.info ? {
|
|
234
|
+
title: raw.info.title,
|
|
235
|
+
version: raw.info.version,
|
|
236
|
+
description: raw.info.description,
|
|
237
|
+
servers: raw.servers
|
|
238
|
+
} : null;
|
|
239
|
+
const eps = deref ? extractEndpoints(deref, resolved, src.id, raw ?? void 0) : [];
|
|
240
|
+
const state = loadStates.get(src.id) ?? { loading: !raw, error: null };
|
|
241
|
+
return {
|
|
242
|
+
source: src,
|
|
243
|
+
info,
|
|
244
|
+
rawSchema: raw,
|
|
245
|
+
endpoints: eps,
|
|
246
|
+
resolvedBaseUrl: resolved || void 0,
|
|
247
|
+
loading: state.loading,
|
|
248
|
+
error: state.error
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
}, [preloadAll, schemas, loadedSchemas, loadStates, configBaseUrl]);
|
|
252
|
+
const setCurrentSchema = useCallback((schemaId) => {
|
|
253
|
+
setCurrentSchemaId(schemaId);
|
|
254
|
+
}, []);
|
|
255
|
+
const refresh = useCallback(() => {
|
|
256
|
+
if (!currentSchema) return;
|
|
257
|
+
setLoading(true);
|
|
258
|
+
setError(null);
|
|
259
|
+
setLoadedSchemas((prev) => {
|
|
260
|
+
const next = new Map(prev);
|
|
261
|
+
next.delete(currentSchema.id);
|
|
262
|
+
return next;
|
|
263
|
+
});
|
|
264
|
+
fetchSchema(currentSchema.url).then((schema) => {
|
|
265
|
+
setLoadedSchemas((prev) => new Map(prev).set(currentSchema.id, schema));
|
|
266
|
+
consola.success(`Schema refreshed: ${currentSchema.name}`);
|
|
267
|
+
setLoading(false);
|
|
268
|
+
}).catch((err) => {
|
|
269
|
+
consola.error(`Error refreshing schema from ${currentSchema.url}:`, err);
|
|
270
|
+
setError(err instanceof Error ? err.message : "Failed to refresh schema");
|
|
271
|
+
setLoading(false);
|
|
272
|
+
});
|
|
273
|
+
}, [currentSchema]);
|
|
274
|
+
return {
|
|
275
|
+
loading,
|
|
276
|
+
error,
|
|
277
|
+
endpoints,
|
|
278
|
+
categories,
|
|
279
|
+
schemas,
|
|
280
|
+
currentSchema,
|
|
281
|
+
schemaInfo,
|
|
282
|
+
rawSchema: currentOpenApiSchema ?? null,
|
|
283
|
+
// Consumers expect ``undefined`` when no base URL was resolved (for
|
|
284
|
+
// conditional ``{ baseUrl?: … }`` plumbing). Turn the empty-string
|
|
285
|
+
// convention from the resolver into undefined at the API boundary.
|
|
286
|
+
resolvedBaseUrl: resolvedBaseUrl || void 0,
|
|
287
|
+
setCurrentSchema,
|
|
288
|
+
refresh,
|
|
289
|
+
schemasData
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
__name(useOpenApiSchema, "useOpenApiSchema");
|
|
293
|
+
function parseDocsHash(hash) {
|
|
294
|
+
const raw = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
295
|
+
if (!raw) return { schemaId: null, anchor: null };
|
|
296
|
+
const [schemaId = null, ...rest] = raw.split("/");
|
|
297
|
+
const anchor = rest.length > 0 ? rest.join("/") : null;
|
|
298
|
+
return {
|
|
299
|
+
schemaId: schemaId || null,
|
|
300
|
+
anchor: anchor || null
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
__name(parseDocsHash, "parseDocsHash");
|
|
304
|
+
function buildDocsHash(schemaId, anchor) {
|
|
305
|
+
if (!schemaId && !anchor) return "";
|
|
306
|
+
if (schemaId && anchor) return `#${schemaId}/${anchor}`;
|
|
307
|
+
if (schemaId) return `#${schemaId}`;
|
|
308
|
+
return anchor ? `#${anchor}` : "";
|
|
309
|
+
}
|
|
310
|
+
__name(buildDocsHash, "buildDocsHash");
|
|
311
|
+
function useDocsUrlSync({
|
|
312
|
+
enabled,
|
|
313
|
+
currentSchemaId,
|
|
314
|
+
activeAnchor,
|
|
315
|
+
onHashTarget
|
|
316
|
+
}) {
|
|
317
|
+
const primedRef = useRef(false);
|
|
318
|
+
const onHashTargetRef = useRef(onHashTarget);
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
onHashTargetRef.current = onHashTarget;
|
|
321
|
+
}, [onHashTarget]);
|
|
322
|
+
useEffect(() => {
|
|
323
|
+
if (!enabled || typeof window === "undefined") return;
|
|
324
|
+
const apply = /* @__PURE__ */ __name(() => {
|
|
325
|
+
onHashTargetRef.current(parseDocsHash(window.location.hash));
|
|
326
|
+
}, "apply");
|
|
327
|
+
apply();
|
|
328
|
+
primedRef.current = true;
|
|
329
|
+
window.addEventListener("hashchange", apply);
|
|
330
|
+
window.addEventListener("popstate", apply);
|
|
331
|
+
return () => {
|
|
332
|
+
window.removeEventListener("hashchange", apply);
|
|
333
|
+
window.removeEventListener("popstate", apply);
|
|
334
|
+
};
|
|
335
|
+
}, [enabled]);
|
|
336
|
+
useEffect(() => {
|
|
337
|
+
if (!enabled || typeof window === "undefined") return;
|
|
338
|
+
if (!primedRef.current) return;
|
|
339
|
+
const next = buildDocsHash(currentSchemaId, activeAnchor);
|
|
340
|
+
const current = window.location.hash;
|
|
341
|
+
if (next === current) return;
|
|
342
|
+
const url = next ? `${window.location.pathname}${window.location.search}${next}` : `${window.location.pathname}${window.location.search}`;
|
|
343
|
+
window.history.replaceState(window.history.state, "", url);
|
|
344
|
+
}, [enabled, currentSchemaId, activeAnchor]);
|
|
345
|
+
const pushTarget = useCallback(
|
|
346
|
+
(schemaId, anchor) => {
|
|
347
|
+
if (!enabled || typeof window === "undefined") return;
|
|
348
|
+
const next = buildDocsHash(schemaId, anchor);
|
|
349
|
+
const url = next ? `${window.location.pathname}${window.location.search}${next}` : `${window.location.pathname}${window.location.search}`;
|
|
350
|
+
window.history.pushState(window.history.state, "", url);
|
|
351
|
+
},
|
|
352
|
+
[enabled]
|
|
353
|
+
);
|
|
354
|
+
return { pushTarget };
|
|
355
|
+
}
|
|
356
|
+
__name(useDocsUrlSync, "useDocsUrlSync");
|
|
357
|
+
var EMPTY_DRAFT = { parameters: {}, requestBody: "" };
|
|
358
|
+
function storageKey(schemaId, ep) {
|
|
359
|
+
if (!schemaId || !ep) return null;
|
|
360
|
+
return `openapi-playground:draft:${schemaId}:${ep.method}:${ep.path}`;
|
|
361
|
+
}
|
|
362
|
+
__name(storageKey, "storageKey");
|
|
363
|
+
function readDraft(key) {
|
|
364
|
+
if (!key || typeof window === "undefined") return EMPTY_DRAFT;
|
|
365
|
+
try {
|
|
366
|
+
const raw = window.localStorage.getItem(key);
|
|
367
|
+
if (!raw) return EMPTY_DRAFT;
|
|
368
|
+
const parsed = JSON.parse(raw);
|
|
369
|
+
return {
|
|
370
|
+
parameters: parsed?.parameters ?? {},
|
|
371
|
+
requestBody: typeof parsed?.requestBody === "string" ? parsed.requestBody : ""
|
|
372
|
+
};
|
|
373
|
+
} catch {
|
|
374
|
+
return EMPTY_DRAFT;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
__name(readDraft, "readDraft");
|
|
378
|
+
function writeDraft(key, value) {
|
|
379
|
+
if (!key || typeof window === "undefined") return;
|
|
380
|
+
try {
|
|
381
|
+
if (Object.keys(value.parameters).length === 0 && !value.requestBody) {
|
|
382
|
+
window.localStorage.removeItem(key);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
386
|
+
} catch {
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
__name(writeDraft, "writeDraft");
|
|
390
|
+
function useEndpointDraft(schemaId, endpoint) {
|
|
391
|
+
const key = storageKey(schemaId, endpoint);
|
|
392
|
+
const [draft, setDraftSnapshot] = useState(() => readDraft(key));
|
|
393
|
+
const loadedKeyRef = useRef(key);
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
if (loadedKeyRef.current === key) return;
|
|
396
|
+
loadedKeyRef.current = key;
|
|
397
|
+
setDraftSnapshot(readDraft(key));
|
|
398
|
+
}, [key]);
|
|
399
|
+
const keyRef = useRef(key);
|
|
400
|
+
useEffect(() => {
|
|
401
|
+
keyRef.current = key;
|
|
402
|
+
}, [key]);
|
|
403
|
+
const latestRef = useRef(draft);
|
|
404
|
+
useEffect(() => {
|
|
405
|
+
latestRef.current = draft;
|
|
406
|
+
}, [draft]);
|
|
407
|
+
const setParameters = useCallback((params) => {
|
|
408
|
+
const next = {
|
|
409
|
+
parameters: params,
|
|
410
|
+
requestBody: latestRef.current.requestBody
|
|
411
|
+
};
|
|
412
|
+
latestRef.current = next;
|
|
413
|
+
writeDraft(keyRef.current, next);
|
|
414
|
+
}, []);
|
|
415
|
+
const setRequestBody = useCallback((body) => {
|
|
416
|
+
const next = {
|
|
417
|
+
parameters: latestRef.current.parameters,
|
|
418
|
+
requestBody: body
|
|
419
|
+
};
|
|
420
|
+
latestRef.current = next;
|
|
421
|
+
writeDraft(keyRef.current, next);
|
|
422
|
+
}, []);
|
|
423
|
+
const reset = useCallback(() => {
|
|
424
|
+
latestRef.current = EMPTY_DRAFT;
|
|
425
|
+
if (keyRef.current && typeof window !== "undefined") {
|
|
426
|
+
try {
|
|
427
|
+
window.localStorage.removeItem(keyRef.current);
|
|
428
|
+
} catch {
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
setDraftSnapshot(EMPTY_DRAFT);
|
|
432
|
+
}, []);
|
|
433
|
+
return { draft, setParameters, setRequestBody, reset };
|
|
434
|
+
}
|
|
435
|
+
__name(useEndpointDraft, "useEndpointDraft");
|
|
436
|
+
|
|
437
|
+
// src/tools/OpenapiViewer/components/shared/EndpointDraftSync.tsx
|
|
438
|
+
function EndpointDraftSync({ schemaId }) {
|
|
439
|
+
const { state, setParameters, setRequestBody, setActiveSchemaId } = usePlaygroundContext();
|
|
440
|
+
const ep = state.selectedEndpoint;
|
|
441
|
+
useEffect(() => {
|
|
442
|
+
setActiveSchemaId(schemaId);
|
|
443
|
+
}, [schemaId, setActiveSchemaId]);
|
|
444
|
+
const { draft, setParameters: persistParams, setRequestBody: persistBody } = useEndpointDraft(schemaId, ep);
|
|
445
|
+
const lastLoadedKeyRef = useRef(null);
|
|
446
|
+
const lastPersistedParamsRef = useRef("");
|
|
447
|
+
const lastPersistedBodyRef = useRef("");
|
|
448
|
+
const currentKey = ep ? `${ep.method}|${ep.path}` : null;
|
|
449
|
+
useEffect(() => {
|
|
450
|
+
if (!ep || !currentKey) {
|
|
451
|
+
lastLoadedKeyRef.current = null;
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (lastLoadedKeyRef.current === currentKey) return;
|
|
455
|
+
lastLoadedKeyRef.current = currentKey;
|
|
456
|
+
const hasStoredParams = draft.parameters && Object.keys(draft.parameters).length > 0;
|
|
457
|
+
const hasStoredBody = typeof draft.requestBody === "string" && draft.requestBody !== "";
|
|
458
|
+
if (hasStoredParams) {
|
|
459
|
+
setParameters(draft.parameters);
|
|
460
|
+
lastPersistedParamsRef.current = JSON.stringify(draft.parameters);
|
|
461
|
+
} else {
|
|
462
|
+
lastPersistedParamsRef.current = JSON.stringify(state.parameters);
|
|
463
|
+
}
|
|
464
|
+
if (hasStoredBody) {
|
|
465
|
+
setRequestBody(draft.requestBody);
|
|
466
|
+
lastPersistedBodyRef.current = draft.requestBody;
|
|
467
|
+
} else {
|
|
468
|
+
lastPersistedBodyRef.current = state.requestBody;
|
|
469
|
+
}
|
|
470
|
+
}, [currentKey]);
|
|
471
|
+
useEffect(() => {
|
|
472
|
+
if (!ep || lastLoadedKeyRef.current !== currentKey) return;
|
|
473
|
+
const serialised = JSON.stringify(state.parameters);
|
|
474
|
+
if (serialised === lastPersistedParamsRef.current) return;
|
|
475
|
+
lastPersistedParamsRef.current = serialised;
|
|
476
|
+
persistParams(state.parameters);
|
|
477
|
+
}, [state.parameters, ep, currentKey, persistParams]);
|
|
478
|
+
useEffect(() => {
|
|
479
|
+
if (!ep || lastLoadedKeyRef.current !== currentKey) return;
|
|
480
|
+
if (state.requestBody === lastPersistedBodyRef.current) return;
|
|
481
|
+
lastPersistedBodyRef.current = state.requestBody;
|
|
482
|
+
persistBody(state.requestBody);
|
|
483
|
+
}, [state.requestBody, ep, currentKey, persistBody]);
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
486
|
+
__name(EndpointDraftSync, "EndpointDraftSync");
|
|
487
|
+
|
|
488
|
+
// src/tools/OpenapiViewer/components/DocsLayout/anchor.ts
|
|
489
|
+
function endpointAnchor(ep, schemaId) {
|
|
490
|
+
const slug = ep.path.replace(/^https?:\/\/[^/]+/, "").replace(/[{}]/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
|
|
491
|
+
const schemaSlug = schemaId ? `${slugifySchemaId(schemaId)}-` : "";
|
|
492
|
+
return `ep-${schemaSlug}${ep.method.toLowerCase()}-${slug}`;
|
|
493
|
+
}
|
|
494
|
+
__name(endpointAnchor, "endpointAnchor");
|
|
495
|
+
function slugifySchemaId(id) {
|
|
496
|
+
return id.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
497
|
+
}
|
|
498
|
+
__name(slugifySchemaId, "slugifySchemaId");
|
|
499
|
+
var METHOD_STYLES = {
|
|
500
|
+
GET: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25",
|
|
501
|
+
POST: "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25",
|
|
502
|
+
PUT: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25",
|
|
503
|
+
PATCH: "bg-orange-500/10 text-orange-600 dark:text-orange-400 border-orange-500/25",
|
|
504
|
+
DELETE: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25"
|
|
505
|
+
};
|
|
506
|
+
var METHOD_FALLBACK = "bg-muted text-muted-foreground border-border";
|
|
507
|
+
function getMethodStyle(method) {
|
|
508
|
+
return METHOD_STYLES[method.toUpperCase()] ?? METHOD_FALLBACK;
|
|
509
|
+
}
|
|
510
|
+
__name(getMethodStyle, "getMethodStyle");
|
|
511
|
+
function getStatusStyle(status) {
|
|
512
|
+
if (status >= 500) return "bg-red-500/10 text-red-500 dark:text-red-400 border-red-500/25";
|
|
513
|
+
if (status >= 400) return "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25";
|
|
514
|
+
if (status >= 300) return "bg-blue-500/10 text-blue-600 dark:text-blue-400 border-blue-500/25";
|
|
515
|
+
return "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/25";
|
|
516
|
+
}
|
|
517
|
+
__name(getStatusStyle, "getStatusStyle");
|
|
518
|
+
function MethodBadge({ method }) {
|
|
519
|
+
return /* @__PURE__ */ jsx("span", { className: cn(
|
|
520
|
+
"inline-flex shrink-0 items-center rounded border px-1.5 py-px",
|
|
521
|
+
"font-mono text-[10px] font-bold uppercase tracking-wider leading-none",
|
|
522
|
+
getMethodStyle(method)
|
|
523
|
+
), children: method });
|
|
524
|
+
}
|
|
525
|
+
__name(MethodBadge, "MethodBadge");
|
|
526
|
+
function StatusBadge({ status }) {
|
|
527
|
+
return /* @__PURE__ */ jsx("span", { className: cn(
|
|
528
|
+
"inline-flex items-center rounded border px-1.5 py-px",
|
|
529
|
+
"font-mono text-[11px] font-bold leading-none",
|
|
530
|
+
getStatusStyle(status)
|
|
531
|
+
), children: status });
|
|
532
|
+
}
|
|
533
|
+
__name(StatusBadge, "StatusBadge");
|
|
534
|
+
function SectionLabel({ children }) {
|
|
535
|
+
return /* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60 select-none", children });
|
|
536
|
+
}
|
|
537
|
+
__name(SectionLabel, "SectionLabel");
|
|
538
|
+
function Panel({ children, className }) {
|
|
539
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col min-h-0 overflow-hidden", className), children });
|
|
540
|
+
}
|
|
541
|
+
__name(Panel, "Panel");
|
|
542
|
+
function ScrollArea({ children, className }) {
|
|
543
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex-1 overflow-y-auto min-h-0", className), children });
|
|
544
|
+
}
|
|
545
|
+
__name(ScrollArea, "ScrollArea");
|
|
546
|
+
function EmptyState({
|
|
547
|
+
icon: Icon,
|
|
548
|
+
text,
|
|
549
|
+
className
|
|
550
|
+
}) {
|
|
551
|
+
return /* @__PURE__ */ jsxs(
|
|
552
|
+
"div",
|
|
553
|
+
{
|
|
554
|
+
className: cn(
|
|
555
|
+
"flex flex-col items-center justify-center h-full gap-3 px-6 text-center",
|
|
556
|
+
className
|
|
557
|
+
),
|
|
558
|
+
children: [
|
|
559
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-7 w-7 text-muted-foreground/25" }),
|
|
560
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: text })
|
|
561
|
+
]
|
|
562
|
+
}
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
__name(EmptyState, "EmptyState");
|
|
566
|
+
function CollapsibleSection({
|
|
567
|
+
label,
|
|
568
|
+
action,
|
|
569
|
+
children,
|
|
570
|
+
defaultOpen = false
|
|
571
|
+
}) {
|
|
572
|
+
const [open, setOpen] = React12.useState(defaultOpen);
|
|
573
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-0", children: [
|
|
574
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
575
|
+
/* @__PURE__ */ jsxs(
|
|
576
|
+
"button",
|
|
577
|
+
{
|
|
578
|
+
type: "button",
|
|
579
|
+
onClick: () => setOpen((v) => !v),
|
|
580
|
+
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",
|
|
581
|
+
children: [
|
|
582
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3 w-3 transition-transform", open && "rotate-90") }),
|
|
583
|
+
label
|
|
584
|
+
]
|
|
585
|
+
}
|
|
586
|
+
),
|
|
587
|
+
action && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: action })
|
|
588
|
+
] }),
|
|
589
|
+
open && /* @__PURE__ */ jsx("div", { children })
|
|
590
|
+
] });
|
|
591
|
+
}
|
|
592
|
+
__name(CollapsibleSection, "CollapsibleSection");
|
|
593
|
+
var FLAVOUR_LABELS = {
|
|
594
|
+
markdown: {
|
|
595
|
+
title: "Markdown for LLM",
|
|
596
|
+
hint: "Endpoints + params as prose. Smallest."
|
|
597
|
+
},
|
|
598
|
+
compact: {
|
|
599
|
+
title: "Compact JSON",
|
|
600
|
+
hint: "Dereferenced, no examples, minified."
|
|
601
|
+
},
|
|
602
|
+
raw: {
|
|
603
|
+
title: "Raw JSON",
|
|
604
|
+
hint: "Full OpenAPI document with $refs."
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
function SchemaCopyMenu({ schema, endpoints, baseUrl, variant = "button" }) {
|
|
608
|
+
const [sizeCache, setSizeCache] = useState({});
|
|
609
|
+
const [justCopied, setJustCopied] = useState(null);
|
|
610
|
+
const [open, setOpen] = useState(false);
|
|
611
|
+
const isReady = schema !== null && endpoints.length > 0;
|
|
612
|
+
const build = useCallback(
|
|
613
|
+
(flavour) => {
|
|
614
|
+
if (!schema) return "";
|
|
615
|
+
if (flavour === "markdown") return toMarkdown(schema, endpoints, baseUrl);
|
|
616
|
+
if (flavour === "compact") return toCompactJson(schema, baseUrl);
|
|
617
|
+
return toRawJson(schema, baseUrl);
|
|
618
|
+
},
|
|
619
|
+
[schema, endpoints, baseUrl]
|
|
620
|
+
);
|
|
621
|
+
const handleCopy = useCallback(
|
|
622
|
+
async (flavour) => {
|
|
623
|
+
if (!isReady) return;
|
|
624
|
+
const text = build(flavour);
|
|
625
|
+
const label = FLAVOUR_LABELS[flavour].title;
|
|
626
|
+
try {
|
|
627
|
+
await navigator.clipboard.writeText(text);
|
|
628
|
+
const size = formatBytes(text);
|
|
629
|
+
setSizeCache((prev) => ({ ...prev, [flavour]: size }));
|
|
630
|
+
setJustCopied(flavour);
|
|
631
|
+
setTimeout(() => setJustCopied(null), 1500);
|
|
632
|
+
setOpen(false);
|
|
633
|
+
toast.success(`Copied ${label}`, { description: size });
|
|
634
|
+
} catch (err) {
|
|
635
|
+
const message = err instanceof Error ? err.message : "Clipboard permission denied";
|
|
636
|
+
toast.error("Copy failed", { description: message });
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
[build, isReady]
|
|
640
|
+
);
|
|
641
|
+
const flavours = useMemo(() => ["markdown", "compact", "raw"], []);
|
|
642
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { open, onOpenChange: setOpen, children: [
|
|
643
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: variant === "icon" ? /* @__PURE__ */ jsx(
|
|
644
|
+
Button,
|
|
645
|
+
{
|
|
646
|
+
variant: "ghost",
|
|
647
|
+
size: "icon",
|
|
648
|
+
className: "h-7 w-7 shrink-0",
|
|
649
|
+
disabled: !isReady,
|
|
650
|
+
title: "Copy schema for AI",
|
|
651
|
+
"aria-label": "Copy schema for AI",
|
|
652
|
+
children: /* @__PURE__ */ jsx(Sparkles, { className: "h-3.5 w-3.5" })
|
|
653
|
+
}
|
|
654
|
+
) : /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", className: "h-8 gap-1.5 text-xs", disabled: !isReady, children: [
|
|
655
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
|
|
656
|
+
"Copy for AI",
|
|
657
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 opacity-60" })
|
|
658
|
+
] }) }),
|
|
659
|
+
/* @__PURE__ */ jsxs(
|
|
660
|
+
DropdownMenuContent,
|
|
661
|
+
{
|
|
662
|
+
side: "right",
|
|
663
|
+
align: "start",
|
|
664
|
+
sideOffset: 6,
|
|
665
|
+
collisionPadding: 8,
|
|
666
|
+
className: "w-60 max-w-[calc(100vw-16px)]",
|
|
667
|
+
children: [
|
|
668
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-[10px] uppercase tracking-wider text-muted-foreground/70", children: "Copy schema" }),
|
|
669
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
670
|
+
flavours.map((f) => {
|
|
671
|
+
const label = FLAVOUR_LABELS[f];
|
|
672
|
+
const size = sizeCache[f];
|
|
673
|
+
const isDone = justCopied === f;
|
|
674
|
+
return /* @__PURE__ */ jsxs(
|
|
675
|
+
DropdownMenuItem,
|
|
676
|
+
{
|
|
677
|
+
onClick: (e) => {
|
|
678
|
+
e.preventDefault();
|
|
679
|
+
void handleCopy(f);
|
|
680
|
+
},
|
|
681
|
+
className: "flex flex-col items-start gap-0.5 py-2 cursor-pointer",
|
|
682
|
+
children: [
|
|
683
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
|
|
684
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium flex-1", children: label.title }),
|
|
685
|
+
isDone ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] text-emerald-500", children: [
|
|
686
|
+
/* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
|
|
687
|
+
" Copied"
|
|
688
|
+
] }) : size ? /* @__PURE__ */ jsx("span", { className: "text-[10px] font-mono text-muted-foreground/70 tabular-nums", children: size }) : null
|
|
689
|
+
] }),
|
|
690
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/70 leading-snug line-clamp-1", children: label.hint })
|
|
691
|
+
]
|
|
692
|
+
},
|
|
693
|
+
f
|
|
694
|
+
);
|
|
695
|
+
})
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
)
|
|
699
|
+
] });
|
|
700
|
+
}
|
|
701
|
+
__name(SchemaCopyMenu, "SchemaCopyMenu");
|
|
702
|
+
function BrandHeader({ info, endpoints, rawSchema, resolvedBaseUrl }) {
|
|
703
|
+
const apiTitle = info?.title ?? "API Reference";
|
|
704
|
+
const copyReady = rawSchema !== null && rawSchema !== void 0 && endpoints.length > 0;
|
|
705
|
+
return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 py-2.5 flex items-start gap-2", children: [
|
|
706
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
707
|
+
/* @__PURE__ */ jsx(
|
|
708
|
+
"div",
|
|
709
|
+
{
|
|
710
|
+
className: "text-[13px] font-semibold text-foreground leading-tight truncate",
|
|
711
|
+
title: apiTitle,
|
|
712
|
+
children: apiTitle
|
|
713
|
+
}
|
|
714
|
+
),
|
|
715
|
+
info?.version && /* @__PURE__ */ jsxs("div", { className: "font-mono text-[10px] text-muted-foreground/60 leading-tight mt-0.5", children: [
|
|
716
|
+
"v",
|
|
717
|
+
info.version
|
|
718
|
+
] })
|
|
719
|
+
] }),
|
|
720
|
+
copyReady && /* @__PURE__ */ jsx(
|
|
721
|
+
SchemaCopyMenu,
|
|
722
|
+
{
|
|
723
|
+
schema: rawSchema ?? null,
|
|
724
|
+
endpoints,
|
|
725
|
+
baseUrl: resolvedBaseUrl,
|
|
726
|
+
variant: "icon"
|
|
727
|
+
}
|
|
728
|
+
)
|
|
729
|
+
] });
|
|
730
|
+
}
|
|
731
|
+
__name(BrandHeader, "BrandHeader");
|
|
732
|
+
|
|
733
|
+
// src/tools/OpenapiViewer/components/DocsLayout/sidebarLabel.ts
|
|
734
|
+
function longestCommonPrefix(paths) {
|
|
735
|
+
if (paths.length === 0) return "";
|
|
736
|
+
if (paths.length === 1) return "";
|
|
737
|
+
const segments = paths.map((p) => p.split("/"));
|
|
738
|
+
const minLen = Math.min(...segments.map((s) => s.length));
|
|
739
|
+
const shared = [];
|
|
740
|
+
for (let i = 0; i < minLen; i++) {
|
|
741
|
+
const first = segments[0][i];
|
|
742
|
+
if (segments.every((s) => s[i] === first)) {
|
|
743
|
+
shared.push(first);
|
|
744
|
+
} else {
|
|
745
|
+
break;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const joined = shared.join("/");
|
|
749
|
+
return joined;
|
|
750
|
+
}
|
|
751
|
+
__name(longestCommonPrefix, "longestCommonPrefix");
|
|
752
|
+
function sidebarLabel(ep, groupCommonPrefix) {
|
|
753
|
+
if (ep.summary) return ep.summary;
|
|
754
|
+
if (groupCommonPrefix && ep.path.startsWith(groupCommonPrefix)) {
|
|
755
|
+
const tail = ep.path.slice(groupCommonPrefix.length) || "/";
|
|
756
|
+
return tail;
|
|
757
|
+
}
|
|
758
|
+
return relativePath2(ep.path);
|
|
759
|
+
}
|
|
760
|
+
__name(sidebarLabel, "sidebarLabel");
|
|
761
|
+
function relativePath2(full) {
|
|
762
|
+
try {
|
|
763
|
+
return new URL(full).pathname;
|
|
764
|
+
} catch {
|
|
765
|
+
return full;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
__name(relativePath2, "relativePath");
|
|
769
|
+
function sidebarTooltip(ep) {
|
|
770
|
+
return `${ep.method} ${relativePath2(ep.path)}`;
|
|
771
|
+
}
|
|
772
|
+
__name(sidebarTooltip, "sidebarTooltip");
|
|
773
|
+
|
|
774
|
+
// src/tools/OpenapiViewer/components/DocsLayout/grouping.ts
|
|
775
|
+
var METHOD_ORDER = {
|
|
776
|
+
GET: 0,
|
|
777
|
+
POST: 1,
|
|
778
|
+
PUT: 2,
|
|
779
|
+
PATCH: 3,
|
|
780
|
+
DELETE: 4
|
|
781
|
+
};
|
|
782
|
+
var methodRank = /* @__PURE__ */ __name((ep) => METHOD_ORDER[ep.method] ?? 99, "methodRank");
|
|
783
|
+
function groupEndpoints(list) {
|
|
784
|
+
const byCategory = groupBy(list, "category");
|
|
785
|
+
const all = Object.entries(byCategory).map(([category, endpoints]) => ({
|
|
786
|
+
category,
|
|
787
|
+
endpoints: orderBy(endpoints, ["path", methodRank], ["asc", "asc"]),
|
|
788
|
+
commonPrefix: longestCommonPrefix(endpoints.map((e) => e.path))
|
|
789
|
+
}));
|
|
790
|
+
const [other, named] = partition(all, (g) => g.category === "Other");
|
|
791
|
+
return [...sortBy(named, (g) => g.category.toLowerCase()), ...other];
|
|
792
|
+
}
|
|
793
|
+
__name(groupEndpoints, "groupEndpoints");
|
|
794
|
+
function buildSchemaSections(sources, endpointsBySchema) {
|
|
795
|
+
return sources.map((source) => ({
|
|
796
|
+
source,
|
|
797
|
+
groups: groupEndpoints(endpointsBySchema[source.id] ?? [])
|
|
798
|
+
}));
|
|
799
|
+
}
|
|
800
|
+
__name(buildSchemaSections, "buildSchemaSections");
|
|
801
|
+
|
|
802
|
+
// src/tools/OpenapiViewer/components/DocsLayout/Sidebar/buildVM.ts
|
|
803
|
+
function filterEndpoints(list, query, method) {
|
|
804
|
+
let out = list;
|
|
805
|
+
if (method !== "ALL") {
|
|
806
|
+
out = out.filter((e) => e.method === method);
|
|
807
|
+
}
|
|
808
|
+
if (query) {
|
|
809
|
+
const q = query.toLowerCase();
|
|
810
|
+
out = out.filter(
|
|
811
|
+
(e) => e.summary.toLowerCase().includes(q) || e.name.toLowerCase().includes(q) || e.description.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
return out;
|
|
815
|
+
}
|
|
816
|
+
__name(filterEndpoints, "filterEndpoints");
|
|
817
|
+
function buildCategory(group, activeEndpointId, schemaId, keyPrefix) {
|
|
818
|
+
const rows = group.endpoints.map((ep) => {
|
|
819
|
+
const anchor = endpointAnchor(ep, schemaId ?? ep.schemaId ?? null);
|
|
820
|
+
return {
|
|
821
|
+
key: `${ep.method}-${ep.path}`,
|
|
822
|
+
anchor,
|
|
823
|
+
schemaId: schemaId ?? ep.schemaId ?? null,
|
|
824
|
+
label: sidebarLabel(ep, group.commonPrefix),
|
|
825
|
+
tooltip: sidebarTooltip(ep),
|
|
826
|
+
method: ep.method,
|
|
827
|
+
useMono: !ep.summary,
|
|
828
|
+
isActive: activeEndpointId === anchor
|
|
829
|
+
};
|
|
830
|
+
});
|
|
831
|
+
return {
|
|
832
|
+
key: `${keyPrefix}${group.category}`,
|
|
833
|
+
category: group.category,
|
|
834
|
+
rows
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
__name(buildCategory, "buildCategory");
|
|
838
|
+
function emptyTextFor(query, method, defaultText) {
|
|
839
|
+
if (query && method !== "ALL") return `No ${method} endpoints match "${query}"`;
|
|
840
|
+
if (query) return `No endpoints match "${query}"`;
|
|
841
|
+
if (method !== "ALL") return `No ${method} endpoints`;
|
|
842
|
+
return defaultText;
|
|
843
|
+
}
|
|
844
|
+
__name(emptyTextFor, "emptyTextFor");
|
|
845
|
+
function buildFlatVM(endpoints, selectedVersion, query, method, activeEndpointId) {
|
|
846
|
+
const filtered = filterEndpoints(
|
|
847
|
+
deduplicateEndpoints(endpoints, selectedVersion),
|
|
848
|
+
query,
|
|
849
|
+
method
|
|
850
|
+
);
|
|
851
|
+
const groups = groupEndpoints(filtered);
|
|
852
|
+
return {
|
|
853
|
+
kind: "flat",
|
|
854
|
+
categories: groups.map((g) => buildCategory(g, activeEndpointId, null, "")),
|
|
855
|
+
emptyText: emptyTextFor(query, method, "No endpoints in this schema")
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
__name(buildFlatVM, "buildFlatVM");
|
|
859
|
+
function buildSectionsVM(schemas, endpointsBySchema, selectedVersion, query, method, activeEndpointId) {
|
|
860
|
+
const filteredMap = {};
|
|
861
|
+
for (const src of schemas) {
|
|
862
|
+
const raw = endpointsBySchema[src.id] ?? [];
|
|
863
|
+
filteredMap[src.id] = filterEndpoints(
|
|
864
|
+
deduplicateEndpoints(raw, selectedVersion),
|
|
865
|
+
query,
|
|
866
|
+
method
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
const rawSections = buildSchemaSections(schemas, filteredMap);
|
|
870
|
+
const sections = rawSections.filter((s) => s.groups.length > 0).map((s) => ({
|
|
871
|
+
sourceId: s.source.id,
|
|
872
|
+
sourceName: s.source.name,
|
|
873
|
+
categories: s.groups.map(
|
|
874
|
+
(g) => buildCategory(g, activeEndpointId, s.source.id, `${s.source.id}-`)
|
|
875
|
+
)
|
|
876
|
+
}));
|
|
877
|
+
return {
|
|
878
|
+
kind: "sections",
|
|
879
|
+
sections,
|
|
880
|
+
emptyText: emptyTextFor(query, method, "No endpoints in any schema")
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
__name(buildSectionsVM, "buildSectionsVM");
|
|
884
|
+
var EndpointRow = React12.memo(/* @__PURE__ */ __name(function EndpointRow2({
|
|
885
|
+
row,
|
|
886
|
+
onNavigate
|
|
887
|
+
}) {
|
|
888
|
+
const displayLabel = row.label.replace(/\.$/, "");
|
|
889
|
+
return /* @__PURE__ */ jsxs(Tooltip, { delayDuration: 350, children: [
|
|
890
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
891
|
+
"button",
|
|
892
|
+
{
|
|
893
|
+
onClick: () => onNavigate(row.anchor, row.schemaId),
|
|
894
|
+
"aria-current": row.isActive ? "location" : void 0,
|
|
895
|
+
className: cn(
|
|
896
|
+
"relative w-full text-left grid grid-cols-[52px_minmax(0,1fr)] items-baseline gap-2 pl-3 pr-3 py-1 transition-colors",
|
|
897
|
+
row.isActive ? "bg-primary/10 text-foreground" : "hover:bg-muted/40 text-foreground/75 hover:text-foreground"
|
|
898
|
+
),
|
|
899
|
+
children: [
|
|
900
|
+
row.isActive && /* @__PURE__ */ jsx("span", { className: "absolute left-0 top-1 bottom-1 w-0.5 rounded-r bg-primary" }),
|
|
901
|
+
/* @__PURE__ */ jsx("span", { className: "justify-self-start", children: /* @__PURE__ */ jsx(MethodBadge, { method: row.method }) }),
|
|
902
|
+
/* @__PURE__ */ jsx(
|
|
903
|
+
"span",
|
|
904
|
+
{
|
|
905
|
+
className: cn(
|
|
906
|
+
"line-clamp-2 leading-snug min-w-0",
|
|
907
|
+
row.useMono ? "font-mono text-[11px] break-all" : "text-[12px]",
|
|
908
|
+
row.isActive && "text-foreground font-medium"
|
|
909
|
+
),
|
|
910
|
+
children: displayLabel
|
|
911
|
+
}
|
|
912
|
+
)
|
|
913
|
+
]
|
|
914
|
+
}
|
|
915
|
+
) }),
|
|
916
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "right", align: "center", className: "font-mono text-[11px]", children: row.tooltip })
|
|
917
|
+
] });
|
|
918
|
+
}, "EndpointRow"));
|
|
919
|
+
var CategoryBlock = React12.memo(/* @__PURE__ */ __name(function CategoryBlock2({
|
|
920
|
+
category,
|
|
921
|
+
onNavigate
|
|
922
|
+
}) {
|
|
923
|
+
return /* @__PURE__ */ jsxs("div", { className: "mb-2.5 last:mb-1", children: [
|
|
924
|
+
/* @__PURE__ */ jsx("div", { className: "px-3 pt-3 pb-1 text-[10px] font-semibold uppercase tracking-[0.14em] text-muted-foreground/50 select-none", children: category.category }),
|
|
925
|
+
/* @__PURE__ */ jsx("div", { children: category.rows.map((row) => /* @__PURE__ */ jsx(EndpointRow, { row, onNavigate }, row.key)) })
|
|
926
|
+
] });
|
|
927
|
+
}, "CategoryBlock"));
|
|
928
|
+
function SchemaSection({ section, onNavigate }) {
|
|
929
|
+
return /* @__PURE__ */ jsxs("div", { className: "mb-4 last:mb-2", children: [
|
|
930
|
+
/* @__PURE__ */ jsx("div", { className: "px-3 py-1.5 sticky top-0 z-[1] bg-background/95 backdrop-blur-[2px] border-b border-border/40", children: /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold uppercase tracking-[0.12em] text-foreground/80", children: section.sourceName }) }),
|
|
931
|
+
section.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBlock, { category: cat, onNavigate }, cat.key))
|
|
932
|
+
] });
|
|
933
|
+
}
|
|
934
|
+
__name(SchemaSection, "SchemaSection");
|
|
935
|
+
function SidebarBody({ body, onNavigate }) {
|
|
936
|
+
if (body.kind === "flat") {
|
|
937
|
+
if (body.categories.length === 0) {
|
|
938
|
+
return /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: body.emptyText });
|
|
939
|
+
}
|
|
940
|
+
return /* @__PURE__ */ jsx("nav", { className: "py-1.5", children: body.categories.map((cat) => /* @__PURE__ */ jsx(CategoryBlock, { category: cat, onNavigate }, cat.key)) });
|
|
941
|
+
}
|
|
942
|
+
if (body.sections.length === 0) {
|
|
943
|
+
return /* @__PURE__ */ jsx("div", { className: "py-10 px-4 text-center text-xs text-muted-foreground", children: body.emptyText });
|
|
944
|
+
}
|
|
945
|
+
return /* @__PURE__ */ jsx("nav", { className: "py-1.5", children: body.sections.map((section) => /* @__PURE__ */ jsx(SchemaSection, { section, onNavigate }, section.sourceId)) });
|
|
946
|
+
}
|
|
947
|
+
__name(SidebarBody, "SidebarBody");
|
|
948
|
+
|
|
949
|
+
// src/tools/OpenapiViewer/components/DocsLayout/Sidebar/types.ts
|
|
950
|
+
var METHOD_FILTERS = ["ALL", "GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
951
|
+
function MethodChips({ value, onChange }) {
|
|
952
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 overflow-x-auto -mx-1 px-1 pb-px", children: METHOD_FILTERS.map((m) => {
|
|
953
|
+
const active = value === m;
|
|
954
|
+
return /* @__PURE__ */ jsx(
|
|
955
|
+
"button",
|
|
956
|
+
{
|
|
957
|
+
type: "button",
|
|
958
|
+
onClick: () => onChange(m),
|
|
959
|
+
"aria-pressed": active,
|
|
960
|
+
className: cn(
|
|
961
|
+
"shrink-0 px-2 h-6 rounded font-mono text-[10px] font-semibold tracking-wide uppercase transition-colors",
|
|
962
|
+
active ? "bg-foreground text-background" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted"
|
|
963
|
+
),
|
|
964
|
+
children: m
|
|
965
|
+
},
|
|
966
|
+
m
|
|
967
|
+
);
|
|
968
|
+
}) });
|
|
969
|
+
}
|
|
970
|
+
__name(MethodChips, "MethodChips");
|
|
971
|
+
function SearchInput({ value, onChange, placeholder }) {
|
|
972
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
973
|
+
/* @__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" }),
|
|
974
|
+
/* @__PURE__ */ jsx(
|
|
975
|
+
Input,
|
|
976
|
+
{
|
|
977
|
+
placeholder: placeholder ?? "Search endpoints\u2026",
|
|
978
|
+
value,
|
|
979
|
+
onChange: (e) => onChange(e.target.value),
|
|
980
|
+
className: "pl-8 pr-7 h-8 text-xs"
|
|
981
|
+
}
|
|
982
|
+
),
|
|
983
|
+
value && /* @__PURE__ */ jsx(
|
|
984
|
+
"button",
|
|
985
|
+
{
|
|
986
|
+
type: "button",
|
|
987
|
+
onClick: () => onChange(""),
|
|
988
|
+
"aria-label": "Clear search",
|
|
989
|
+
className: cn(
|
|
990
|
+
"absolute right-1.5 top-1/2 -translate-y-1/2 h-5 w-5 rounded",
|
|
991
|
+
"inline-flex items-center justify-center",
|
|
992
|
+
"text-muted-foreground/50 hover:text-foreground hover:bg-muted transition-colors"
|
|
993
|
+
),
|
|
994
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
|
|
995
|
+
}
|
|
996
|
+
)
|
|
997
|
+
] });
|
|
998
|
+
}
|
|
999
|
+
__name(SearchInput, "SearchInput");
|
|
1000
|
+
function Toolbar({
|
|
1001
|
+
schemas,
|
|
1002
|
+
currentSchemaId,
|
|
1003
|
+
onSchemaChange,
|
|
1004
|
+
showSchemaSelector,
|
|
1005
|
+
search,
|
|
1006
|
+
onSearchChange,
|
|
1007
|
+
methodFilter,
|
|
1008
|
+
onMethodFilterChange
|
|
1009
|
+
}) {
|
|
1010
|
+
const schemaOptions = React12.useMemo(
|
|
1011
|
+
() => schemas.map((s) => ({ value: s.id, label: s.name })),
|
|
1012
|
+
[schemas]
|
|
1013
|
+
);
|
|
1014
|
+
return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-3 py-2.5 space-y-2", children: [
|
|
1015
|
+
showSchemaSelector && /* @__PURE__ */ jsx(
|
|
1016
|
+
Combobox,
|
|
1017
|
+
{
|
|
1018
|
+
options: schemaOptions,
|
|
1019
|
+
value: currentSchemaId ?? "",
|
|
1020
|
+
onValueChange: (id) => id && onSchemaChange(id),
|
|
1021
|
+
placeholder: "Select API",
|
|
1022
|
+
searchPlaceholder: "Search APIs\u2026",
|
|
1023
|
+
emptyText: "No APIs found",
|
|
1024
|
+
className: "w-full h-8 text-xs"
|
|
1025
|
+
}
|
|
1026
|
+
),
|
|
1027
|
+
/* @__PURE__ */ jsx(SearchInput, { value: search, onChange: onSearchChange }),
|
|
1028
|
+
/* @__PURE__ */ jsx(MethodChips, { value: methodFilter, onChange: onMethodFilterChange })
|
|
1029
|
+
] });
|
|
1030
|
+
}
|
|
1031
|
+
__name(Toolbar, "Toolbar");
|
|
1032
|
+
function useDebouncedValue(value, delayMs = 120) {
|
|
1033
|
+
const [debounced, setDebounced] = useState(value);
|
|
1034
|
+
useEffect(() => {
|
|
1035
|
+
const id = setTimeout(() => setDebounced(value), delayMs);
|
|
1036
|
+
return () => clearTimeout(id);
|
|
1037
|
+
}, [value, delayMs]);
|
|
1038
|
+
return debounced;
|
|
1039
|
+
}
|
|
1040
|
+
__name(useDebouncedValue, "useDebouncedValue");
|
|
1041
|
+
function DocsSidebar({
|
|
1042
|
+
info,
|
|
1043
|
+
endpoints,
|
|
1044
|
+
schemas,
|
|
1045
|
+
currentSchemaId,
|
|
1046
|
+
onSchemaChange,
|
|
1047
|
+
activeEndpointId,
|
|
1048
|
+
selectedVersion,
|
|
1049
|
+
onNavigate,
|
|
1050
|
+
grouping = "selector",
|
|
1051
|
+
endpointsBySchema,
|
|
1052
|
+
rawSchema,
|
|
1053
|
+
resolvedBaseUrl
|
|
1054
|
+
}) {
|
|
1055
|
+
const [search, setSearch] = useState("");
|
|
1056
|
+
const [methodFilter, setMethodFilter] = useState("ALL");
|
|
1057
|
+
const debouncedSearch = useDebouncedValue(search);
|
|
1058
|
+
const body = useMemo(() => {
|
|
1059
|
+
if (grouping === "sections") {
|
|
1060
|
+
return buildSectionsVM(
|
|
1061
|
+
schemas,
|
|
1062
|
+
endpointsBySchema ?? {},
|
|
1063
|
+
selectedVersion,
|
|
1064
|
+
debouncedSearch,
|
|
1065
|
+
methodFilter,
|
|
1066
|
+
activeEndpointId
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
return buildFlatVM(
|
|
1070
|
+
endpoints,
|
|
1071
|
+
selectedVersion,
|
|
1072
|
+
debouncedSearch,
|
|
1073
|
+
methodFilter,
|
|
1074
|
+
activeEndpointId
|
|
1075
|
+
);
|
|
1076
|
+
}, [
|
|
1077
|
+
grouping,
|
|
1078
|
+
schemas,
|
|
1079
|
+
endpointsBySchema,
|
|
1080
|
+
endpoints,
|
|
1081
|
+
selectedVersion,
|
|
1082
|
+
debouncedSearch,
|
|
1083
|
+
methodFilter,
|
|
1084
|
+
activeEndpointId
|
|
1085
|
+
]);
|
|
1086
|
+
const hasMultipleSchemas = schemas.length > 1;
|
|
1087
|
+
const showSchemaSelector = grouping === "selector" && hasMultipleSchemas;
|
|
1088
|
+
return /* @__PURE__ */ jsxs("aside", { className: "flex flex-col h-full min-h-0 border-r bg-muted/10", children: [
|
|
1089
|
+
/* @__PURE__ */ jsx(
|
|
1090
|
+
BrandHeader,
|
|
1091
|
+
{
|
|
1092
|
+
info,
|
|
1093
|
+
endpoints,
|
|
1094
|
+
rawSchema,
|
|
1095
|
+
resolvedBaseUrl
|
|
1096
|
+
}
|
|
1097
|
+
),
|
|
1098
|
+
/* @__PURE__ */ jsx(
|
|
1099
|
+
Toolbar,
|
|
1100
|
+
{
|
|
1101
|
+
schemas,
|
|
1102
|
+
currentSchemaId,
|
|
1103
|
+
onSchemaChange,
|
|
1104
|
+
showSchemaSelector,
|
|
1105
|
+
search,
|
|
1106
|
+
onSearchChange: setSearch,
|
|
1107
|
+
methodFilter,
|
|
1108
|
+
onMethodFilterChange: setMethodFilter
|
|
1109
|
+
}
|
|
1110
|
+
),
|
|
1111
|
+
/* @__PURE__ */ jsx(ScrollArea, { children: /* @__PURE__ */ jsx(SidebarBody, { body, onNavigate }) })
|
|
1112
|
+
] });
|
|
1113
|
+
}
|
|
1114
|
+
__name(DocsSidebar, "DocsSidebar");
|
|
1115
|
+
|
|
1116
|
+
// src/tools/OpenapiViewer/utils/scrollParent.ts
|
|
1117
|
+
function getScrollParent(el) {
|
|
1118
|
+
if (typeof window === "undefined") return null;
|
|
1119
|
+
if (!el) return window;
|
|
1120
|
+
let cur = el.parentElement;
|
|
1121
|
+
while (cur && cur !== document.body && cur !== document.documentElement) {
|
|
1122
|
+
const style = getComputedStyle(cur);
|
|
1123
|
+
const overflowY = style.overflowY;
|
|
1124
|
+
const canScroll = (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && cur.scrollHeight > cur.clientHeight;
|
|
1125
|
+
if (canScroll) return cur;
|
|
1126
|
+
cur = cur.parentElement;
|
|
1127
|
+
}
|
|
1128
|
+
return window;
|
|
1129
|
+
}
|
|
1130
|
+
__name(getScrollParent, "getScrollParent");
|
|
1131
|
+
function getScrollTop(target) {
|
|
1132
|
+
return target === window ? window.scrollY : target.scrollTop;
|
|
1133
|
+
}
|
|
1134
|
+
__name(getScrollTop, "getScrollTop");
|
|
1135
|
+
function getViewportHeight(target) {
|
|
1136
|
+
return target === window ? window.innerHeight : target.clientHeight;
|
|
1137
|
+
}
|
|
1138
|
+
__name(getViewportHeight, "getViewportHeight");
|
|
1139
|
+
function getTargetTop(target) {
|
|
1140
|
+
return target === window ? 0 : target.getBoundingClientRect().top;
|
|
1141
|
+
}
|
|
1142
|
+
__name(getTargetTop, "getTargetTop");
|
|
1143
|
+
function scrollTargetTo(target, top) {
|
|
1144
|
+
if (target === window) {
|
|
1145
|
+
window.scrollTo({ top, behavior: "smooth" });
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
target.scrollTop = top;
|
|
1149
|
+
}
|
|
1150
|
+
__name(scrollTargetTo, "scrollTargetTo");
|
|
1151
|
+
function ApiIntroSection({ info, schema, endpoints, resolvedBaseUrl }) {
|
|
1152
|
+
const baseUrlRows = resolvedBaseUrl ? [{ url: resolvedBaseUrl, description: info.servers?.[0]?.description }] : (info.servers ?? []).map((s) => ({ url: s.url, description: s.description }));
|
|
1153
|
+
return /* @__PURE__ */ jsxs("section", { className: "pb-10 mb-10 border-b", children: [
|
|
1154
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [
|
|
1155
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap min-w-0", children: [
|
|
1156
|
+
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold tracking-tight text-foreground leading-tight", children: info.title }),
|
|
1157
|
+
/* @__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: [
|
|
1158
|
+
"v",
|
|
1159
|
+
info.version
|
|
1160
|
+
] })
|
|
1161
|
+
] }),
|
|
1162
|
+
/* @__PURE__ */ jsx(
|
|
1163
|
+
SchemaCopyMenu,
|
|
1164
|
+
{
|
|
1165
|
+
schema,
|
|
1166
|
+
endpoints,
|
|
1167
|
+
baseUrl: resolvedBaseUrl
|
|
1168
|
+
}
|
|
1169
|
+
)
|
|
1170
|
+
] }),
|
|
1171
|
+
info.description && /* @__PURE__ */ jsx("div", { className: "mt-4 text-muted-foreground", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: info.description }) }),
|
|
1172
|
+
baseUrlRows.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-6 space-y-2", children: [
|
|
1173
|
+
/* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60", children: "Base URL" }),
|
|
1174
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: baseUrlRows.map((row, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
|
|
1175
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs px-2 py-1 rounded bg-muted border", children: row.url }),
|
|
1176
|
+
row.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: row.description })
|
|
1177
|
+
] }, `${row.url}-${i}`)) })
|
|
1178
|
+
] })
|
|
1179
|
+
] });
|
|
1180
|
+
}
|
|
1181
|
+
__name(ApiIntroSection, "ApiIntroSection");
|
|
1182
|
+
var EndpointDocContext = createContext(null);
|
|
1183
|
+
function EndpointDocProvider({ endpointId, method, children }) {
|
|
1184
|
+
const value = useMemo(() => ({ endpointId, method }), [endpointId, method]);
|
|
1185
|
+
return /* @__PURE__ */ jsx(EndpointDocContext.Provider, { value, children });
|
|
1186
|
+
}
|
|
1187
|
+
__name(EndpointDocProvider, "EndpointDocProvider");
|
|
1188
|
+
function useEndpointDocContext() {
|
|
1189
|
+
const ctx = useContext(EndpointDocContext);
|
|
1190
|
+
if (!ctx) {
|
|
1191
|
+
throw new Error(
|
|
1192
|
+
"[OpenapiViewer] useEndpointDocContext must be used inside <EndpointDocProvider>."
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
return ctx;
|
|
1196
|
+
}
|
|
1197
|
+
__name(useEndpointDocContext, "useEndpointDocContext");
|
|
1198
|
+
var sectionKey = /* @__PURE__ */ __name((endpointId, sectionId) => `${endpointId}:${sectionId}`, "sectionKey");
|
|
1199
|
+
var initialState = {
|
|
1200
|
+
openSections: {},
|
|
1201
|
+
activeCodeTab: {}
|
|
1202
|
+
};
|
|
1203
|
+
var useEndpointDocStore = create()(
|
|
1204
|
+
persist(
|
|
1205
|
+
(set) => ({
|
|
1206
|
+
...initialState,
|
|
1207
|
+
toggleSection: /* @__PURE__ */ __name((endpointId, sectionId) => set((state) => {
|
|
1208
|
+
const key = sectionKey(endpointId, sectionId);
|
|
1209
|
+
const current = state.openSections[key];
|
|
1210
|
+
return {
|
|
1211
|
+
openSections: {
|
|
1212
|
+
...state.openSections,
|
|
1213
|
+
// If there's no explicit override yet, the user's
|
|
1214
|
+
// first click means "flip from the default". We
|
|
1215
|
+
// assume the default was ``true`` for the most
|
|
1216
|
+
// common case (bodies/responses) and ``false``
|
|
1217
|
+
// otherwise; the Section component tracks this
|
|
1218
|
+
// via its ``defaultOpen`` prop and never calls
|
|
1219
|
+
// toggle on sections whose defaults match the
|
|
1220
|
+
// next state.
|
|
1221
|
+
[key]: current === void 0 ? false : !current
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
}), "toggleSection"),
|
|
1225
|
+
setSectionOpen: /* @__PURE__ */ __name((endpointId, sectionId, open) => set((state) => ({
|
|
1226
|
+
openSections: {
|
|
1227
|
+
...state.openSections,
|
|
1228
|
+
[sectionKey(endpointId, sectionId)]: open
|
|
1229
|
+
}
|
|
1230
|
+
})), "setSectionOpen"),
|
|
1231
|
+
setCodeTab: /* @__PURE__ */ __name((endpointId, tab) => set((state) => ({
|
|
1232
|
+
activeCodeTab: {
|
|
1233
|
+
...state.activeCodeTab,
|
|
1234
|
+
[endpointId]: tab
|
|
1235
|
+
}
|
|
1236
|
+
})), "setCodeTab"),
|
|
1237
|
+
expandAll: /* @__PURE__ */ __name((endpointId, sectionIds) => set((state) => {
|
|
1238
|
+
const next = { ...state.openSections };
|
|
1239
|
+
for (const sid of sectionIds) {
|
|
1240
|
+
next[sectionKey(endpointId, sid)] = true;
|
|
1241
|
+
}
|
|
1242
|
+
return { openSections: next };
|
|
1243
|
+
}), "expandAll"),
|
|
1244
|
+
collapseAll: /* @__PURE__ */ __name((endpointId, sectionIds) => set((state) => {
|
|
1245
|
+
const next = { ...state.openSections };
|
|
1246
|
+
for (const sid of sectionIds) {
|
|
1247
|
+
next[sectionKey(endpointId, sid)] = false;
|
|
1248
|
+
}
|
|
1249
|
+
return { openSections: next };
|
|
1250
|
+
}), "collapseAll")
|
|
1251
|
+
}),
|
|
1252
|
+
{
|
|
1253
|
+
name: "openapi-viewer:endpoint-doc",
|
|
1254
|
+
storage: createJSONStorage(() => {
|
|
1255
|
+
if (typeof window === "undefined") {
|
|
1256
|
+
return {
|
|
1257
|
+
getItem: /* @__PURE__ */ __name(() => null, "getItem"),
|
|
1258
|
+
setItem: /* @__PURE__ */ __name(() => {
|
|
1259
|
+
}, "setItem"),
|
|
1260
|
+
removeItem: /* @__PURE__ */ __name(() => {
|
|
1261
|
+
}, "removeItem")
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
return window.sessionStorage;
|
|
1265
|
+
}),
|
|
1266
|
+
// Only persist user overrides, not the functions. Zustand
|
|
1267
|
+
// serialises everything by default and logs a warning on
|
|
1268
|
+
// non-serialisable values; partialize keeps the payload lean.
|
|
1269
|
+
partialize: /* @__PURE__ */ __name((state) => ({
|
|
1270
|
+
openSections: state.openSections,
|
|
1271
|
+
activeCodeTab: state.activeCodeTab
|
|
1272
|
+
}), "partialize")
|
|
1273
|
+
}
|
|
1274
|
+
)
|
|
1275
|
+
);
|
|
1276
|
+
|
|
1277
|
+
// src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/store/selectors.ts
|
|
1278
|
+
function useIsSectionOpen(endpointId, sectionId, defaultOpen) {
|
|
1279
|
+
return useEndpointDocStore((s) => {
|
|
1280
|
+
const explicit = s.openSections[sectionKey(endpointId, sectionId)];
|
|
1281
|
+
return explicit === void 0 ? defaultOpen : explicit;
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
__name(useIsSectionOpen, "useIsSectionOpen");
|
|
1285
|
+
function useActiveCodeTab(endpointId, fallback = "curl") {
|
|
1286
|
+
return useEndpointDocStore((s) => s.activeCodeTab[endpointId] ?? fallback);
|
|
1287
|
+
}
|
|
1288
|
+
__name(useActiveCodeTab, "useActiveCodeTab");
|
|
1289
|
+
function LanguageTabs({ activeId, onChange }) {
|
|
1290
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 overflow-x-auto -mx-1 px-1", children: CODE_SAMPLE_TARGETS.map((t) => /* @__PURE__ */ jsx(
|
|
1291
|
+
"button",
|
|
1292
|
+
{
|
|
1293
|
+
type: "button",
|
|
1294
|
+
onClick: () => onChange(t.id),
|
|
1295
|
+
className: cn(
|
|
1296
|
+
"shrink-0 h-7 px-2.5 rounded text-xs font-medium transition-colors",
|
|
1297
|
+
activeId === t.id ? "bg-muted text-foreground" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted/50"
|
|
1298
|
+
),
|
|
1299
|
+
children: t.label
|
|
1300
|
+
},
|
|
1301
|
+
t.id
|
|
1302
|
+
)) });
|
|
1303
|
+
}
|
|
1304
|
+
__name(LanguageTabs, "LanguageTabs");
|
|
1305
|
+
function useCodeSnippet({
|
|
1306
|
+
endpoint,
|
|
1307
|
+
body,
|
|
1308
|
+
parameters,
|
|
1309
|
+
headers,
|
|
1310
|
+
baseUrl,
|
|
1311
|
+
activeId
|
|
1312
|
+
}) {
|
|
1313
|
+
const effectiveBody = body ?? endpoint.requestBody?.example;
|
|
1314
|
+
const har = useMemo(() => {
|
|
1315
|
+
const h = buildHarRequest({
|
|
1316
|
+
endpoint,
|
|
1317
|
+
body: effectiveBody,
|
|
1318
|
+
parameters,
|
|
1319
|
+
headers,
|
|
1320
|
+
baseUrl
|
|
1321
|
+
});
|
|
1322
|
+
return baseUrl ? h : { ...h, url: resolveAbsolute(h.url) };
|
|
1323
|
+
}, [endpoint, effectiveBody, parameters, headers, baseUrl]);
|
|
1324
|
+
return useMemo(() => {
|
|
1325
|
+
const target = CODE_SAMPLE_TARGETS.find((t) => t.id === activeId);
|
|
1326
|
+
const code = renderSnippet(har, activeId);
|
|
1327
|
+
return {
|
|
1328
|
+
snippet: code ?? `// Snippet for ${target.label} is unavailable for this request.`,
|
|
1329
|
+
prism: target.prism
|
|
1330
|
+
};
|
|
1331
|
+
}, [har, activeId]);
|
|
1332
|
+
}
|
|
1333
|
+
__name(useCodeSnippet, "useCodeSnippet");
|
|
1334
|
+
function CodeSamples({ endpoint, body, parameters, headers, baseUrl }) {
|
|
1335
|
+
const { endpointId } = useEndpointDocContext();
|
|
1336
|
+
const activeId = useActiveCodeTab(endpointId);
|
|
1337
|
+
const setCodeTab = useEndpointDocStore((s) => s.setCodeTab);
|
|
1338
|
+
const { snippet, prism } = useCodeSnippet({
|
|
1339
|
+
endpoint,
|
|
1340
|
+
body,
|
|
1341
|
+
parameters,
|
|
1342
|
+
headers,
|
|
1343
|
+
baseUrl,
|
|
1344
|
+
activeId
|
|
1345
|
+
});
|
|
1346
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
|
|
1347
|
+
/* @__PURE__ */ jsx(LanguageTabs, { activeId, onChange: (id) => setCodeTab(endpointId, id) }),
|
|
1348
|
+
/* @__PURE__ */ jsx(
|
|
1349
|
+
PrettyCode_default,
|
|
1350
|
+
{
|
|
1351
|
+
data: snippet,
|
|
1352
|
+
language: prism,
|
|
1353
|
+
isCompact: true,
|
|
1354
|
+
maxLines: 20
|
|
1355
|
+
}
|
|
1356
|
+
)
|
|
1357
|
+
] });
|
|
1358
|
+
}
|
|
1359
|
+
__name(CodeSamples, "CodeSamples");
|
|
1360
|
+
function IconButton({ label, onClick, children, active }) {
|
|
1361
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1362
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
1363
|
+
"button",
|
|
1364
|
+
{
|
|
1365
|
+
type: "button",
|
|
1366
|
+
onClick,
|
|
1367
|
+
"aria-label": label,
|
|
1368
|
+
className: cn(
|
|
1369
|
+
"shrink-0 h-6 w-6 inline-flex items-center justify-center rounded",
|
|
1370
|
+
"text-muted-foreground/60 hover:text-foreground hover:bg-muted transition-colors",
|
|
1371
|
+
active && "text-emerald-500 hover:text-emerald-500"
|
|
1372
|
+
),
|
|
1373
|
+
children
|
|
1374
|
+
}
|
|
1375
|
+
) }),
|
|
1376
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "bottom", className: "text-[11px]", children: label })
|
|
1377
|
+
] });
|
|
1378
|
+
}
|
|
1379
|
+
__name(IconButton, "IconButton");
|
|
1380
|
+
function MetaActions({ anchor, endpointMarkdown, presentSections }) {
|
|
1381
|
+
const { endpointId } = useEndpointDocContext();
|
|
1382
|
+
const expandAll = useEndpointDocStore((s) => s.expandAll);
|
|
1383
|
+
const collapseAll = useEndpointDocStore((s) => s.collapseAll);
|
|
1384
|
+
const openSections = useEndpointDocStore((s) => s.openSections);
|
|
1385
|
+
const [justCopied, setJustCopied] = useState(null);
|
|
1386
|
+
const flash = useCallback((which) => {
|
|
1387
|
+
setJustCopied(which);
|
|
1388
|
+
setTimeout(() => setJustCopied(null), 1200);
|
|
1389
|
+
}, []);
|
|
1390
|
+
const mostlyOpen = useMemo(() => {
|
|
1391
|
+
if (presentSections.length === 0) return false;
|
|
1392
|
+
let openCount = 0;
|
|
1393
|
+
for (const sid of presentSections) {
|
|
1394
|
+
if (openSections[sectionKey(endpointId, sid)]) openCount += 1;
|
|
1395
|
+
}
|
|
1396
|
+
return openCount > presentSections.length / 2;
|
|
1397
|
+
}, [openSections, presentSections, endpointId]);
|
|
1398
|
+
const copyLink = useCallback(() => {
|
|
1399
|
+
if (typeof window === "undefined") return;
|
|
1400
|
+
const url = `${window.location.origin}${window.location.pathname}#${anchor}`;
|
|
1401
|
+
void navigator.clipboard?.writeText(url).then(() => flash("link"));
|
|
1402
|
+
}, [anchor, flash]);
|
|
1403
|
+
const copyMarkdown = useCallback(() => {
|
|
1404
|
+
if (typeof window === "undefined") return;
|
|
1405
|
+
void navigator.clipboard?.writeText(endpointMarkdown).then(() => flash("md"));
|
|
1406
|
+
}, [endpointMarkdown, flash]);
|
|
1407
|
+
const toggleAll = useCallback(() => {
|
|
1408
|
+
if (mostlyOpen) collapseAll(endpointId, presentSections);
|
|
1409
|
+
else expandAll(endpointId, presentSections);
|
|
1410
|
+
}, [mostlyOpen, collapseAll, expandAll, endpointId, presentSections]);
|
|
1411
|
+
return /* @__PURE__ */ jsx(SafeTooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5", children: [
|
|
1412
|
+
/* @__PURE__ */ jsx(
|
|
1413
|
+
IconButton,
|
|
1414
|
+
{
|
|
1415
|
+
label: justCopied === "link" ? "Copied!" : "Copy link to endpoint",
|
|
1416
|
+
onClick: copyLink,
|
|
1417
|
+
active: justCopied === "link",
|
|
1418
|
+
children: justCopied === "link" ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5" })
|
|
1419
|
+
}
|
|
1420
|
+
),
|
|
1421
|
+
/* @__PURE__ */ jsx(
|
|
1422
|
+
IconButton,
|
|
1423
|
+
{
|
|
1424
|
+
label: justCopied === "md" ? "Copied!" : "Copy as Markdown (for AI)",
|
|
1425
|
+
onClick: copyMarkdown,
|
|
1426
|
+
active: justCopied === "md",
|
|
1427
|
+
children: justCopied === "md" ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(FileCode2, { className: "h-3.5 w-3.5" })
|
|
1428
|
+
}
|
|
1429
|
+
),
|
|
1430
|
+
presentSections.length >= 2 && /* @__PURE__ */ jsx(
|
|
1431
|
+
IconButton,
|
|
1432
|
+
{
|
|
1433
|
+
label: mostlyOpen ? "Collapse all sections" : "Expand all sections",
|
|
1434
|
+
onClick: toggleAll,
|
|
1435
|
+
children: mostlyOpen ? /* @__PURE__ */ jsx(ChevronsDownUp, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: "h-3.5 w-3.5" })
|
|
1436
|
+
}
|
|
1437
|
+
)
|
|
1438
|
+
] }) });
|
|
1439
|
+
}
|
|
1440
|
+
__name(MetaActions, "MetaActions");
|
|
1441
|
+
function PathDisplay({ path }) {
|
|
1442
|
+
return /* @__PURE__ */ jsx(
|
|
1443
|
+
"code",
|
|
1444
|
+
{
|
|
1445
|
+
className: "block font-mono text-lg md:text-xl font-semibold text-foreground leading-tight",
|
|
1446
|
+
style: { overflowWrap: "anywhere", wordBreak: "break-word" },
|
|
1447
|
+
children: relativePath(path)
|
|
1448
|
+
}
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
__name(PathDisplay, "PathDisplay");
|
|
1452
|
+
function EndpointHeader({
|
|
1453
|
+
endpoint,
|
|
1454
|
+
anchor,
|
|
1455
|
+
isLoadedInPlayground,
|
|
1456
|
+
onTryIt,
|
|
1457
|
+
presentSections
|
|
1458
|
+
}) {
|
|
1459
|
+
const endpointMd = useMemo(() => endpointToMarkdown(endpoint), [endpoint]);
|
|
1460
|
+
return /* @__PURE__ */ jsxs("header", { className: "space-y-3", children: [
|
|
1461
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [
|
|
1462
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
1463
|
+
/* @__PURE__ */ jsx(MethodBadge, { method: endpoint.method }),
|
|
1464
|
+
/* @__PURE__ */ jsx(
|
|
1465
|
+
MetaActions,
|
|
1466
|
+
{
|
|
1467
|
+
anchor,
|
|
1468
|
+
endpointMarkdown: endpointMd,
|
|
1469
|
+
presentSections
|
|
1470
|
+
}
|
|
1471
|
+
)
|
|
1472
|
+
] }),
|
|
1473
|
+
/* @__PURE__ */ jsxs(
|
|
1474
|
+
Button,
|
|
1475
|
+
{
|
|
1476
|
+
size: "sm",
|
|
1477
|
+
variant: isLoadedInPlayground ? "secondary" : "default",
|
|
1478
|
+
onClick: onTryIt,
|
|
1479
|
+
className: "ml-auto h-7 text-xs gap-1.5 px-2.5",
|
|
1480
|
+
children: [
|
|
1481
|
+
/* @__PURE__ */ jsx(Play, { className: "h-3 w-3" }),
|
|
1482
|
+
isLoadedInPlayground ? "Loaded" : "Try it"
|
|
1483
|
+
]
|
|
1484
|
+
}
|
|
1485
|
+
)
|
|
1486
|
+
] }),
|
|
1487
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsx(PathDisplay, { path: endpoint.path }) }),
|
|
1488
|
+
endpoint.description && /* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-sm", children: /* @__PURE__ */ jsx(MarkdownMessage, { content: endpoint.description }) })
|
|
1489
|
+
] });
|
|
1490
|
+
}
|
|
1491
|
+
__name(EndpointHeader, "EndpointHeader");
|
|
1492
|
+
function ParamRow({ param }) {
|
|
1493
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-3 py-2.5 bg-background space-y-1", children: [
|
|
1494
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
|
|
1495
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: param.name }),
|
|
1496
|
+
param.required && /* @__PURE__ */ jsx(
|
|
1497
|
+
"span",
|
|
1498
|
+
{
|
|
1499
|
+
title: "Required",
|
|
1500
|
+
className: "text-[9px] text-destructive font-bold leading-none",
|
|
1501
|
+
children: "*"
|
|
1502
|
+
}
|
|
1503
|
+
),
|
|
1504
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: param.type })
|
|
1505
|
+
] }),
|
|
1506
|
+
param.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: param.description })
|
|
1507
|
+
] });
|
|
1508
|
+
}
|
|
1509
|
+
__name(ParamRow, "ParamRow");
|
|
1510
|
+
function ParamGroup({ label, params }) {
|
|
1511
|
+
if (params.length === 0) return null;
|
|
1512
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1513
|
+
/* @__PURE__ */ jsx("div", { className: "text-[10px] font-semibold uppercase tracking-[0.1em] text-muted-foreground/60 px-1", children: label }),
|
|
1514
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: params.map((p) => /* @__PURE__ */ jsx(ParamRow, { param: p }, `${label}-${p.name}`)) })
|
|
1515
|
+
] });
|
|
1516
|
+
}
|
|
1517
|
+
__name(ParamGroup, "ParamGroup");
|
|
1518
|
+
function Parameters({ pathParams, queryParams }) {
|
|
1519
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1520
|
+
/* @__PURE__ */ jsx(ParamGroup, { label: "Path", params: pathParams }),
|
|
1521
|
+
/* @__PURE__ */ jsx(ParamGroup, { label: "Query", params: queryParams })
|
|
1522
|
+
] });
|
|
1523
|
+
}
|
|
1524
|
+
__name(Parameters, "Parameters");
|
|
1525
|
+
|
|
1526
|
+
// src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/SchemaFields/buildTree.ts
|
|
1527
|
+
var MAX_DEPTH = 5;
|
|
1528
|
+
function mergeAllOf(branches) {
|
|
1529
|
+
const properties = {};
|
|
1530
|
+
const required = [];
|
|
1531
|
+
for (const b of branches) {
|
|
1532
|
+
if (b.properties) Object.assign(properties, b.properties);
|
|
1533
|
+
if (Array.isArray(b.required)) required.push(...b.required);
|
|
1534
|
+
}
|
|
1535
|
+
return { type: "object", properties, required };
|
|
1536
|
+
}
|
|
1537
|
+
__name(mergeAllOf, "mergeAllOf");
|
|
1538
|
+
function describeType(node) {
|
|
1539
|
+
if (node.type === "array") {
|
|
1540
|
+
const itemLabel = node.items ? describeType(node.items).label : "any";
|
|
1541
|
+
return { label: `array<${itemLabel}>`, kind: "array" };
|
|
1542
|
+
}
|
|
1543
|
+
if (node.type === "object" || node.properties) {
|
|
1544
|
+
return { label: "object", kind: "object" };
|
|
1545
|
+
}
|
|
1546
|
+
const base = node.type || "any";
|
|
1547
|
+
if (Array.isArray(node.enum) && node.enum.length > 0) {
|
|
1548
|
+
return { label: `${base} enum`, kind: "primitive" };
|
|
1549
|
+
}
|
|
1550
|
+
if (node.format) {
|
|
1551
|
+
return { label: `${base} (${node.format})`, kind: "primitive" };
|
|
1552
|
+
}
|
|
1553
|
+
return { label: base, kind: "primitive" };
|
|
1554
|
+
}
|
|
1555
|
+
__name(describeType, "describeType");
|
|
1556
|
+
function resolveCombinators(node) {
|
|
1557
|
+
if (Array.isArray(node.allOf) && node.allOf.length > 0) {
|
|
1558
|
+
return { ...mergeAllOf(node.allOf), description: node.description };
|
|
1559
|
+
}
|
|
1560
|
+
if (Array.isArray(node.oneOf) && node.oneOf.length > 0) {
|
|
1561
|
+
return { ...node.oneOf[0], description: node.description ?? node.oneOf[0].description };
|
|
1562
|
+
}
|
|
1563
|
+
if (Array.isArray(node.anyOf) && node.anyOf.length > 0) {
|
|
1564
|
+
return { ...node.anyOf[0], description: node.description ?? node.anyOf[0].description };
|
|
1565
|
+
}
|
|
1566
|
+
return node;
|
|
1567
|
+
}
|
|
1568
|
+
__name(resolveCombinators, "resolveCombinators");
|
|
1569
|
+
function buildNode(name, schema, isRequired, depth) {
|
|
1570
|
+
const resolved = resolveCombinators(schema);
|
|
1571
|
+
const { label, kind } = describeType(resolved);
|
|
1572
|
+
const node = {
|
|
1573
|
+
name,
|
|
1574
|
+
type: label,
|
|
1575
|
+
kind,
|
|
1576
|
+
required: isRequired,
|
|
1577
|
+
description: resolved.description
|
|
1578
|
+
};
|
|
1579
|
+
if (Array.isArray(resolved.enum) && resolved.enum.length > 0) {
|
|
1580
|
+
node.enumValues = resolved.enum.map((v) => String(v));
|
|
1581
|
+
}
|
|
1582
|
+
if (depth >= MAX_DEPTH) return node;
|
|
1583
|
+
if (kind === "object" && resolved.properties) {
|
|
1584
|
+
const required = new Set(resolved.required ?? []);
|
|
1585
|
+
node.children = Object.entries(resolved.properties).map(
|
|
1586
|
+
([key, child]) => buildNode(key, child, required.has(key), depth + 1)
|
|
1587
|
+
);
|
|
1588
|
+
} else if (kind === "array" && resolved.items) {
|
|
1589
|
+
node.children = [buildNode("[]", resolved.items, false, depth + 1)];
|
|
1590
|
+
}
|
|
1591
|
+
return node;
|
|
1592
|
+
}
|
|
1593
|
+
__name(buildNode, "buildNode");
|
|
1594
|
+
function buildSchemaTree(schema) {
|
|
1595
|
+
if (!schema) return [];
|
|
1596
|
+
const root = buildNode("", schema, false, 0);
|
|
1597
|
+
if (root.children && root.children.length > 0) return root.children;
|
|
1598
|
+
if (root.kind === "primitive" || !root.children && root.name === "") {
|
|
1599
|
+
return [{ ...root, name: root.name || "(body)" }];
|
|
1600
|
+
}
|
|
1601
|
+
return [];
|
|
1602
|
+
}
|
|
1603
|
+
__name(buildSchemaTree, "buildSchemaTree");
|
|
1604
|
+
function FieldRow({ field, depth, showTreeLine = true }) {
|
|
1605
|
+
const isExpandable = (field.kind === "object" || field.kind === "array") && Array.isArray(field.children) && field.children.length > 0;
|
|
1606
|
+
const [open, setOpen] = useState(depth < 2);
|
|
1607
|
+
const padLeft = showTreeLine ? depth * 14 : 0;
|
|
1608
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-background", children: [
|
|
1609
|
+
/* @__PURE__ */ jsxs(
|
|
1610
|
+
"div",
|
|
1611
|
+
{
|
|
1612
|
+
className: cn(
|
|
1613
|
+
"grid grid-cols-[16px_minmax(0,1fr)] items-baseline gap-2 px-3 py-2",
|
|
1614
|
+
isExpandable && "cursor-pointer hover:bg-muted/30"
|
|
1615
|
+
),
|
|
1616
|
+
style: { paddingLeft: 12 + padLeft },
|
|
1617
|
+
onClick: () => isExpandable && setOpen((v) => !v),
|
|
1618
|
+
role: isExpandable ? "button" : void 0,
|
|
1619
|
+
"aria-expanded": isExpandable ? open : void 0,
|
|
1620
|
+
children: [
|
|
1621
|
+
/* @__PURE__ */ jsx(
|
|
1622
|
+
ChevronRight,
|
|
1623
|
+
{
|
|
1624
|
+
className: cn(
|
|
1625
|
+
"h-3.5 w-3.5 text-muted-foreground/50 shrink-0 transition-transform",
|
|
1626
|
+
!isExpandable && "opacity-0",
|
|
1627
|
+
open && "rotate-90"
|
|
1628
|
+
)
|
|
1629
|
+
}
|
|
1630
|
+
),
|
|
1631
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-1", children: [
|
|
1632
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
|
|
1633
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-xs font-medium text-foreground", children: field.name }),
|
|
1634
|
+
field.required && /* @__PURE__ */ jsx(
|
|
1635
|
+
"span",
|
|
1636
|
+
{
|
|
1637
|
+
title: "Required",
|
|
1638
|
+
className: "text-[9px] text-destructive font-bold leading-none",
|
|
1639
|
+
children: "*"
|
|
1640
|
+
}
|
|
1641
|
+
),
|
|
1642
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/70", children: field.type })
|
|
1643
|
+
] }),
|
|
1644
|
+
field.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground leading-relaxed break-words", children: field.description }),
|
|
1645
|
+
field.enumValues && field.enumValues.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1 pt-0.5", children: field.enumValues.map((v) => /* @__PURE__ */ jsx(
|
|
1646
|
+
"code",
|
|
1647
|
+
{
|
|
1648
|
+
className: "inline-flex items-center rounded border border-border/60 bg-muted/40 px-1.5 py-px font-mono text-[10px] text-muted-foreground",
|
|
1649
|
+
children: v
|
|
1650
|
+
},
|
|
1651
|
+
v
|
|
1652
|
+
)) })
|
|
1653
|
+
] })
|
|
1654
|
+
]
|
|
1655
|
+
}
|
|
1656
|
+
),
|
|
1657
|
+
isExpandable && open && /* @__PURE__ */ jsx("div", { children: field.children.map((child, i) => /* @__PURE__ */ jsx(
|
|
1658
|
+
FieldRow,
|
|
1659
|
+
{
|
|
1660
|
+
field: child,
|
|
1661
|
+
depth: depth + 1
|
|
1662
|
+
},
|
|
1663
|
+
`${child.name}-${i}`
|
|
1664
|
+
)) })
|
|
1665
|
+
] });
|
|
1666
|
+
}
|
|
1667
|
+
__name(FieldRow, "FieldRow");
|
|
1668
|
+
function SchemaFields({ schema }) {
|
|
1669
|
+
const tree = useMemo(() => buildSchemaTree(schema), [schema]);
|
|
1670
|
+
if (tree.length === 0) return null;
|
|
1671
|
+
return /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: tree.map((node, i) => /* @__PURE__ */ jsx(FieldRow, { field: node, depth: 0 }, `${node.name}-${i}`)) });
|
|
1672
|
+
}
|
|
1673
|
+
__name(SchemaFields, "SchemaFields");
|
|
1674
|
+
function RequestBody({ body }) {
|
|
1675
|
+
const typeLabel = body.schema ? body.type === "array" ? `array<${body.schema.items?.type ?? "object"}>` : body.type : body.type;
|
|
1676
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1677
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 flex-wrap", children: [
|
|
1678
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground/80", children: typeLabel }),
|
|
1679
|
+
body.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: body.description })
|
|
1680
|
+
] }),
|
|
1681
|
+
body.schema && /* @__PURE__ */ jsx(SchemaFields, { schema: body.schema })
|
|
1682
|
+
] });
|
|
1683
|
+
}
|
|
1684
|
+
__name(RequestBody, "RequestBody");
|
|
1685
|
+
var EXAMPLE_JSON_TREE_CONFIG = {
|
|
1686
|
+
maxAutoExpandDepth: 2,
|
|
1687
|
+
maxAutoExpandArrayItems: 5,
|
|
1688
|
+
maxAutoExpandObjectKeys: 8,
|
|
1689
|
+
maxStringLength: 160,
|
|
1690
|
+
collectionLimit: 25,
|
|
1691
|
+
showCollectionInfo: true,
|
|
1692
|
+
showExpandControls: false,
|
|
1693
|
+
showActionButtons: false,
|
|
1694
|
+
preserveKeyOrder: true,
|
|
1695
|
+
className: "border-0 rounded-none"
|
|
1696
|
+
};
|
|
1697
|
+
function ResponseBody({ example, contentType }) {
|
|
1698
|
+
const parsed = useMemo(() => {
|
|
1699
|
+
try {
|
|
1700
|
+
return JSON.parse(example);
|
|
1701
|
+
} catch {
|
|
1702
|
+
return null;
|
|
1703
|
+
}
|
|
1704
|
+
}, [example]);
|
|
1705
|
+
return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/20", children: [
|
|
1706
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-border/50", children: [
|
|
1707
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-[10px] uppercase tracking-wider text-muted-foreground/70", children: contentType ?? "application/json" }),
|
|
1708
|
+
/* @__PURE__ */ jsx(
|
|
1709
|
+
CopyButton,
|
|
1710
|
+
{
|
|
1711
|
+
value: example,
|
|
1712
|
+
variant: "ghost",
|
|
1713
|
+
size: "sm",
|
|
1714
|
+
className: "h-6 px-2 text-[10px] text-muted-foreground",
|
|
1715
|
+
children: "Copy"
|
|
1716
|
+
}
|
|
1717
|
+
)
|
|
1718
|
+
] }),
|
|
1719
|
+
parsed != null ? /* @__PURE__ */ jsx(
|
|
1720
|
+
JsonTree_default,
|
|
1721
|
+
{
|
|
1722
|
+
title: "",
|
|
1723
|
+
data: parsed,
|
|
1724
|
+
mode: "compact",
|
|
1725
|
+
config: EXAMPLE_JSON_TREE_CONFIG
|
|
1726
|
+
}
|
|
1727
|
+
) : /* @__PURE__ */ jsx("pre", { className: "p-3 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: example })
|
|
1728
|
+
] });
|
|
1729
|
+
}
|
|
1730
|
+
__name(ResponseBody, "ResponseBody");
|
|
1731
|
+
function StatusTag({ code }) {
|
|
1732
|
+
const numeric = Number.parseInt(code, 10);
|
|
1733
|
+
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";
|
|
1734
|
+
return /* @__PURE__ */ jsx("span", { className: cn(
|
|
1735
|
+
"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",
|
|
1736
|
+
cls
|
|
1737
|
+
), children: code });
|
|
1738
|
+
}
|
|
1739
|
+
__name(StatusTag, "StatusTag");
|
|
1740
|
+
function ResponseRow({ response }) {
|
|
1741
|
+
const hasExample = Boolean(response.example);
|
|
1742
|
+
const numeric = Number.parseInt(response.code, 10);
|
|
1743
|
+
const isSuccess = Number.isFinite(numeric) && numeric >= 200 && numeric < 300;
|
|
1744
|
+
const [open, setOpen] = useState(hasExample && isSuccess);
|
|
1745
|
+
if (!hasExample) {
|
|
1746
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2 bg-background", children: [
|
|
1747
|
+
/* @__PURE__ */ jsx("div", { className: "w-12 shrink-0 flex justify-start", children: /* @__PURE__ */ jsx(StatusTag, { code: response.code }) }),
|
|
1748
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground leading-relaxed break-words min-w-0", children: response.description })
|
|
1749
|
+
] });
|
|
1750
|
+
}
|
|
1751
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-background", children: [
|
|
1752
|
+
/* @__PURE__ */ jsxs(
|
|
1753
|
+
"button",
|
|
1754
|
+
{
|
|
1755
|
+
type: "button",
|
|
1756
|
+
onClick: () => setOpen((v) => !v),
|
|
1757
|
+
className: "w-full flex items-center gap-3 px-3 py-2 text-left hover:bg-muted/40 cursor-pointer transition-colors",
|
|
1758
|
+
"aria-expanded": open,
|
|
1759
|
+
children: [
|
|
1760
|
+
/* @__PURE__ */ jsx(
|
|
1761
|
+
ChevronRight,
|
|
1762
|
+
{
|
|
1763
|
+
className: cn(
|
|
1764
|
+
"h-3.5 w-3.5 text-muted-foreground/60 transition-transform shrink-0",
|
|
1765
|
+
open && "rotate-90"
|
|
1766
|
+
)
|
|
1767
|
+
}
|
|
1768
|
+
),
|
|
1769
|
+
/* @__PURE__ */ jsx("div", { className: "w-12 shrink-0 flex justify-start", children: /* @__PURE__ */ jsx(StatusTag, { code: response.code }) }),
|
|
1770
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground leading-relaxed break-words min-w-0 flex-1", children: response.description })
|
|
1771
|
+
]
|
|
1772
|
+
}
|
|
1773
|
+
),
|
|
1774
|
+
open && /* @__PURE__ */ jsx(
|
|
1775
|
+
ResponseBody,
|
|
1776
|
+
{
|
|
1777
|
+
example: response.example,
|
|
1778
|
+
contentType: response.contentType
|
|
1779
|
+
}
|
|
1780
|
+
)
|
|
1781
|
+
] });
|
|
1782
|
+
}
|
|
1783
|
+
__name(ResponseRow, "ResponseRow");
|
|
1784
|
+
function Responses({ responses }) {
|
|
1785
|
+
return /* @__PURE__ */ jsx("div", { className: "divide-y border rounded-md overflow-hidden", children: responses.map((r) => /* @__PURE__ */ jsx(ResponseRow, { response: r }, r.code)) });
|
|
1786
|
+
}
|
|
1787
|
+
__name(Responses, "Responses");
|
|
1788
|
+
|
|
1789
|
+
// src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/Section/defaults.ts
|
|
1790
|
+
var DEFAULTS_BY_METHOD = {
|
|
1791
|
+
GET: { parameters: true, responses: true },
|
|
1792
|
+
DELETE: { parameters: true, responses: true },
|
|
1793
|
+
POST: { requestBody: true, responses: true },
|
|
1794
|
+
PUT: { requestBody: true, responses: true },
|
|
1795
|
+
PATCH: { requestBody: true, responses: true }
|
|
1796
|
+
};
|
|
1797
|
+
function defaultSectionOpen(method, sectionId) {
|
|
1798
|
+
return DEFAULTS_BY_METHOD[method.toUpperCase()]?.[sectionId] ?? false;
|
|
1799
|
+
}
|
|
1800
|
+
__name(defaultSectionOpen, "defaultSectionOpen");
|
|
1801
|
+
|
|
1802
|
+
// src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/types.ts
|
|
1803
|
+
var ALL_SECTION_IDS = [
|
|
1804
|
+
"parameters",
|
|
1805
|
+
"requestBody",
|
|
1806
|
+
"responses",
|
|
1807
|
+
"codeSamples"
|
|
1808
|
+
];
|
|
1809
|
+
|
|
1810
|
+
// src/tools/OpenapiViewer/components/DocsLayout/EndpointDoc/hooks/useSectionHash.ts
|
|
1811
|
+
function parseSectionHash(hash) {
|
|
1812
|
+
const raw = hash.startsWith("#") ? hash.slice(1) : hash;
|
|
1813
|
+
if (!raw.startsWith("section=")) return null;
|
|
1814
|
+
const value = raw.slice("section=".length);
|
|
1815
|
+
const dot = value.lastIndexOf(".");
|
|
1816
|
+
if (dot <= 0 || dot === value.length - 1) return null;
|
|
1817
|
+
const endpointId = value.slice(0, dot);
|
|
1818
|
+
const sectionIdCandidate = value.slice(dot + 1);
|
|
1819
|
+
if (!ALL_SECTION_IDS.includes(sectionIdCandidate)) return null;
|
|
1820
|
+
return { endpointId, sectionId: sectionIdCandidate };
|
|
1821
|
+
}
|
|
1822
|
+
__name(parseSectionHash, "parseSectionHash");
|
|
1823
|
+
function buildSectionHash(endpointId, sectionId) {
|
|
1824
|
+
return `section=${endpointId}.${sectionId}`;
|
|
1825
|
+
}
|
|
1826
|
+
__name(buildSectionHash, "buildSectionHash");
|
|
1827
|
+
function useSectionHashRouter() {
|
|
1828
|
+
const setSectionOpen = useEndpointDocStore((s) => s.setSectionOpen);
|
|
1829
|
+
useEffect(() => {
|
|
1830
|
+
if (typeof window === "undefined") return;
|
|
1831
|
+
function apply() {
|
|
1832
|
+
const parsed = parseSectionHash(window.location.hash);
|
|
1833
|
+
if (!parsed) return;
|
|
1834
|
+
setSectionOpen(parsed.endpointId, parsed.sectionId, true);
|
|
1835
|
+
requestAnimationFrame(() => {
|
|
1836
|
+
const el = document.getElementById(parsed.endpointId);
|
|
1837
|
+
el?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
__name(apply, "apply");
|
|
1841
|
+
apply();
|
|
1842
|
+
window.addEventListener("hashchange", apply);
|
|
1843
|
+
return () => window.removeEventListener("hashchange", apply);
|
|
1844
|
+
}, [setSectionOpen]);
|
|
1845
|
+
}
|
|
1846
|
+
__name(useSectionHashRouter, "useSectionHashRouter");
|
|
1847
|
+
function SectionHeader({ sectionId, title, badge, open, onToggle }) {
|
|
1848
|
+
const { endpointId } = useEndpointDocContext();
|
|
1849
|
+
const [copied, setCopied] = useState(false);
|
|
1850
|
+
const copyHash = /* @__PURE__ */ __name((e) => {
|
|
1851
|
+
e.stopPropagation();
|
|
1852
|
+
if (typeof window === "undefined") return;
|
|
1853
|
+
const hash = buildSectionHash(endpointId, sectionId);
|
|
1854
|
+
const url = `${window.location.origin}${window.location.pathname}#${hash}`;
|
|
1855
|
+
void navigator.clipboard?.writeText(url).then(() => {
|
|
1856
|
+
setCopied(true);
|
|
1857
|
+
setTimeout(() => setCopied(false), 1200);
|
|
1858
|
+
});
|
|
1859
|
+
}, "copyHash");
|
|
1860
|
+
return /* @__PURE__ */ jsxs(
|
|
1861
|
+
"div",
|
|
1862
|
+
{
|
|
1863
|
+
className: cn(
|
|
1864
|
+
"group/section w-full flex items-center gap-2 py-1.5 -ml-1 px-1 rounded cursor-pointer",
|
|
1865
|
+
"hover:bg-muted/30 transition-colors"
|
|
1866
|
+
),
|
|
1867
|
+
onClick: onToggle,
|
|
1868
|
+
role: "button",
|
|
1869
|
+
"aria-expanded": open,
|
|
1870
|
+
tabIndex: 0,
|
|
1871
|
+
onKeyDown: (e) => {
|
|
1872
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1873
|
+
e.preventDefault();
|
|
1874
|
+
onToggle();
|
|
1875
|
+
}
|
|
1876
|
+
},
|
|
1877
|
+
children: [
|
|
1878
|
+
/* @__PURE__ */ jsx(
|
|
1879
|
+
ChevronDown,
|
|
1880
|
+
{
|
|
1881
|
+
className: cn(
|
|
1882
|
+
"h-3.5 w-3.5 text-muted-foreground/50 transition-transform shrink-0",
|
|
1883
|
+
!open && "-rotate-90"
|
|
1884
|
+
)
|
|
1885
|
+
}
|
|
1886
|
+
),
|
|
1887
|
+
/* @__PURE__ */ jsx("h4", { className: "text-[10px] font-semibold uppercase tracking-[0.12em] text-muted-foreground/80", children: title }),
|
|
1888
|
+
typeof badge === "number" && badge > 0 && /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50 tabular-nums", children: badge }),
|
|
1889
|
+
/* @__PURE__ */ jsx(
|
|
1890
|
+
"button",
|
|
1891
|
+
{
|
|
1892
|
+
type: "button",
|
|
1893
|
+
onClick: copyHash,
|
|
1894
|
+
title: "Copy link to this section",
|
|
1895
|
+
className: cn(
|
|
1896
|
+
"ml-auto shrink-0 p-1 rounded text-muted-foreground/40 hover:text-foreground hover:bg-muted transition-all",
|
|
1897
|
+
"opacity-0 group-hover/section:opacity-100 focus-visible:opacity-100",
|
|
1898
|
+
copied && "opacity-100 text-emerald-500"
|
|
1899
|
+
),
|
|
1900
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx(Link2, { className: "h-3 w-3" })
|
|
1901
|
+
}
|
|
1902
|
+
)
|
|
1903
|
+
]
|
|
1904
|
+
}
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
__name(SectionHeader, "SectionHeader");
|
|
1908
|
+
function Section({ id, title, badge, children }) {
|
|
1909
|
+
const { endpointId, method } = useEndpointDocContext();
|
|
1910
|
+
const defaultOpen = defaultSectionOpen(method, id);
|
|
1911
|
+
const open = useIsSectionOpen(endpointId, id, defaultOpen);
|
|
1912
|
+
const toggleSection = useEndpointDocStore((s) => s.toggleSection);
|
|
1913
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2.5", children: [
|
|
1914
|
+
/* @__PURE__ */ jsx(
|
|
1915
|
+
SectionHeader,
|
|
1916
|
+
{
|
|
1917
|
+
sectionId: id,
|
|
1918
|
+
title,
|
|
1919
|
+
badge,
|
|
1920
|
+
open,
|
|
1921
|
+
onToggle: () => toggleSection(endpointId, id)
|
|
1922
|
+
}
|
|
1923
|
+
),
|
|
1924
|
+
open && /* @__PURE__ */ jsx("div", { children })
|
|
1925
|
+
] });
|
|
1926
|
+
}
|
|
1927
|
+
__name(Section, "Section");
|
|
1928
|
+
function EndpointDoc({ endpoint, isLoadedInPlayground, onTryIt, schemaId }) {
|
|
1929
|
+
const scopedSchemaId = schemaId ?? endpoint.schemaId ?? null;
|
|
1930
|
+
const anchor = endpointAnchor(endpoint, scopedSchemaId);
|
|
1931
|
+
const pathParams = endpoint.parameters?.filter((p) => endpoint.path.includes(`{${p.name}}`)) ?? [];
|
|
1932
|
+
const queryParams = endpoint.parameters?.filter((p) => !endpoint.path.includes(`{${p.name}}`)) ?? [];
|
|
1933
|
+
const hasParameters = pathParams.length > 0 || queryParams.length > 0;
|
|
1934
|
+
const hasResponses = (endpoint.responses?.length ?? 0) > 0;
|
|
1935
|
+
const presentSections = [];
|
|
1936
|
+
if (hasParameters) presentSections.push("parameters");
|
|
1937
|
+
if (endpoint.requestBody) presentSections.push("requestBody");
|
|
1938
|
+
presentSections.push("codeSamples");
|
|
1939
|
+
if (hasResponses) presentSections.push("responses");
|
|
1940
|
+
return /* @__PURE__ */ jsx(EndpointDocProvider, { endpointId: anchor, method: endpoint.method, children: /* @__PURE__ */ jsxs(
|
|
1941
|
+
"section",
|
|
1942
|
+
{
|
|
1943
|
+
id: anchor,
|
|
1944
|
+
"data-endpoint-anchor": anchor,
|
|
1945
|
+
"data-schema-id": scopedSchemaId ?? "",
|
|
1946
|
+
className: "scroll-mt-24 py-10 first:pt-0",
|
|
1947
|
+
children: [
|
|
1948
|
+
/* @__PURE__ */ jsx(
|
|
1949
|
+
EndpointHeader,
|
|
1950
|
+
{
|
|
1951
|
+
endpoint,
|
|
1952
|
+
anchor,
|
|
1953
|
+
isLoadedInPlayground,
|
|
1954
|
+
onTryIt,
|
|
1955
|
+
presentSections
|
|
1956
|
+
}
|
|
1957
|
+
),
|
|
1958
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-8 space-y-5", children: [
|
|
1959
|
+
hasParameters && /* @__PURE__ */ jsx(
|
|
1960
|
+
Section,
|
|
1961
|
+
{
|
|
1962
|
+
id: "parameters",
|
|
1963
|
+
title: "Parameters",
|
|
1964
|
+
badge: pathParams.length + queryParams.length,
|
|
1965
|
+
children: /* @__PURE__ */ jsx(Parameters, { pathParams, queryParams })
|
|
1966
|
+
}
|
|
1967
|
+
),
|
|
1968
|
+
endpoint.requestBody && /* @__PURE__ */ jsx(Section, { id: "requestBody", title: "Request body", children: /* @__PURE__ */ jsx(RequestBody, { body: endpoint.requestBody }) }),
|
|
1969
|
+
/* @__PURE__ */ jsx(Section, { id: "codeSamples", title: "Code samples", children: /* @__PURE__ */ jsx(CodeSamples, { endpoint }) }),
|
|
1970
|
+
hasResponses && /* @__PURE__ */ jsx(
|
|
1971
|
+
Section,
|
|
1972
|
+
{
|
|
1973
|
+
id: "responses",
|
|
1974
|
+
title: "Responses",
|
|
1975
|
+
badge: endpoint.responses.length,
|
|
1976
|
+
children: /* @__PURE__ */ jsx(Responses, { responses: endpoint.responses })
|
|
1977
|
+
}
|
|
1978
|
+
)
|
|
1979
|
+
] })
|
|
1980
|
+
]
|
|
1981
|
+
}
|
|
1982
|
+
) });
|
|
1983
|
+
}
|
|
1984
|
+
__name(EndpointDoc, "EndpointDoc");
|
|
1985
|
+
var readNavbarOffset = /* @__PURE__ */ __name(() => {
|
|
1986
|
+
if (typeof document === "undefined") return 0;
|
|
1987
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue("--navbar-height");
|
|
1988
|
+
const parsed = parseInt(raw || "", 10);
|
|
1989
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
1990
|
+
}, "readNavbarOffset");
|
|
1991
|
+
var isSameEndpoint = /* @__PURE__ */ __name((a, b) => a !== null && a.method === b.method && a.path === b.path, "isSameEndpoint");
|
|
1992
|
+
function buildEndpointRow(ep, loadedEndpoint, schemaId) {
|
|
1993
|
+
const keySchema = schemaId ? `${schemaId}-` : "";
|
|
1994
|
+
return {
|
|
1995
|
+
key: `${keySchema}${ep.method}-${ep.path}`,
|
|
1996
|
+
endpoint: ep,
|
|
1997
|
+
isLoaded: isSameEndpoint(loadedEndpoint, ep),
|
|
1998
|
+
schemaId
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
__name(buildEndpointRow, "buildEndpointRow");
|
|
2002
|
+
function buildSchemaSectionVM(entry, selectedVersion, loadedEndpoint) {
|
|
2003
|
+
const title = entry.info?.title ?? entry.source.name;
|
|
2004
|
+
const version = entry.info?.version ?? null;
|
|
2005
|
+
const description = entry.info?.description ?? null;
|
|
2006
|
+
let state;
|
|
2007
|
+
if (entry.loading) {
|
|
2008
|
+
state = { kind: "loading" };
|
|
2009
|
+
} else if (entry.error) {
|
|
2010
|
+
state = { kind: "error", message: entry.error };
|
|
2011
|
+
} else {
|
|
2012
|
+
const visible = deduplicateEndpoints(entry.endpoints, selectedVersion);
|
|
2013
|
+
state = visible.length === 0 ? { kind: "empty" } : {
|
|
2014
|
+
kind: "ready",
|
|
2015
|
+
rows: visible.map((ep) => buildEndpointRow(ep, loadedEndpoint, entry.source.id))
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
return {
|
|
2019
|
+
schemaId: entry.source.id,
|
|
2020
|
+
title,
|
|
2021
|
+
version,
|
|
2022
|
+
description,
|
|
2023
|
+
state,
|
|
2024
|
+
rawSchema: entry.rawSchema,
|
|
2025
|
+
baseUrl: entry.resolvedBaseUrl,
|
|
2026
|
+
allEndpoints: entry.endpoints
|
|
2027
|
+
};
|
|
2028
|
+
}
|
|
2029
|
+
__name(buildSchemaSectionVM, "buildSchemaSectionVM");
|
|
2030
|
+
var DocsView = React12.forwardRef(/* @__PURE__ */ __name(function DocsView2(props, ref) {
|
|
2031
|
+
const scrollRef = useRef(null);
|
|
2032
|
+
const scrollTargetRef = useRef(null);
|
|
2033
|
+
const { onActiveChange } = props;
|
|
2034
|
+
useSectionHashRouter();
|
|
2035
|
+
const ensureScrollTarget = useCallback(() => {
|
|
2036
|
+
if (scrollTargetRef.current) return scrollTargetRef.current;
|
|
2037
|
+
if (!scrollRef.current) return null;
|
|
2038
|
+
scrollTargetRef.current = getScrollParent(scrollRef.current);
|
|
2039
|
+
return scrollTargetRef.current;
|
|
2040
|
+
}, []);
|
|
2041
|
+
const scrollToAnchor = useCallback(
|
|
2042
|
+
(anchor) => {
|
|
2043
|
+
const root = scrollRef.current;
|
|
2044
|
+
if (!root) return;
|
|
2045
|
+
const el = root.querySelector(`[data-endpoint-anchor="${anchor}"]`);
|
|
2046
|
+
if (!el) return;
|
|
2047
|
+
const target = ensureScrollTarget();
|
|
2048
|
+
if (!target) return;
|
|
2049
|
+
const navbar = readNavbarOffset();
|
|
2050
|
+
const top = el.getBoundingClientRect().top - getTargetTop(target) + getScrollTop(target) - navbar - 8;
|
|
2051
|
+
scrollTargetTo(target, top);
|
|
2052
|
+
},
|
|
2053
|
+
[ensureScrollTarget]
|
|
2054
|
+
);
|
|
2055
|
+
React12.useImperativeHandle(ref, () => ({ scrollToAnchor }), [scrollToAnchor]);
|
|
2056
|
+
useEffect(() => {
|
|
2057
|
+
const root = scrollRef.current;
|
|
2058
|
+
if (!root) return;
|
|
2059
|
+
const target = ensureScrollTarget();
|
|
2060
|
+
if (!target) return;
|
|
2061
|
+
let rafId = 0;
|
|
2062
|
+
let lastActive = null;
|
|
2063
|
+
const compute = /* @__PURE__ */ __name(() => {
|
|
2064
|
+
rafId = 0;
|
|
2065
|
+
const sections = root.querySelectorAll("[data-endpoint-anchor]");
|
|
2066
|
+
if (sections.length === 0) return;
|
|
2067
|
+
const navbar = readNavbarOffset();
|
|
2068
|
+
const viewportTop = getTargetTop(target);
|
|
2069
|
+
const threshold = viewportTop + navbar + getViewportHeight(target) * 0.25;
|
|
2070
|
+
let active = null;
|
|
2071
|
+
for (const s of Array.from(sections)) {
|
|
2072
|
+
const top = s.getBoundingClientRect().top;
|
|
2073
|
+
if (top <= threshold) {
|
|
2074
|
+
active = s;
|
|
2075
|
+
} else {
|
|
2076
|
+
break;
|
|
2077
|
+
}
|
|
2078
|
+
}
|
|
2079
|
+
const anchor = active?.dataset.endpointAnchor ?? null;
|
|
2080
|
+
if (anchor !== lastActive) {
|
|
2081
|
+
lastActive = anchor;
|
|
2082
|
+
onActiveChange(anchor, active?.dataset.schemaId || null);
|
|
2083
|
+
}
|
|
2084
|
+
}, "compute");
|
|
2085
|
+
const onScroll = /* @__PURE__ */ __name(() => {
|
|
2086
|
+
if (rafId) return;
|
|
2087
|
+
rafId = requestAnimationFrame(compute);
|
|
2088
|
+
}, "onScroll");
|
|
2089
|
+
compute();
|
|
2090
|
+
target.addEventListener("scroll", onScroll, { passive: true });
|
|
2091
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
2092
|
+
return () => {
|
|
2093
|
+
target.removeEventListener("scroll", onScroll);
|
|
2094
|
+
window.removeEventListener("resize", onScroll);
|
|
2095
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
2096
|
+
};
|
|
2097
|
+
}, [onActiveChange, ensureScrollTarget, props]);
|
|
2098
|
+
if (props.grouping === "sections") {
|
|
2099
|
+
return /* @__PURE__ */ jsx(SectionsBody, { scrollRef, ...props });
|
|
2100
|
+
}
|
|
2101
|
+
return /* @__PURE__ */ jsx(SelectorBody, { scrollRef, ...props });
|
|
2102
|
+
}, "DocsView"));
|
|
2103
|
+
function SelectorBody({
|
|
2104
|
+
scrollRef,
|
|
2105
|
+
info,
|
|
2106
|
+
rawSchema,
|
|
2107
|
+
resolvedBaseUrl,
|
|
2108
|
+
endpoints,
|
|
2109
|
+
selectedVersion,
|
|
2110
|
+
loadedEndpoint,
|
|
2111
|
+
onTryEndpoint
|
|
2112
|
+
}) {
|
|
2113
|
+
const visibleEndpoints = useMemo(
|
|
2114
|
+
() => deduplicateEndpoints(endpoints, selectedVersion),
|
|
2115
|
+
[endpoints, selectedVersion]
|
|
2116
|
+
);
|
|
2117
|
+
const rows = useMemo(
|
|
2118
|
+
() => visibleEndpoints.map((ep) => buildEndpointRow(ep, loadedEndpoint, ep.schemaId ?? null)),
|
|
2119
|
+
[visibleEndpoints, loadedEndpoint]
|
|
2120
|
+
);
|
|
2121
|
+
const isEmpty = rows.length === 0;
|
|
2122
|
+
return /* @__PURE__ */ jsx("div", { ref: scrollRef, children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-[860px] px-6 md:px-10 lg:px-14 py-12", children: [
|
|
2123
|
+
info && /* @__PURE__ */ jsx(
|
|
2124
|
+
ApiIntroSection,
|
|
2125
|
+
{
|
|
2126
|
+
info,
|
|
2127
|
+
schema: rawSchema,
|
|
2128
|
+
endpoints: visibleEndpoints,
|
|
2129
|
+
resolvedBaseUrl
|
|
2130
|
+
}
|
|
2131
|
+
),
|
|
2132
|
+
isEmpty ? /* @__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: rows.map((row) => /* @__PURE__ */ jsx(
|
|
2133
|
+
EndpointDoc,
|
|
2134
|
+
{
|
|
2135
|
+
endpoint: row.endpoint,
|
|
2136
|
+
isLoadedInPlayground: row.isLoaded,
|
|
2137
|
+
onTryIt: () => onTryEndpoint(row.endpoint),
|
|
2138
|
+
schemaId: row.schemaId
|
|
2139
|
+
},
|
|
2140
|
+
row.key
|
|
2141
|
+
)) })
|
|
2142
|
+
] }) });
|
|
2143
|
+
}
|
|
2144
|
+
__name(SelectorBody, "SelectorBody");
|
|
2145
|
+
function SectionsBody({
|
|
2146
|
+
scrollRef,
|
|
2147
|
+
schemasData,
|
|
2148
|
+
selectedVersion,
|
|
2149
|
+
loadedEndpoint,
|
|
2150
|
+
onTryEndpoint
|
|
2151
|
+
}) {
|
|
2152
|
+
const sections = useMemo(
|
|
2153
|
+
() => schemasData.map((e) => buildSchemaSectionVM(e, selectedVersion, loadedEndpoint)),
|
|
2154
|
+
[schemasData, selectedVersion, loadedEndpoint]
|
|
2155
|
+
);
|
|
2156
|
+
return /* @__PURE__ */ jsx("div", { ref: scrollRef, children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-[860px] px-6 md:px-10 lg:px-14 py-12 space-y-16", children: sections.map((section) => /* @__PURE__ */ jsx(SchemaSectionView, { section, onTryEndpoint }, section.schemaId)) }) });
|
|
2157
|
+
}
|
|
2158
|
+
__name(SectionsBody, "SectionsBody");
|
|
2159
|
+
var SchemaSectionView = React12.memo(/* @__PURE__ */ __name(function SchemaSectionView2({
|
|
2160
|
+
section,
|
|
2161
|
+
onTryEndpoint
|
|
2162
|
+
}) {
|
|
2163
|
+
const canCopy = section.rawSchema !== null && section.allEndpoints.length > 0;
|
|
2164
|
+
return /* @__PURE__ */ jsxs("section", { "data-schema-anchor": section.schemaId, className: "scroll-mt-20", children: [
|
|
2165
|
+
/* @__PURE__ */ jsxs("header", { className: "mb-8 pb-4 border-b", children: [
|
|
2166
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
|
|
2167
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-3 min-w-0", children: [
|
|
2168
|
+
/* @__PURE__ */ jsx("h2", { className: "text-2xl font-semibold tracking-tight", children: section.title }),
|
|
2169
|
+
section.version && /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground/70", children: [
|
|
2170
|
+
"v",
|
|
2171
|
+
section.version
|
|
2172
|
+
] })
|
|
2173
|
+
] }),
|
|
2174
|
+
canCopy && /* @__PURE__ */ jsx(
|
|
2175
|
+
SchemaCopyMenu,
|
|
2176
|
+
{
|
|
2177
|
+
schema: section.rawSchema,
|
|
2178
|
+
endpoints: section.allEndpoints,
|
|
2179
|
+
baseUrl: section.baseUrl
|
|
2180
|
+
}
|
|
2181
|
+
)
|
|
2182
|
+
] }),
|
|
2183
|
+
section.description && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground whitespace-pre-wrap", children: section.description })
|
|
2184
|
+
] }),
|
|
2185
|
+
/* @__PURE__ */ jsx(SchemaSectionStateView, { section, onTryEndpoint })
|
|
2186
|
+
] });
|
|
2187
|
+
}, "SchemaSectionView"));
|
|
2188
|
+
function SchemaSectionStateView({
|
|
2189
|
+
section,
|
|
2190
|
+
onTryEndpoint
|
|
2191
|
+
}) {
|
|
2192
|
+
switch (section.state.kind) {
|
|
2193
|
+
case "loading":
|
|
2194
|
+
return /* @__PURE__ */ jsxs("div", { className: "py-8 text-center text-sm text-muted-foreground", children: [
|
|
2195
|
+
"Loading ",
|
|
2196
|
+
section.title,
|
|
2197
|
+
"\u2026"
|
|
2198
|
+
] });
|
|
2199
|
+
case "error":
|
|
2200
|
+
return /* @__PURE__ */ jsxs("div", { className: "py-8 text-center text-sm text-destructive", children: [
|
|
2201
|
+
"Failed to load ",
|
|
2202
|
+
section.title,
|
|
2203
|
+
": ",
|
|
2204
|
+
section.state.message
|
|
2205
|
+
] });
|
|
2206
|
+
case "empty":
|
|
2207
|
+
return /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: "No endpoints in this schema." });
|
|
2208
|
+
case "ready":
|
|
2209
|
+
return /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/60", children: section.state.rows.map((row) => /* @__PURE__ */ jsx(
|
|
2210
|
+
EndpointDoc,
|
|
2211
|
+
{
|
|
2212
|
+
endpoint: row.endpoint,
|
|
2213
|
+
isLoadedInPlayground: row.isLoaded,
|
|
2214
|
+
onTryIt: () => onTryEndpoint(row.endpoint),
|
|
2215
|
+
schemaId: row.schemaId
|
|
2216
|
+
},
|
|
2217
|
+
row.key
|
|
2218
|
+
)) });
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
__name(SchemaSectionStateView, "SchemaSectionStateView");
|
|
2222
|
+
var MAX_DEPTH2 = 6;
|
|
2223
|
+
function defaultForSchema(schema) {
|
|
2224
|
+
if (!schema) return null;
|
|
2225
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
2226
|
+
switch (schema.type) {
|
|
2227
|
+
case "object": {
|
|
2228
|
+
const out = {};
|
|
2229
|
+
for (const [k, v] of Object.entries(schema.properties ?? {})) {
|
|
2230
|
+
out[k] = defaultForSchema(v);
|
|
2231
|
+
}
|
|
2232
|
+
return out;
|
|
2233
|
+
}
|
|
2234
|
+
case "array":
|
|
2235
|
+
return [];
|
|
2236
|
+
case "integer":
|
|
2237
|
+
case "number":
|
|
2238
|
+
return 0;
|
|
2239
|
+
case "boolean":
|
|
2240
|
+
return false;
|
|
2241
|
+
case "string":
|
|
2242
|
+
return "";
|
|
2243
|
+
default:
|
|
2244
|
+
if (schema.properties) {
|
|
2245
|
+
const out = {};
|
|
2246
|
+
for (const [k, v] of Object.entries(schema.properties)) {
|
|
2247
|
+
out[k] = defaultForSchema(v);
|
|
2248
|
+
}
|
|
2249
|
+
return out;
|
|
2250
|
+
}
|
|
2251
|
+
return "";
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
__name(defaultForSchema, "defaultForSchema");
|
|
2255
|
+
function BodyFormEditor({ schema, value, onChange }) {
|
|
2256
|
+
return /* @__PURE__ */ jsx(
|
|
2257
|
+
SchemaField,
|
|
2258
|
+
{
|
|
2259
|
+
schema,
|
|
2260
|
+
value,
|
|
2261
|
+
onChange,
|
|
2262
|
+
depth: 0,
|
|
2263
|
+
required: false
|
|
2264
|
+
}
|
|
2265
|
+
);
|
|
2266
|
+
}
|
|
2267
|
+
__name(BodyFormEditor, "BodyFormEditor");
|
|
2268
|
+
function SchemaField({ schema, value, onChange, depth, required, label }) {
|
|
2269
|
+
if (depth > MAX_DEPTH2) {
|
|
2270
|
+
return /* @__PURE__ */ jsx(RawJsonField, { label, value, onChange });
|
|
2271
|
+
}
|
|
2272
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
2273
|
+
return /* @__PURE__ */ jsx(EnumField, { schema, value, onChange, label, required });
|
|
2274
|
+
}
|
|
2275
|
+
switch (schema.type) {
|
|
2276
|
+
case "object":
|
|
2277
|
+
return /* @__PURE__ */ jsx(ObjectField, { schema, value, onChange, depth, label });
|
|
2278
|
+
case "array":
|
|
2279
|
+
return /* @__PURE__ */ jsx(ArrayField, { schema, value, onChange, depth, label, required });
|
|
2280
|
+
case "boolean":
|
|
2281
|
+
return /* @__PURE__ */ jsx(BooleanField, { value, onChange, label, schema, required });
|
|
2282
|
+
case "integer":
|
|
2283
|
+
case "number":
|
|
2284
|
+
return /* @__PURE__ */ jsx(NumberField, { value, onChange, label, schema, required });
|
|
2285
|
+
case "string":
|
|
2286
|
+
default:
|
|
2287
|
+
if (!schema.type && schema.properties) {
|
|
2288
|
+
return /* @__PURE__ */ jsx(ObjectField, { schema, value, onChange, depth, label });
|
|
2289
|
+
}
|
|
2290
|
+
return /* @__PURE__ */ jsx(StringField, { value, onChange, label, schema, required });
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
__name(SchemaField, "SchemaField");
|
|
2294
|
+
function FieldHeader({
|
|
2295
|
+
label,
|
|
2296
|
+
type,
|
|
2297
|
+
required,
|
|
2298
|
+
description
|
|
2299
|
+
}) {
|
|
2300
|
+
if (!label) return null;
|
|
2301
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
2302
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
|
|
2303
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
|
|
2304
|
+
required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
|
|
2305
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: type })
|
|
2306
|
+
] }),
|
|
2307
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/70 leading-snug", children: description })
|
|
2308
|
+
] });
|
|
2309
|
+
}
|
|
2310
|
+
__name(FieldHeader, "FieldHeader");
|
|
2311
|
+
function StringField({
|
|
2312
|
+
value,
|
|
2313
|
+
onChange,
|
|
2314
|
+
label,
|
|
2315
|
+
schema,
|
|
2316
|
+
required
|
|
2317
|
+
}) {
|
|
2318
|
+
const stringValue = typeof value === "string" ? value : value == null ? "" : String(value);
|
|
2319
|
+
const placeholder = schema.format ? `${schema.type ?? "string"} (${schema.format})` : schema.description || schema.type || "string";
|
|
2320
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2321
|
+
/* @__PURE__ */ jsx(FieldHeader, { label, type: schema.format ? `string (${schema.format})` : "string", required, description: schema.description }),
|
|
2322
|
+
/* @__PURE__ */ jsx(
|
|
2323
|
+
Input,
|
|
2324
|
+
{
|
|
2325
|
+
value: stringValue,
|
|
2326
|
+
onChange: (e) => onChange(e.target.value),
|
|
2327
|
+
placeholder,
|
|
2328
|
+
className: "h-8 text-xs font-mono"
|
|
2329
|
+
}
|
|
2330
|
+
)
|
|
2331
|
+
] });
|
|
2332
|
+
}
|
|
2333
|
+
__name(StringField, "StringField");
|
|
2334
|
+
function NumberField({
|
|
2335
|
+
value,
|
|
2336
|
+
onChange,
|
|
2337
|
+
label,
|
|
2338
|
+
schema,
|
|
2339
|
+
required
|
|
2340
|
+
}) {
|
|
2341
|
+
const raw = value == null ? "" : String(value);
|
|
2342
|
+
const type = schema.type === "integer" ? "integer" : "number";
|
|
2343
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2344
|
+
/* @__PURE__ */ jsx(FieldHeader, { label, type: schema.format ? `${type} (${schema.format})` : type, required, description: schema.description }),
|
|
2345
|
+
/* @__PURE__ */ jsx(
|
|
2346
|
+
Input,
|
|
2347
|
+
{
|
|
2348
|
+
type: "number",
|
|
2349
|
+
value: raw,
|
|
2350
|
+
onChange: (e) => {
|
|
2351
|
+
const v = e.target.value;
|
|
2352
|
+
if (v === "") return onChange(null);
|
|
2353
|
+
const n = schema.type === "integer" ? parseInt(v, 10) : parseFloat(v);
|
|
2354
|
+
onChange(Number.isNaN(n) ? null : n);
|
|
2355
|
+
},
|
|
2356
|
+
placeholder: type,
|
|
2357
|
+
className: "h-8 text-xs font-mono"
|
|
2358
|
+
}
|
|
2359
|
+
)
|
|
2360
|
+
] });
|
|
2361
|
+
}
|
|
2362
|
+
__name(NumberField, "NumberField");
|
|
2363
|
+
function BooleanField({
|
|
2364
|
+
value,
|
|
2365
|
+
onChange,
|
|
2366
|
+
label,
|
|
2367
|
+
schema,
|
|
2368
|
+
required
|
|
2369
|
+
}) {
|
|
2370
|
+
const bool = value === true;
|
|
2371
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
2372
|
+
/* @__PURE__ */ jsx(FieldHeader, { label, type: "boolean", required, description: schema.description }),
|
|
2373
|
+
/* @__PURE__ */ jsx(Switch, { checked: bool, onCheckedChange: onChange, className: "mt-0.5 shrink-0" })
|
|
2374
|
+
] });
|
|
2375
|
+
}
|
|
2376
|
+
__name(BooleanField, "BooleanField");
|
|
2377
|
+
function EnumField({
|
|
2378
|
+
schema,
|
|
2379
|
+
value,
|
|
2380
|
+
onChange,
|
|
2381
|
+
label,
|
|
2382
|
+
required
|
|
2383
|
+
}) {
|
|
2384
|
+
const options = (schema.enum ?? []).map((v) => ({
|
|
2385
|
+
value: String(v),
|
|
2386
|
+
label: String(v)
|
|
2387
|
+
}));
|
|
2388
|
+
const strValue = value == null ? "" : String(value);
|
|
2389
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2390
|
+
/* @__PURE__ */ jsx(FieldHeader, { label, type: `${schema.type ?? "enum"} enum`, required, description: schema.description }),
|
|
2391
|
+
/* @__PURE__ */ jsx(
|
|
2392
|
+
Combobox,
|
|
2393
|
+
{
|
|
2394
|
+
options,
|
|
2395
|
+
value: strValue,
|
|
2396
|
+
onValueChange: (v) => {
|
|
2397
|
+
if (schema.type === "integer") onChange(parseInt(v, 10));
|
|
2398
|
+
else if (schema.type === "number") onChange(parseFloat(v));
|
|
2399
|
+
else onChange(v);
|
|
2400
|
+
},
|
|
2401
|
+
placeholder: "Select\u2026",
|
|
2402
|
+
searchPlaceholder: "Search\u2026",
|
|
2403
|
+
className: "h-8 text-xs"
|
|
2404
|
+
}
|
|
2405
|
+
)
|
|
2406
|
+
] });
|
|
2407
|
+
}
|
|
2408
|
+
__name(EnumField, "EnumField");
|
|
2409
|
+
function RawJsonField({
|
|
2410
|
+
label,
|
|
2411
|
+
value,
|
|
2412
|
+
onChange
|
|
2413
|
+
}) {
|
|
2414
|
+
const [text, setText] = React12.useState(() => JSON.stringify(value ?? null, null, 2));
|
|
2415
|
+
React12.useEffect(() => {
|
|
2416
|
+
setText(JSON.stringify(value ?? null, null, 2));
|
|
2417
|
+
}, [value]);
|
|
2418
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2419
|
+
label && /* @__PURE__ */ jsxs(SectionLabel, { children: [
|
|
2420
|
+
label,
|
|
2421
|
+
" (raw)"
|
|
2422
|
+
] }),
|
|
2423
|
+
/* @__PURE__ */ jsx(
|
|
2424
|
+
Textarea,
|
|
2425
|
+
{
|
|
2426
|
+
value: text,
|
|
2427
|
+
onChange: (e) => {
|
|
2428
|
+
setText(e.target.value);
|
|
2429
|
+
try {
|
|
2430
|
+
onChange(JSON.parse(e.target.value));
|
|
2431
|
+
} catch {
|
|
2432
|
+
}
|
|
2433
|
+
},
|
|
2434
|
+
className: "font-mono text-[11px] min-h-[80px]",
|
|
2435
|
+
rows: 4
|
|
2436
|
+
}
|
|
2437
|
+
)
|
|
2438
|
+
] });
|
|
2439
|
+
}
|
|
2440
|
+
__name(RawJsonField, "RawJsonField");
|
|
2441
|
+
function ObjectField({
|
|
2442
|
+
schema,
|
|
2443
|
+
value,
|
|
2444
|
+
onChange,
|
|
2445
|
+
depth,
|
|
2446
|
+
label
|
|
2447
|
+
}) {
|
|
2448
|
+
const obj = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2449
|
+
const required = new Set(schema.required ?? []);
|
|
2450
|
+
const entries = Object.entries(schema.properties ?? {});
|
|
2451
|
+
const setKey = useCallback(
|
|
2452
|
+
(key) => (next) => {
|
|
2453
|
+
onChange({ ...obj, [key]: next });
|
|
2454
|
+
},
|
|
2455
|
+
[obj, onChange]
|
|
2456
|
+
);
|
|
2457
|
+
if (entries.length === 0) {
|
|
2458
|
+
return /* @__PURE__ */ jsx(RawJsonField, { label, value: obj, onChange });
|
|
2459
|
+
}
|
|
2460
|
+
const wrapperClass = depth === 0 ? "space-y-3" : "space-y-2 border-l-2 border-border/60 pl-3 ml-px";
|
|
2461
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2462
|
+
label && depth > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
|
|
2463
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
|
|
2464
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: "object" })
|
|
2465
|
+
] }),
|
|
2466
|
+
/* @__PURE__ */ jsx("div", { className: cn(wrapperClass), children: entries.map(([key, subSchema]) => /* @__PURE__ */ jsx(
|
|
2467
|
+
SchemaField,
|
|
2468
|
+
{
|
|
2469
|
+
schema: subSchema,
|
|
2470
|
+
value: obj[key],
|
|
2471
|
+
onChange: setKey(key),
|
|
2472
|
+
depth: depth + 1,
|
|
2473
|
+
required: required.has(key),
|
|
2474
|
+
label: key
|
|
2475
|
+
},
|
|
2476
|
+
key
|
|
2477
|
+
)) })
|
|
2478
|
+
] });
|
|
2479
|
+
}
|
|
2480
|
+
__name(ObjectField, "ObjectField");
|
|
2481
|
+
function ArrayField({
|
|
2482
|
+
schema,
|
|
2483
|
+
value,
|
|
2484
|
+
onChange,
|
|
2485
|
+
depth,
|
|
2486
|
+
label,
|
|
2487
|
+
required
|
|
2488
|
+
}) {
|
|
2489
|
+
const arr = Array.isArray(value) ? value : [];
|
|
2490
|
+
const items = schema.items ?? { type: "string" };
|
|
2491
|
+
const addItem = /* @__PURE__ */ __name(() => onChange([...arr, defaultForSchema(items)]), "addItem");
|
|
2492
|
+
const removeAt = /* @__PURE__ */ __name((i) => onChange(arr.filter((_, idx) => idx !== i)), "removeAt");
|
|
2493
|
+
const setAt = /* @__PURE__ */ __name((i) => (next) => onChange(arr.map((v, idx) => idx === i ? next : v)), "setAt");
|
|
2494
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2495
|
+
label && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-1.5", children: [
|
|
2496
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: label }),
|
|
2497
|
+
required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
|
|
2498
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: `array<${items.type ?? "any"}>` })
|
|
2499
|
+
] }),
|
|
2500
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2 border-l-2 border-border/60 pl-3 ml-px", children: [
|
|
2501
|
+
arr.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/50 italic", children: "Empty array" }),
|
|
2502
|
+
arr.map((v, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
|
|
2503
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: /* @__PURE__ */ jsx(
|
|
2504
|
+
SchemaField,
|
|
2505
|
+
{
|
|
2506
|
+
schema: items,
|
|
2507
|
+
value: v,
|
|
2508
|
+
onChange: setAt(i),
|
|
2509
|
+
depth: depth + 1,
|
|
2510
|
+
required: false,
|
|
2511
|
+
label: `${label ?? ""}[${i}]`
|
|
2512
|
+
}
|
|
2513
|
+
) }),
|
|
2514
|
+
/* @__PURE__ */ jsx(
|
|
2515
|
+
"button",
|
|
2516
|
+
{
|
|
2517
|
+
type: "button",
|
|
2518
|
+
onClick: () => removeAt(i),
|
|
2519
|
+
title: "Remove item",
|
|
2520
|
+
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",
|
|
2521
|
+
children: /* @__PURE__ */ jsx(Minus, { className: "h-3.5 w-3.5" })
|
|
2522
|
+
}
|
|
2523
|
+
)
|
|
2524
|
+
] }, i)),
|
|
2525
|
+
/* @__PURE__ */ jsxs(
|
|
2526
|
+
"button",
|
|
2527
|
+
{
|
|
2528
|
+
type: "button",
|
|
2529
|
+
onClick: addItem,
|
|
2530
|
+
className: "inline-flex items-center gap-1.5 text-[10px] text-muted-foreground hover:text-foreground transition-colors py-1",
|
|
2531
|
+
children: [
|
|
2532
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
|
|
2533
|
+
"Add item"
|
|
2534
|
+
]
|
|
2535
|
+
}
|
|
2536
|
+
)
|
|
2537
|
+
] })
|
|
2538
|
+
] });
|
|
2539
|
+
}
|
|
2540
|
+
__name(ArrayField, "ArrayField");
|
|
2541
|
+
function EndpointResetButton() {
|
|
2542
|
+
const { state, setParameters, setRequestBody } = usePlaygroundContext();
|
|
2543
|
+
const ep = state.selectedEndpoint;
|
|
2544
|
+
const { reset } = useEndpointDraft(state.activeSchemaId, ep);
|
|
2545
|
+
const hasDraft = Object.keys(state.parameters).length > 0 || state.requestBody.length > 0;
|
|
2546
|
+
const onClick = useCallback(() => {
|
|
2547
|
+
setParameters({});
|
|
2548
|
+
setRequestBody("");
|
|
2549
|
+
reset();
|
|
2550
|
+
}, [setParameters, setRequestBody, reset]);
|
|
2551
|
+
if (!ep || !hasDraft) return null;
|
|
2552
|
+
return /* @__PURE__ */ jsxs(
|
|
2553
|
+
"button",
|
|
2554
|
+
{
|
|
2555
|
+
type: "button",
|
|
2556
|
+
onClick,
|
|
2557
|
+
title: "Reset parameters & body (keeps auth)",
|
|
2558
|
+
className: cn(
|
|
2559
|
+
"inline-flex items-center gap-1 text-[10px] text-muted-foreground",
|
|
2560
|
+
"hover:text-foreground transition-colors"
|
|
2561
|
+
),
|
|
2562
|
+
children: [
|
|
2563
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "h-2.5 w-2.5" }),
|
|
2564
|
+
"Reset"
|
|
2565
|
+
]
|
|
2566
|
+
}
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
__name(EndpointResetButton, "EndpointResetButton");
|
|
2570
|
+
function ParamFields({ label, params }) {
|
|
2571
|
+
const { state, setParameters } = usePlaygroundContext();
|
|
2572
|
+
function handleChange(name, value) {
|
|
2573
|
+
setParameters({ ...state.parameters, [name]: value });
|
|
2574
|
+
}
|
|
2575
|
+
__name(handleChange, "handleChange");
|
|
2576
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2577
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: label }),
|
|
2578
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: params.map((p) => {
|
|
2579
|
+
const value = state.parameters[p.name] ?? "";
|
|
2580
|
+
const placeholder = p.description || p.name;
|
|
2581
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2582
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
2583
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[11px] text-foreground/80", children: p.name }),
|
|
2584
|
+
p.required && /* @__PURE__ */ jsx("span", { className: "text-[9px] text-destructive font-bold leading-none", children: "*" }),
|
|
2585
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] text-muted-foreground/50", children: p.type })
|
|
2586
|
+
] }),
|
|
2587
|
+
/* @__PURE__ */ jsx(
|
|
2588
|
+
Input,
|
|
2589
|
+
{
|
|
2590
|
+
value,
|
|
2591
|
+
onChange: (e) => handleChange(p.name, e.target.value),
|
|
2592
|
+
placeholder,
|
|
2593
|
+
className: "h-8 text-xs font-mono"
|
|
2594
|
+
}
|
|
2595
|
+
)
|
|
2596
|
+
] }, p.name);
|
|
2597
|
+
}) })
|
|
2598
|
+
] });
|
|
2599
|
+
}
|
|
2600
|
+
__name(ParamFields, "ParamFields");
|
|
2601
|
+
function RequestPanel() {
|
|
2602
|
+
const {
|
|
2603
|
+
state,
|
|
2604
|
+
apiKeys,
|
|
2605
|
+
apiKeysLoading,
|
|
2606
|
+
setRequestBody,
|
|
2607
|
+
setRequestHeaders,
|
|
2608
|
+
setSelectedApiKey,
|
|
2609
|
+
setManualApiToken,
|
|
2610
|
+
sendRequest
|
|
2611
|
+
} = usePlaygroundContext();
|
|
2612
|
+
const apiKeyOptions = useMemo(
|
|
2613
|
+
() => apiKeys.map((k) => ({
|
|
2614
|
+
value: k.id,
|
|
2615
|
+
label: k.name || "Unnamed key",
|
|
2616
|
+
// Surface the first 8 chars of the secret so the user
|
|
2617
|
+
// can tell two similarly-named keys apart at a glance.
|
|
2618
|
+
description: k.secret ? `${k.secret.slice(0, 8)}\u2026` : void 0
|
|
2619
|
+
})),
|
|
2620
|
+
[apiKeys]
|
|
2621
|
+
);
|
|
2622
|
+
const hasApiKeys = apiKeyOptions.length > 0;
|
|
2623
|
+
const ep = state.selectedEndpoint;
|
|
2624
|
+
const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
|
|
2625
|
+
const curlCommand = useMemo(() => {
|
|
2626
|
+
if (!state.requestUrl) return "";
|
|
2627
|
+
const absoluteUrl = resolveAbsolute(state.requestUrl);
|
|
2628
|
+
const apiKey = state.selectedApiKey ? findApiKeyById(apiKeys, state.selectedApiKey) : null;
|
|
2629
|
+
const hdrs = parseRequestHeaders(state.requestHeaders);
|
|
2630
|
+
if (apiKey) hdrs["X-API-Key"] = apiKey.secret || apiKey.id;
|
|
2631
|
+
let cmd = `curl -X ${state.requestMethod} "${absoluteUrl}"`;
|
|
2632
|
+
Object.entries(hdrs).forEach(([k, v]) => {
|
|
2633
|
+
cmd += ` \\
|
|
2634
|
+
-H "${k}: ${v}"`;
|
|
2635
|
+
});
|
|
2636
|
+
if (state.requestBody && state.requestMethod !== "GET" && isJsonValid) {
|
|
2637
|
+
cmd += ` \\
|
|
2638
|
+
-d '${state.requestBody}'`;
|
|
2639
|
+
}
|
|
2640
|
+
return cmd;
|
|
2641
|
+
}, [state, apiKeys, isJsonValid]);
|
|
2642
|
+
const pathParams = useMemo(
|
|
2643
|
+
() => ep?.parameters?.filter((p) => ep.path.includes(`{${p.name}}`)) ?? [],
|
|
2644
|
+
[ep]
|
|
2645
|
+
);
|
|
2646
|
+
const queryParams = useMemo(
|
|
2647
|
+
() => ep?.parameters?.filter((p) => !ep.path.includes(`{${p.name}}`)) ?? [],
|
|
2648
|
+
[ep]
|
|
2649
|
+
);
|
|
2650
|
+
state.loading || !state.requestUrl || !isJsonValid;
|
|
2651
|
+
const displayUrl = resolveAbsolute(state.requestUrl || ep?.path || "");
|
|
2652
|
+
const hasBody = ep?.method !== "GET";
|
|
2653
|
+
const bodyType = ep?.requestBody?.type ?? "";
|
|
2654
|
+
const hasPathParams = pathParams.length > 0;
|
|
2655
|
+
const hasQueryParams = queryParams.length > 0;
|
|
2656
|
+
const hasCurl = Boolean(curlCommand);
|
|
2657
|
+
const epPath = ep ? relativePath(ep.path) : "";
|
|
2658
|
+
const urlChanged = displayUrl !== "" && displayUrl !== epPath;
|
|
2659
|
+
if (!ep) {
|
|
2660
|
+
return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: "Select an endpoint to build a request" });
|
|
2661
|
+
}
|
|
2662
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2663
|
+
(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: [
|
|
2664
|
+
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" }),
|
|
2665
|
+
/* @__PURE__ */ jsx(EndpointResetButton, {})
|
|
2666
|
+
] }),
|
|
2667
|
+
/* @__PURE__ */ jsxs(ScrollArea, { className: "px-4 py-3 space-y-3", children: [
|
|
2668
|
+
hasPathParams && /* @__PURE__ */ jsx(ParamFields, { label: "Path Parameters", params: pathParams }),
|
|
2669
|
+
hasQueryParams && /* @__PURE__ */ jsx(ParamFields, { label: "Query Parameters", params: queryParams }),
|
|
2670
|
+
hasBody && /* @__PURE__ */ jsx(
|
|
2671
|
+
BodySection,
|
|
2672
|
+
{
|
|
2673
|
+
schema: ep.requestBody?.schema,
|
|
2674
|
+
bodyType,
|
|
2675
|
+
bodyDescription: ep.requestBody?.description,
|
|
2676
|
+
value: state.requestBody,
|
|
2677
|
+
onChange: setRequestBody,
|
|
2678
|
+
isJsonValid
|
|
2679
|
+
}
|
|
2680
|
+
),
|
|
2681
|
+
/* @__PURE__ */ jsx(
|
|
2682
|
+
CollapsibleSection,
|
|
2683
|
+
{
|
|
2684
|
+
label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
2685
|
+
/* @__PURE__ */ jsx(Key, { className: "h-2.5 w-2.5" }),
|
|
2686
|
+
"Auth & Headers"
|
|
2687
|
+
] }),
|
|
2688
|
+
children: /* @__PURE__ */ jsxs("div", { className: "space-y-3 pt-2", children: [
|
|
2689
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2690
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "API Key" }),
|
|
2691
|
+
/* @__PURE__ */ jsx(
|
|
2692
|
+
Combobox,
|
|
2693
|
+
{
|
|
2694
|
+
options: apiKeyOptions,
|
|
2695
|
+
value: state.selectedApiKey ?? "",
|
|
2696
|
+
onValueChange: (v) => setSelectedApiKey(v || null),
|
|
2697
|
+
placeholder: apiKeysLoading ? "Loading keys\u2026" : hasApiKeys ? "Select an API key" : "No API keys yet",
|
|
2698
|
+
searchPlaceholder: "Search keys\u2026",
|
|
2699
|
+
emptyText: "No matching key",
|
|
2700
|
+
disabled: apiKeysLoading || !hasApiKeys,
|
|
2701
|
+
className: "h-8"
|
|
2702
|
+
}
|
|
2703
|
+
),
|
|
2704
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
2705
|
+
"Picks are sent via the",
|
|
2706
|
+
" ",
|
|
2707
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "X-API-Key" }),
|
|
2708
|
+
" header."
|
|
2709
|
+
] })
|
|
2710
|
+
] }),
|
|
2711
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2712
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "Bearer Token" }),
|
|
2713
|
+
/* @__PURE__ */ jsx(
|
|
2714
|
+
Input,
|
|
2715
|
+
{
|
|
2716
|
+
type: "password",
|
|
2717
|
+
placeholder: "Leave empty to use JWT from localStorage",
|
|
2718
|
+
value: state.manualApiToken,
|
|
2719
|
+
onChange: (e) => setManualApiToken(e.target.value),
|
|
2720
|
+
className: "font-mono text-xs h-8"
|
|
2721
|
+
}
|
|
2722
|
+
)
|
|
2723
|
+
] }),
|
|
2724
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
2725
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "Headers" }),
|
|
2726
|
+
/* @__PURE__ */ jsx(
|
|
2727
|
+
Textarea,
|
|
2728
|
+
{
|
|
2729
|
+
value: state.requestHeaders,
|
|
2730
|
+
onChange: (e) => setRequestHeaders(e.target.value),
|
|
2731
|
+
className: "font-mono text-[11px] min-h-[60px] resize-y",
|
|
2732
|
+
rows: 3
|
|
2733
|
+
}
|
|
2734
|
+
)
|
|
2735
|
+
] })
|
|
2736
|
+
] })
|
|
2737
|
+
}
|
|
2738
|
+
),
|
|
2739
|
+
hasCurl && /* @__PURE__ */ jsx(
|
|
2740
|
+
CollapsibleSection,
|
|
2741
|
+
{
|
|
2742
|
+
label: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
2743
|
+
/* @__PURE__ */ jsx(Terminal, { className: "h-2.5 w-2.5" }),
|
|
2744
|
+
"cURL"
|
|
2745
|
+
] }),
|
|
2746
|
+
children: /* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(
|
|
2747
|
+
PrettyCode_default,
|
|
2748
|
+
{
|
|
2749
|
+
data: curlCommand,
|
|
2750
|
+
language: "bash",
|
|
2751
|
+
isCompact: true,
|
|
2752
|
+
maxLines: 50
|
|
2753
|
+
}
|
|
2754
|
+
) })
|
|
2755
|
+
}
|
|
2756
|
+
),
|
|
2757
|
+
/* @__PURE__ */ jsx("div", { className: "h-4" })
|
|
2758
|
+
] })
|
|
2759
|
+
] });
|
|
2760
|
+
}
|
|
2761
|
+
__name(RequestPanel, "RequestPanel");
|
|
2762
|
+
function BodySection({ schema, bodyType, bodyDescription, value, onChange, isJsonValid }) {
|
|
2763
|
+
const hasSchema = !!schema;
|
|
2764
|
+
const [mode, setMode] = React12.useState(hasSchema ? "form" : "json");
|
|
2765
|
+
const parsed = React12.useMemo(() => {
|
|
2766
|
+
if (!value) return null;
|
|
2767
|
+
try {
|
|
2768
|
+
return JSON.parse(value);
|
|
2769
|
+
} catch {
|
|
2770
|
+
return null;
|
|
2771
|
+
}
|
|
2772
|
+
}, [value]);
|
|
2773
|
+
const handleFormChange = useCallback(
|
|
2774
|
+
(next) => {
|
|
2775
|
+
onChange(JSON.stringify(next, null, 2));
|
|
2776
|
+
},
|
|
2777
|
+
[onChange]
|
|
2778
|
+
);
|
|
2779
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2780
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
|
|
2781
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2 min-w-0", children: [
|
|
2782
|
+
/* @__PURE__ */ jsx(SectionLabel, { children: "Body" }),
|
|
2783
|
+
bodyType && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/40 font-mono", children: bodyType }),
|
|
2784
|
+
bodyDescription && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60 truncate", children: bodyDescription })
|
|
2785
|
+
] }),
|
|
2786
|
+
hasSchema && /* @__PURE__ */ jsxs("div", { className: "inline-flex rounded-md border overflow-hidden text-[10px]", children: [
|
|
2787
|
+
/* @__PURE__ */ jsx(ModeButton, { active: mode === "form", onClick: () => setMode("form"), children: "Form" }),
|
|
2788
|
+
/* @__PURE__ */ jsx(ModeButton, { active: mode === "json", onClick: () => setMode("json"), children: "JSON" })
|
|
2789
|
+
] }),
|
|
2790
|
+
mode === "json" && isJsonValid && value && /* @__PURE__ */ jsxs(
|
|
2791
|
+
"button",
|
|
2792
|
+
{
|
|
2793
|
+
type: "button",
|
|
2794
|
+
onClick: () => {
|
|
2795
|
+
try {
|
|
2796
|
+
onChange(JSON.stringify(JSON.parse(value), null, 2));
|
|
2797
|
+
} catch {
|
|
2798
|
+
}
|
|
2799
|
+
},
|
|
2800
|
+
className: "inline-flex items-center gap-1 text-[10px] text-muted-foreground hover:text-foreground transition-colors",
|
|
2801
|
+
children: [
|
|
2802
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "h-2.5 w-2.5" }),
|
|
2803
|
+
"Format"
|
|
2804
|
+
]
|
|
2805
|
+
}
|
|
2806
|
+
)
|
|
2807
|
+
] }),
|
|
2808
|
+
mode === "form" && hasSchema ? /* @__PURE__ */ jsx(
|
|
2809
|
+
BodyFormEditor,
|
|
2810
|
+
{
|
|
2811
|
+
schema,
|
|
2812
|
+
value: parsed,
|
|
2813
|
+
onChange: handleFormChange
|
|
2814
|
+
}
|
|
2815
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2816
|
+
/* @__PURE__ */ jsx(
|
|
2817
|
+
Textarea,
|
|
2818
|
+
{
|
|
2819
|
+
placeholder: '{\n "key": "value"\n}',
|
|
2820
|
+
value,
|
|
2821
|
+
onChange: (e) => onChange(e.target.value),
|
|
2822
|
+
className: cn(
|
|
2823
|
+
"font-mono text-[11px] min-h-[90px] resize-y",
|
|
2824
|
+
!isJsonValid && "border-destructive focus-visible:ring-destructive/30"
|
|
2825
|
+
),
|
|
2826
|
+
rows: 4
|
|
2827
|
+
}
|
|
2828
|
+
),
|
|
2829
|
+
!isJsonValid && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-destructive", children: "Invalid JSON" })
|
|
2830
|
+
] })
|
|
2831
|
+
] });
|
|
2832
|
+
}
|
|
2833
|
+
__name(BodySection, "BodySection");
|
|
2834
|
+
function ModeButton({
|
|
2835
|
+
active,
|
|
2836
|
+
onClick,
|
|
2837
|
+
children
|
|
2838
|
+
}) {
|
|
2839
|
+
return /* @__PURE__ */ jsx(
|
|
2840
|
+
"button",
|
|
2841
|
+
{
|
|
2842
|
+
type: "button",
|
|
2843
|
+
onClick,
|
|
2844
|
+
className: cn(
|
|
2845
|
+
"px-2 py-0.5 font-medium transition-colors",
|
|
2846
|
+
active ? "bg-primary/10 text-foreground" : "text-muted-foreground hover:text-foreground"
|
|
2847
|
+
),
|
|
2848
|
+
children
|
|
2849
|
+
}
|
|
2850
|
+
);
|
|
2851
|
+
}
|
|
2852
|
+
__name(ModeButton, "ModeButton");
|
|
2853
|
+
function looksLikeSpaShell(html) {
|
|
2854
|
+
const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
2855
|
+
const bodyContent = (bodyMatch?.[1] ?? html).replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
2856
|
+
if (bodyContent.length === 0) return true;
|
|
2857
|
+
const singleEmptyContainer = /^<(div|main|section)[^>]*>\s*<\/\1>$/i;
|
|
2858
|
+
if (singleEmptyContainer.test(bodyContent)) return true;
|
|
2859
|
+
return false;
|
|
2860
|
+
}
|
|
2861
|
+
__name(looksLikeSpaShell, "looksLikeSpaShell");
|
|
2862
|
+
function PreviewView({ html }) {
|
|
2863
|
+
const isSpaShell = useMemo(() => looksLikeSpaShell(html), [html]);
|
|
2864
|
+
if (!html) {
|
|
2865
|
+
return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
|
|
2866
|
+
}
|
|
2867
|
+
if (isSpaShell) {
|
|
2868
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 px-6 text-center gap-3 min-h-[400px]", children: [
|
|
2869
|
+
/* @__PURE__ */ jsx("div", { className: "inline-flex items-center justify-center h-10 w-10 rounded-full bg-muted", children: /* @__PURE__ */ jsx(Info, { className: "h-5 w-5 text-muted-foreground" }) }),
|
|
2870
|
+
/* @__PURE__ */ jsxs("div", { className: "max-w-sm space-y-1.5", children: [
|
|
2871
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: "Looks like a single-page app shell" }),
|
|
2872
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground leading-relaxed", children: [
|
|
2873
|
+
"This page renders its content with JavaScript at runtime. Scripts are disabled in the sandbox, so Preview would show a blank page. Switch to ",
|
|
2874
|
+
/* @__PURE__ */ jsx("strong", { children: "Pretty" }),
|
|
2875
|
+
" or",
|
|
2876
|
+
" ",
|
|
2877
|
+
/* @__PURE__ */ jsx("strong", { children: "Raw" }),
|
|
2878
|
+
" to inspect the HTML source."
|
|
2879
|
+
] })
|
|
2880
|
+
] })
|
|
2881
|
+
] });
|
|
2882
|
+
}
|
|
2883
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full min-h-[400px]", children: [
|
|
2884
|
+
/* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center gap-1.5 px-3 py-1.5 bg-muted/30 border-b text-[10px] text-muted-foreground/70", children: [
|
|
2885
|
+
/* @__PURE__ */ jsx(ShieldCheck, { className: "h-3 w-3" }),
|
|
2886
|
+
"Sandboxed preview \u2014 scripts, forms and popups are disabled"
|
|
2887
|
+
] }),
|
|
2888
|
+
/* @__PURE__ */ jsx(
|
|
2889
|
+
"div",
|
|
2890
|
+
{
|
|
2891
|
+
className: "flex-1 min-h-[360px] p-2",
|
|
2892
|
+
style: {
|
|
2893
|
+
backgroundColor: "#fff",
|
|
2894
|
+
backgroundImage: "linear-gradient(45deg, #f3f4f6 25%, transparent 25%), linear-gradient(-45deg, #f3f4f6 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f3f4f6 75%), linear-gradient(-45deg, transparent 75%, #f3f4f6 75%)",
|
|
2895
|
+
backgroundSize: "16px 16px",
|
|
2896
|
+
backgroundPosition: "0 0, 0 8px, 8px -8px, -8px 0px"
|
|
2897
|
+
},
|
|
2898
|
+
children: /* @__PURE__ */ jsx(
|
|
2899
|
+
"iframe",
|
|
2900
|
+
{
|
|
2901
|
+
title: "Response preview",
|
|
2902
|
+
srcDoc: html,
|
|
2903
|
+
sandbox: "",
|
|
2904
|
+
className: "w-full h-full min-h-[360px] bg-white border-0 rounded shadow-sm"
|
|
2905
|
+
}
|
|
2906
|
+
)
|
|
2907
|
+
}
|
|
2908
|
+
)
|
|
2909
|
+
] });
|
|
2910
|
+
}
|
|
2911
|
+
__name(PreviewView, "PreviewView");
|
|
2912
|
+
var JSON_TREE_CONFIG = {
|
|
2913
|
+
maxAutoExpandDepth: 2,
|
|
2914
|
+
maxAutoExpandArrayItems: 10,
|
|
2915
|
+
maxAutoExpandObjectKeys: 5,
|
|
2916
|
+
maxStringLength: 200,
|
|
2917
|
+
collectionLimit: 50,
|
|
2918
|
+
showCollectionInfo: true,
|
|
2919
|
+
showExpandControls: true,
|
|
2920
|
+
showActionButtons: false,
|
|
2921
|
+
preserveKeyOrder: true,
|
|
2922
|
+
className: "border-0 rounded-none"
|
|
2923
|
+
};
|
|
2924
|
+
function PrettyView({ treeData, rawText, detected }) {
|
|
2925
|
+
if (detected.kind === "json" && treeData != null) {
|
|
2926
|
+
return /* @__PURE__ */ jsx(JsonTree_default, { title: "Response Body", data: treeData, config: JSON_TREE_CONFIG });
|
|
2927
|
+
}
|
|
2928
|
+
if (!rawText) {
|
|
2929
|
+
return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
|
|
2930
|
+
}
|
|
2931
|
+
return /* @__PURE__ */ jsx(
|
|
2932
|
+
PrettyCode_default,
|
|
2933
|
+
{
|
|
2934
|
+
data: rawText,
|
|
2935
|
+
language: detected.prism,
|
|
2936
|
+
variant: "plain",
|
|
2937
|
+
isCompact: true
|
|
2938
|
+
}
|
|
2939
|
+
);
|
|
2940
|
+
}
|
|
2941
|
+
__name(PrettyView, "PrettyView");
|
|
2942
|
+
function RawView({ rawText }) {
|
|
2943
|
+
if (!rawText) {
|
|
2944
|
+
return /* @__PURE__ */ jsx("div", { className: "py-10 text-center text-xs text-muted-foreground", children: "Empty response body" });
|
|
2945
|
+
}
|
|
2946
|
+
return /* @__PURE__ */ jsx("pre", { className: "p-4 text-[11px] font-mono text-foreground/70 whitespace-pre-wrap break-all leading-relaxed", children: rawText });
|
|
2947
|
+
}
|
|
2948
|
+
__name(RawView, "RawView");
|
|
2949
|
+
function StatusBar({ response, rawText, contentType }) {
|
|
2950
|
+
const sizeKb = rawText ? `${(rawText.length / 1024).toFixed(1)} KB` : "";
|
|
2951
|
+
const duration = response.duration != null ? `${response.duration}ms` : "";
|
|
2952
|
+
const hasStatus = response.status != null;
|
|
2953
|
+
const hasCopy = Boolean(rawText);
|
|
2954
|
+
return /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-b px-4 py-2 flex items-center justify-between gap-3 bg-muted/20", children: [
|
|
2955
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
2956
|
+
hasStatus && /* @__PURE__ */ jsx(StatusBadge, { status: response.status }),
|
|
2957
|
+
response.statusText && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground truncate", children: response.statusText }),
|
|
2958
|
+
sizeKb && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: sizeKb }),
|
|
2959
|
+
duration && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 tabular-nums shrink-0", children: duration }),
|
|
2960
|
+
contentType && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/50 font-mono truncate", children: contentType })
|
|
2961
|
+
] }),
|
|
2962
|
+
hasCopy && /* @__PURE__ */ jsx(
|
|
2963
|
+
CopyButton,
|
|
2964
|
+
{
|
|
2965
|
+
value: rawText,
|
|
2966
|
+
variant: "ghost",
|
|
2967
|
+
size: "sm",
|
|
2968
|
+
className: "h-6 px-2 text-[10px] text-muted-foreground shrink-0",
|
|
2969
|
+
children: "Copy"
|
|
2970
|
+
}
|
|
2971
|
+
)
|
|
2972
|
+
] });
|
|
2973
|
+
}
|
|
2974
|
+
__name(StatusBar, "StatusBar");
|
|
2975
|
+
|
|
2976
|
+
// src/tools/OpenapiViewer/components/shared/ResponsePanel/detectContent.ts
|
|
2977
|
+
function normaliseContentType(raw) {
|
|
2978
|
+
if (!raw) return null;
|
|
2979
|
+
const semi = raw.indexOf(";");
|
|
2980
|
+
return (semi === -1 ? raw : raw.slice(0, semi)).trim().toLowerCase();
|
|
2981
|
+
}
|
|
2982
|
+
__name(normaliseContentType, "normaliseContentType");
|
|
2983
|
+
function readContentType(headers) {
|
|
2984
|
+
if (!headers) return null;
|
|
2985
|
+
if (typeof headers.get === "function") {
|
|
2986
|
+
return headers.get("content-type");
|
|
2987
|
+
}
|
|
2988
|
+
if (typeof headers === "object") {
|
|
2989
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
2990
|
+
if (k.toLowerCase() === "content-type") {
|
|
2991
|
+
return typeof v === "string" ? v : null;
|
|
2992
|
+
}
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
return null;
|
|
2996
|
+
}
|
|
2997
|
+
__name(readContentType, "readContentType");
|
|
2998
|
+
function kindFromContentType(mime) {
|
|
2999
|
+
if (!mime) return "text";
|
|
3000
|
+
if (mime === "application/json" || mime.endsWith("+json")) return "json";
|
|
3001
|
+
if (mime === "text/html" || mime === "application/xhtml+xml") return "html";
|
|
3002
|
+
if (mime === "application/xml" || mime === "text/xml" || mime.endsWith("+xml")) return "xml";
|
|
3003
|
+
if (mime === "text/css") return "css";
|
|
3004
|
+
if (mime === "application/javascript" || mime === "text/javascript" || mime === "application/x-javascript") return "javascript";
|
|
3005
|
+
return "text";
|
|
3006
|
+
}
|
|
3007
|
+
__name(kindFromContentType, "kindFromContentType");
|
|
3008
|
+
function kindFromBody(body) {
|
|
3009
|
+
const trimmed = body.trimStart();
|
|
3010
|
+
if (!trimmed) return null;
|
|
3011
|
+
if (trimmed.startsWith("<!DOCTYPE") || /^<html[\s>]/i.test(trimmed)) return "html";
|
|
3012
|
+
if (trimmed.startsWith("<?xml")) return "xml";
|
|
3013
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
3014
|
+
try {
|
|
3015
|
+
JSON.parse(trimmed);
|
|
3016
|
+
return "json";
|
|
3017
|
+
} catch {
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
return null;
|
|
3021
|
+
}
|
|
3022
|
+
__name(kindFromBody, "kindFromBody");
|
|
3023
|
+
var PRISM_BY_KIND = {
|
|
3024
|
+
json: "json",
|
|
3025
|
+
// ``markup`` is Prism's HTML/XML grammar — there isn't a separate
|
|
3026
|
+
// ``html`` language. XML piggy-backs on the same tokeniser.
|
|
3027
|
+
html: "markup",
|
|
3028
|
+
xml: "markup",
|
|
3029
|
+
css: "css",
|
|
3030
|
+
javascript: "javascript",
|
|
3031
|
+
text: "markup"
|
|
3032
|
+
};
|
|
3033
|
+
function detectContent(headers, rawBody) {
|
|
3034
|
+
const contentType = normaliseContentType(readContentType(headers));
|
|
3035
|
+
const headerKind = kindFromContentType(contentType);
|
|
3036
|
+
const kind = headerKind === "text" ? kindFromBody(rawBody) ?? "text" : headerKind;
|
|
3037
|
+
return {
|
|
3038
|
+
kind,
|
|
3039
|
+
prism: PRISM_BY_KIND[kind],
|
|
3040
|
+
contentType
|
|
3041
|
+
};
|
|
3042
|
+
}
|
|
3043
|
+
__name(detectContent, "detectContent");
|
|
3044
|
+
|
|
3045
|
+
// src/tools/OpenapiViewer/components/shared/ResponsePanel/useResponseView.ts
|
|
3046
|
+
function useResponseView(data, headers) {
|
|
3047
|
+
return useMemo(() => {
|
|
3048
|
+
if (data == null) {
|
|
3049
|
+
return {
|
|
3050
|
+
treeData: null,
|
|
3051
|
+
rawText: "",
|
|
3052
|
+
detected: detectContent(headers, "")
|
|
3053
|
+
};
|
|
3054
|
+
}
|
|
3055
|
+
if (typeof data === "string") {
|
|
3056
|
+
try {
|
|
3057
|
+
return {
|
|
3058
|
+
treeData: JSON.parse(data),
|
|
3059
|
+
rawText: data,
|
|
3060
|
+
detected: detectContent(headers, data)
|
|
3061
|
+
};
|
|
3062
|
+
} catch {
|
|
3063
|
+
return {
|
|
3064
|
+
treeData: null,
|
|
3065
|
+
rawText: data,
|
|
3066
|
+
detected: detectContent(headers, data)
|
|
3067
|
+
};
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
const stringified = (() => {
|
|
3071
|
+
try {
|
|
3072
|
+
return JSON.stringify(data, null, 2);
|
|
3073
|
+
} catch {
|
|
3074
|
+
return String(data);
|
|
3075
|
+
}
|
|
3076
|
+
})();
|
|
3077
|
+
return {
|
|
3078
|
+
treeData: data,
|
|
3079
|
+
rawText: stringified,
|
|
3080
|
+
detected: detectContent(headers, stringified)
|
|
3081
|
+
};
|
|
3082
|
+
}, [data, headers]);
|
|
3083
|
+
}
|
|
3084
|
+
__name(useResponseView, "useResponseView");
|
|
3085
|
+
var LABELS = {
|
|
3086
|
+
pretty: "Pretty",
|
|
3087
|
+
raw: "Raw",
|
|
3088
|
+
preview: "Preview"
|
|
3089
|
+
};
|
|
3090
|
+
function ViewTabs({ active, onChange, showPreview }) {
|
|
3091
|
+
const tabs = showPreview ? ["pretty", "raw", "preview"] : ["pretty", "raw"];
|
|
3092
|
+
return /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-3 py-1.5 flex items-center gap-1", children: tabs.map((t) => /* @__PURE__ */ jsx(
|
|
3093
|
+
"button",
|
|
3094
|
+
{
|
|
3095
|
+
type: "button",
|
|
3096
|
+
onClick: () => onChange(t),
|
|
3097
|
+
className: cn(
|
|
3098
|
+
"h-6 px-2.5 rounded text-[11px] font-medium transition-colors",
|
|
3099
|
+
active === t ? "bg-muted text-foreground" : "text-muted-foreground/70 hover:text-foreground hover:bg-muted/50"
|
|
3100
|
+
),
|
|
3101
|
+
children: LABELS[t]
|
|
3102
|
+
},
|
|
3103
|
+
t
|
|
3104
|
+
)) });
|
|
3105
|
+
}
|
|
3106
|
+
__name(ViewTabs, "ViewTabs");
|
|
3107
|
+
function ResponsePanel() {
|
|
3108
|
+
const { state } = usePlaygroundContext();
|
|
3109
|
+
const { response, loading, selectedEndpoint } = state;
|
|
3110
|
+
const { treeData, rawText, detected } = useResponseView(response?.data, response?.headers);
|
|
3111
|
+
const showPreview = detected.kind === "html";
|
|
3112
|
+
const [mode, setMode] = useState(showPreview ? "preview" : "pretty");
|
|
3113
|
+
useEffect(() => {
|
|
3114
|
+
setMode(showPreview ? "preview" : "pretty");
|
|
3115
|
+
}, [selectedEndpoint, response, showPreview]);
|
|
3116
|
+
if (loading) {
|
|
3117
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center h-full gap-2", children: [
|
|
3118
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" }),
|
|
3119
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Sending\u2026" })
|
|
3120
|
+
] });
|
|
3121
|
+
}
|
|
3122
|
+
if (!selectedEndpoint) return /* @__PURE__ */ jsx(EmptyState, { icon: Terminal, text: "Response will appear here" });
|
|
3123
|
+
if (!response) return /* @__PURE__ */ jsx(EmptyState, { icon: Send, text: 'Press "Send Request" to see the response' });
|
|
3124
|
+
const hasError = Boolean(response.error);
|
|
3125
|
+
const hasStatus = response.status != null;
|
|
3126
|
+
if (hasError && !hasStatus) {
|
|
3127
|
+
return /* @__PURE__ */ jsx(
|
|
3128
|
+
EmptyState,
|
|
3129
|
+
{
|
|
3130
|
+
icon: WifiOff,
|
|
3131
|
+
text: response.error,
|
|
3132
|
+
className: "text-destructive [&_svg]:text-destructive"
|
|
3133
|
+
}
|
|
3134
|
+
);
|
|
3135
|
+
}
|
|
3136
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3137
|
+
/* @__PURE__ */ jsx(StatusBar, { response, rawText, contentType: detected.contentType }),
|
|
3138
|
+
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 }) }),
|
|
3139
|
+
/* @__PURE__ */ jsx(ViewTabs, { active: mode, onChange: setMode, showPreview }),
|
|
3140
|
+
/* @__PURE__ */ jsxs(ScrollArea, { children: [
|
|
3141
|
+
mode === "pretty" && /* @__PURE__ */ jsx(PrettyView, { treeData, rawText, detected }),
|
|
3142
|
+
mode === "raw" && /* @__PURE__ */ jsx(RawView, { rawText }),
|
|
3143
|
+
mode === "preview" && /* @__PURE__ */ jsx(PreviewView, { html: rawText })
|
|
3144
|
+
] })
|
|
3145
|
+
] });
|
|
3146
|
+
}
|
|
3147
|
+
__name(ResponsePanel, "ResponsePanel");
|
|
3148
|
+
function SendButton({ className }) {
|
|
3149
|
+
const { state, sendRequest } = usePlaygroundContext();
|
|
3150
|
+
const ep = state.selectedEndpoint;
|
|
3151
|
+
const builder = useMemo(
|
|
3152
|
+
() => ep ? new UrlBuilder(ep, state.parameters) : null,
|
|
3153
|
+
[ep, state.parameters]
|
|
3154
|
+
);
|
|
3155
|
+
const missingRequired = builder?.missingRequired() ?? [];
|
|
3156
|
+
const unsubstituted = builder?.unfilledPlaceholders() ?? [];
|
|
3157
|
+
const isJsonValid = state.requestBody ? isValidJson(state.requestBody) : true;
|
|
3158
|
+
const blockers = [];
|
|
3159
|
+
if (missingRequired.length > 0) {
|
|
3160
|
+
blockers.push(
|
|
3161
|
+
`Fill required parameter${missingRequired.length > 1 ? "s" : ""}: ${missingRequired.join(", ")}`
|
|
3162
|
+
);
|
|
3163
|
+
} else if (unsubstituted.length > 0) {
|
|
3164
|
+
blockers.push(`URL still has unfilled placeholder${unsubstituted.length > 1 ? "s" : ""}: ${unsubstituted.map((n) => `{${n}}`).join(", ")}`);
|
|
3165
|
+
}
|
|
3166
|
+
if (!isJsonValid) blockers.push("Request body is not valid JSON");
|
|
3167
|
+
const disabled = state.loading || !state.requestUrl || blockers.length > 0;
|
|
3168
|
+
const tooltip = blockers.length > 0 ? blockers.join("\n") : void 0;
|
|
3169
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
|
|
3170
|
+
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: [
|
|
3171
|
+
/* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 shrink-0 mt-px" }),
|
|
3172
|
+
/* @__PURE__ */ jsx("span", { className: "leading-snug", children: blockers[0] })
|
|
3173
|
+
] }),
|
|
3174
|
+
/* @__PURE__ */ jsx(
|
|
3175
|
+
Button,
|
|
3176
|
+
{
|
|
3177
|
+
onClick: sendRequest,
|
|
3178
|
+
disabled,
|
|
3179
|
+
size: "sm",
|
|
3180
|
+
title: tooltip,
|
|
3181
|
+
className: "w-full gap-2 h-9",
|
|
3182
|
+
children: state.loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3183
|
+
/* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }),
|
|
3184
|
+
"Sending\u2026"
|
|
3185
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3186
|
+
/* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
|
|
3187
|
+
"Send Request"
|
|
3188
|
+
] })
|
|
3189
|
+
}
|
|
3190
|
+
)
|
|
3191
|
+
] });
|
|
3192
|
+
}
|
|
3193
|
+
__name(SendButton, "SendButton");
|
|
3194
|
+
var WIDTH_NARROW = "clamp(380px, 30vw, 480px)";
|
|
3195
|
+
var WIDTH_WIDE = "clamp(720px, 60vw, 1280px)";
|
|
3196
|
+
function SlideInPlayground({ open, onClose }) {
|
|
3197
|
+
const { state } = usePlaygroundContext();
|
|
3198
|
+
const ep = state.selectedEndpoint;
|
|
3199
|
+
const showResponse = state.response !== null || state.loading;
|
|
3200
|
+
const width = showResponse ? WIDTH_WIDE : WIDTH_NARROW;
|
|
3201
|
+
return /* @__PURE__ */ jsx(SidePanel, { open, onOpenChange: (v) => !v && onClose(), side: "right", children: /* @__PURE__ */ jsxs(SidePanel.Content, { width, className: "max-w-[95vw]", children: [
|
|
3202
|
+
/* @__PURE__ */ jsxs(SidePanel.Header, { children: [
|
|
3203
|
+
/* @__PURE__ */ jsx(SidePanel.Title, { children: "Playground" }),
|
|
3204
|
+
ep && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [
|
|
3205
|
+
/* @__PURE__ */ jsx(MethodBadge, { method: ep.method }),
|
|
3206
|
+
/* @__PURE__ */ jsx("code", { className: "font-mono text-[11px] text-muted-foreground truncate", children: relativePath(ep.path) })
|
|
3207
|
+
] }),
|
|
3208
|
+
/* @__PURE__ */ jsx(SidePanel.Close, { className: "ml-auto" })
|
|
3209
|
+
] }),
|
|
3210
|
+
/* @__PURE__ */ jsxs(
|
|
3211
|
+
SidePanel.Body,
|
|
3212
|
+
{
|
|
3213
|
+
className: cn(
|
|
3214
|
+
"overflow-hidden grid divide-x transition-[grid-template-columns] duration-250",
|
|
3215
|
+
showResponse ? "grid-cols-[minmax(0,1fr)_minmax(0,1fr)]" : "grid-cols-1"
|
|
3216
|
+
),
|
|
3217
|
+
children: [
|
|
3218
|
+
/* @__PURE__ */ jsxs(Panel, { children: [
|
|
3219
|
+
/* @__PURE__ */ jsx(RequestPanel, {}),
|
|
3220
|
+
ep && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background", children: /* @__PURE__ */ jsx(SendButton, {}) })
|
|
3221
|
+
] }),
|
|
3222
|
+
showResponse && /* @__PURE__ */ jsx(Panel, { children: /* @__PURE__ */ jsx(ResponsePanel, {}) })
|
|
3223
|
+
]
|
|
3224
|
+
}
|
|
3225
|
+
)
|
|
3226
|
+
] }) });
|
|
3227
|
+
}
|
|
3228
|
+
__name(SlideInPlayground, "SlideInPlayground");
|
|
3229
|
+
function TryItSheet({ open, onOpenChange }) {
|
|
3230
|
+
const { state } = usePlaygroundContext();
|
|
3231
|
+
const showResponse = state.response !== null || state.loading;
|
|
3232
|
+
return /* @__PURE__ */ jsx(ResponsiveSheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ResponsiveSheetContent, { className: "sm:max-w-xl flex flex-col h-full p-0", children: [
|
|
3233
|
+
/* @__PURE__ */ jsx(ResponsiveSheetHeader, { className: "px-4 py-3 border-b shrink-0", children: /* @__PURE__ */ jsx(ResponsiveSheetTitle, { className: "text-sm", children: "Playground" }) }),
|
|
3234
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 flex flex-col divide-y", children: [
|
|
3235
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 flex flex-col", children: [
|
|
3236
|
+
/* @__PURE__ */ jsx(RequestPanel, {}),
|
|
3237
|
+
state.selectedEndpoint && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-t px-4 py-3 bg-background", children: /* @__PURE__ */ jsx(SendButton, {}) })
|
|
3238
|
+
] }),
|
|
3239
|
+
showResponse && /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex flex-col", children: /* @__PURE__ */ jsx(ResponsePanel, {}) })
|
|
3240
|
+
] })
|
|
3241
|
+
] }) });
|
|
3242
|
+
}
|
|
3243
|
+
__name(TryItSheet, "TryItSheet");
|
|
3244
|
+
var DocsLayout = /* @__PURE__ */ __name(() => {
|
|
3245
|
+
const { state, config, setSelectedEndpoint } = usePlaygroundContext();
|
|
3246
|
+
const isDesktop = useMediaQuery("(min-width: 1024px)");
|
|
3247
|
+
const isMobile = !isDesktop;
|
|
3248
|
+
const grouping = config.schemaGrouping ?? "selector";
|
|
3249
|
+
const preloadAll = grouping === "sections";
|
|
3250
|
+
const urlSyncEnabled = typeof config.urlSync === "boolean" ? config.urlSync : Boolean(config.urlSync?.enabled);
|
|
3251
|
+
const {
|
|
3252
|
+
endpoints,
|
|
3253
|
+
schemaInfo,
|
|
3254
|
+
rawSchema,
|
|
3255
|
+
resolvedBaseUrl,
|
|
3256
|
+
loading,
|
|
3257
|
+
error,
|
|
3258
|
+
schemas,
|
|
3259
|
+
currentSchema,
|
|
3260
|
+
setCurrentSchema,
|
|
3261
|
+
schemasData
|
|
3262
|
+
} = useOpenApiSchema({
|
|
3263
|
+
schemas: config.schemas,
|
|
3264
|
+
defaultSchemaId: config.defaultSchemaId,
|
|
3265
|
+
baseUrl: config.baseUrl,
|
|
3266
|
+
preloadAll
|
|
3267
|
+
});
|
|
3268
|
+
const [activeAnchor, setActiveAnchor] = useState(null);
|
|
3269
|
+
const [activeSchemaId, setActiveSchemaId] = useState(null);
|
|
3270
|
+
const [sheetOpen, setSheetOpen] = useState(false);
|
|
3271
|
+
const docsRef = useRef(null);
|
|
3272
|
+
const slideOpen = !isMobile && state.selectedEndpoint !== null;
|
|
3273
|
+
const endpointsBySchema = useMemo(() => {
|
|
3274
|
+
if (grouping !== "sections") return {};
|
|
3275
|
+
const byId = keyBy(schemasData, (e) => e.source.id);
|
|
3276
|
+
const out = {};
|
|
3277
|
+
for (const src of schemas) out[src.id] = byId[src.id]?.endpoints ?? [];
|
|
3278
|
+
return out;
|
|
3279
|
+
}, [grouping, schemasData, schemas]);
|
|
3280
|
+
const handleTry = useCallback(
|
|
3281
|
+
(ep) => {
|
|
3282
|
+
setSelectedEndpoint(ep);
|
|
3283
|
+
if (isMobile) setSheetOpen(true);
|
|
3284
|
+
},
|
|
3285
|
+
[isMobile, setSelectedEndpoint]
|
|
3286
|
+
);
|
|
3287
|
+
const handleCloseSlide = useCallback(() => {
|
|
3288
|
+
setSelectedEndpoint(null);
|
|
3289
|
+
}, [setSelectedEndpoint]);
|
|
3290
|
+
const handleNavigate = useCallback(
|
|
3291
|
+
(anchor, schemaId) => {
|
|
3292
|
+
if (schemaId && schemaId !== currentSchema?.id && grouping === "selector") {
|
|
3293
|
+
setCurrentSchema(schemaId);
|
|
3294
|
+
requestAnimationFrame(() => {
|
|
3295
|
+
docsRef.current?.scrollToAnchor(anchor);
|
|
3296
|
+
});
|
|
3297
|
+
return;
|
|
3298
|
+
}
|
|
3299
|
+
docsRef.current?.scrollToAnchor(anchor);
|
|
3300
|
+
},
|
|
3301
|
+
[currentSchema?.id, grouping, setCurrentSchema]
|
|
3302
|
+
);
|
|
3303
|
+
const handleActiveChange = useCallback((anchor, schemaId) => {
|
|
3304
|
+
setActiveAnchor(anchor);
|
|
3305
|
+
setActiveSchemaId(schemaId);
|
|
3306
|
+
}, []);
|
|
3307
|
+
const effectiveSchemaId = grouping === "sections" ? activeSchemaId : currentSchema?.id ?? null;
|
|
3308
|
+
const handleHashTarget = useCallback(
|
|
3309
|
+
(target) => {
|
|
3310
|
+
if (!target.schemaId && !target.anchor) return;
|
|
3311
|
+
const matched = target.schemaId ? schemas.find((s) => s.id === target.schemaId || slugifySchemaId(s.id) === target.schemaId) : null;
|
|
3312
|
+
const needsSchemaSwitch = matched && grouping === "selector" && matched.id !== currentSchema?.id;
|
|
3313
|
+
if (needsSchemaSwitch) {
|
|
3314
|
+
setCurrentSchema(matched.id);
|
|
3315
|
+
}
|
|
3316
|
+
if (target.anchor) {
|
|
3317
|
+
const anchor = target.anchor;
|
|
3318
|
+
if (needsSchemaSwitch) {
|
|
3319
|
+
requestAnimationFrame(() => {
|
|
3320
|
+
docsRef.current?.scrollToAnchor(anchor);
|
|
3321
|
+
});
|
|
3322
|
+
} else {
|
|
3323
|
+
docsRef.current?.scrollToAnchor(anchor);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
},
|
|
3327
|
+
[schemas, grouping, currentSchema?.id, setCurrentSchema]
|
|
3328
|
+
);
|
|
3329
|
+
useDocsUrlSync({
|
|
3330
|
+
enabled: urlSyncEnabled,
|
|
3331
|
+
currentSchemaId: effectiveSchemaId,
|
|
3332
|
+
activeAnchor,
|
|
3333
|
+
onHashTarget: handleHashTarget
|
|
3334
|
+
});
|
|
3335
|
+
if (loading) {
|
|
3336
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[260px_1fr] items-start", children: [
|
|
3337
|
+
/* @__PURE__ */ jsx(
|
|
3338
|
+
"div",
|
|
3339
|
+
{
|
|
3340
|
+
className: "sticky top-[var(--navbar-height,64px)] border-r p-3 space-y-1.5 overflow-y-auto",
|
|
3341
|
+
style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
|
|
3342
|
+
children: Array.from({ length: 12 }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-full rounded" }, i))
|
|
3343
|
+
}
|
|
3344
|
+
),
|
|
3345
|
+
/* @__PURE__ */ jsxs("div", { className: "p-8 space-y-4", children: [
|
|
3346
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-1/2" }),
|
|
3347
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }),
|
|
3348
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-3/4" }),
|
|
3349
|
+
/* @__PURE__ */ jsx("div", { className: "mt-8 space-y-6", children: Array.from({ length: 3 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
3350
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-1/3" }),
|
|
3351
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-20 w-full" })
|
|
3352
|
+
] }, i)) })
|
|
3353
|
+
] })
|
|
3354
|
+
] });
|
|
3355
|
+
}
|
|
3356
|
+
if (error) {
|
|
3357
|
+
return /* @__PURE__ */ jsx(
|
|
3358
|
+
"div",
|
|
3359
|
+
{
|
|
3360
|
+
className: "flex items-center justify-center p-8",
|
|
3361
|
+
style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
|
|
3362
|
+
children: /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
|
|
3363
|
+
"Failed to load schema: ",
|
|
3364
|
+
error
|
|
3365
|
+
] })
|
|
3366
|
+
}
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3369
|
+
if (isMobile) {
|
|
3370
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
|
|
3371
|
+
/* @__PURE__ */ jsx(EndpointDraftSync, { schemaId: currentSchema?.id ?? null }),
|
|
3372
|
+
grouping === "sections" ? /* @__PURE__ */ jsx(
|
|
3373
|
+
DocsView,
|
|
3374
|
+
{
|
|
3375
|
+
ref: docsRef,
|
|
3376
|
+
grouping: "sections",
|
|
3377
|
+
schemasData,
|
|
3378
|
+
selectedVersion: state.selectedVersion,
|
|
3379
|
+
loadedEndpoint: state.selectedEndpoint,
|
|
3380
|
+
onTryEndpoint: handleTry,
|
|
3381
|
+
onActiveChange: handleActiveChange
|
|
3382
|
+
}
|
|
3383
|
+
) : /* @__PURE__ */ jsx(
|
|
3384
|
+
DocsView,
|
|
3385
|
+
{
|
|
3386
|
+
ref: docsRef,
|
|
3387
|
+
info: schemaInfo,
|
|
3388
|
+
rawSchema,
|
|
3389
|
+
resolvedBaseUrl,
|
|
3390
|
+
endpoints,
|
|
3391
|
+
selectedVersion: state.selectedVersion,
|
|
3392
|
+
loadedEndpoint: state.selectedEndpoint,
|
|
3393
|
+
onTryEndpoint: handleTry,
|
|
3394
|
+
onActiveChange: handleActiveChange
|
|
3395
|
+
}
|
|
3396
|
+
),
|
|
3397
|
+
/* @__PURE__ */ jsx(TryItSheet, { open: sheetOpen, onOpenChange: setSheetOpen })
|
|
3398
|
+
] });
|
|
3399
|
+
}
|
|
3400
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 350, children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[260px_minmax(0,1fr)] items-start", children: [
|
|
3401
|
+
/* @__PURE__ */ jsx(EndpointDraftSync, { schemaId: currentSchema?.id ?? null }),
|
|
3402
|
+
/* @__PURE__ */ jsx(
|
|
3403
|
+
"div",
|
|
3404
|
+
{
|
|
3405
|
+
className: "sticky top-[var(--navbar-height,64px)]",
|
|
3406
|
+
style: { height: "calc(100dvh - var(--navbar-height, 64px))" },
|
|
3407
|
+
children: /* @__PURE__ */ jsx(
|
|
3408
|
+
DocsSidebar,
|
|
3409
|
+
{
|
|
3410
|
+
info: schemaInfo,
|
|
3411
|
+
endpoints,
|
|
3412
|
+
schemas,
|
|
3413
|
+
currentSchemaId: currentSchema?.id ?? null,
|
|
3414
|
+
onSchemaChange: setCurrentSchema,
|
|
3415
|
+
activeEndpointId: activeAnchor,
|
|
3416
|
+
selectedVersion: state.selectedVersion,
|
|
3417
|
+
onNavigate: handleNavigate,
|
|
3418
|
+
grouping,
|
|
3419
|
+
endpointsBySchema,
|
|
3420
|
+
rawSchema,
|
|
3421
|
+
resolvedBaseUrl
|
|
3422
|
+
}
|
|
3423
|
+
)
|
|
3424
|
+
}
|
|
3425
|
+
),
|
|
3426
|
+
grouping === "sections" ? /* @__PURE__ */ jsx(
|
|
3427
|
+
DocsView,
|
|
3428
|
+
{
|
|
3429
|
+
ref: docsRef,
|
|
3430
|
+
grouping: "sections",
|
|
3431
|
+
schemasData,
|
|
3432
|
+
selectedVersion: state.selectedVersion,
|
|
3433
|
+
loadedEndpoint: state.selectedEndpoint,
|
|
3434
|
+
onTryEndpoint: handleTry,
|
|
3435
|
+
onActiveChange: handleActiveChange
|
|
3436
|
+
}
|
|
3437
|
+
) : /* @__PURE__ */ jsx(
|
|
3438
|
+
DocsView,
|
|
3439
|
+
{
|
|
3440
|
+
ref: docsRef,
|
|
3441
|
+
info: schemaInfo,
|
|
3442
|
+
rawSchema,
|
|
3443
|
+
resolvedBaseUrl,
|
|
3444
|
+
endpoints,
|
|
3445
|
+
selectedVersion: state.selectedVersion,
|
|
3446
|
+
loadedEndpoint: state.selectedEndpoint,
|
|
3447
|
+
onTryEndpoint: handleTry,
|
|
3448
|
+
onActiveChange: handleActiveChange
|
|
3449
|
+
}
|
|
3450
|
+
),
|
|
3451
|
+
/* @__PURE__ */ jsx(SlideInPlayground, { open: slideOpen, onClose: handleCloseSlide })
|
|
3452
|
+
] }) });
|
|
3453
|
+
}, "DocsLayout");
|
|
3454
|
+
|
|
3455
|
+
export { DocsLayout };
|
|
3456
|
+
//# sourceMappingURL=DocsLayout-JPXFUKAR.mjs.map
|
|
3457
|
+
//# sourceMappingURL=DocsLayout-JPXFUKAR.mjs.map
|