zudoku 0.25.0 → 0.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/validators/InputSidebarSchema.d.ts +15 -0
- package/dist/config/validators/InputSidebarSchema.js +1 -0
- package/dist/config/validators/InputSidebarSchema.js.map +1 -1
- package/dist/config/validators/common.d.ts +45 -34
- package/dist/config/validators/common.js +2 -1
- package/dist/config/validators/common.js.map +1 -1
- package/dist/config/validators/validate.d.ts +19 -14
- package/dist/lib/authentication/components/SignOut.js +1 -1
- package/dist/lib/authentication/components/SignOut.js.map +1 -1
- package/dist/lib/authentication/providers/clerk.js +29 -6
- package/dist/lib/authentication/providers/clerk.js.map +1 -1
- package/dist/lib/authentication/providers/openid.js +1 -1
- package/dist/lib/authentication/providers/openid.js.map +1 -1
- package/dist/lib/components/navigation/SidebarBadge.d.ts +11 -1
- package/dist/lib/components/navigation/SidebarBadge.js +11 -2
- package/dist/lib/components/navigation/SidebarBadge.js.map +1 -1
- package/dist/lib/components/navigation/SidebarCategory.js +2 -2
- package/dist/lib/components/navigation/SidebarCategory.js.map +1 -1
- package/dist/lib/components/navigation/SidebarWrapper.js +1 -1
- package/dist/lib/components/navigation/SidebarWrapper.js.map +1 -1
- package/dist/lib/oas/graphql/index.js +16 -3
- package/dist/lib/oas/graphql/index.js.map +1 -1
- package/dist/lib/oas/parser/upgrade/index.js +17 -3
- package/dist/lib/oas/parser/upgrade/index.js.map +1 -1
- package/dist/lib/plugins/openapi/CollapsibleCode.js +1 -1
- package/dist/lib/plugins/openapi/Endpoint.d.ts +1 -1
- package/dist/lib/plugins/openapi/Endpoint.js +2 -0
- package/dist/lib/plugins/openapi/Endpoint.js.map +1 -1
- package/dist/lib/plugins/openapi/ExampleDisplay.d.ts +12 -0
- package/dist/lib/plugins/openapi/ExampleDisplay.js +78 -0
- package/dist/lib/plugins/openapi/ExampleDisplay.js.map +1 -0
- package/dist/lib/plugins/openapi/OperationListItem.js +1 -1
- package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
- package/dist/lib/plugins/openapi/ParameterListItem.js +1 -1
- package/dist/lib/plugins/openapi/ParameterListItem.js.map +1 -1
- package/dist/lib/plugins/openapi/RequestBodySidecarBox.d.ts +2 -4
- package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +6 -13
- package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +11 -13
- package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
- package/dist/lib/plugins/openapi/Sidecar.js +1 -1
- package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
- package/dist/lib/plugins/openapi/index.js +1 -0
- package/dist/lib/plugins/openapi/index.js.map +1 -1
- package/dist/vite/build.js +10 -8
- package/dist/vite/build.js.map +1 -1
- package/dist/vite/config.js +10 -0
- package/dist/vite/config.js.map +1 -1
- package/lib/{AuthenticationPlugin-DVLEc6cm.js → AuthenticationPlugin-D7G3me8L.js} +16 -16
- package/lib/AuthenticationPlugin-D7G3me8L.js.map +1 -0
- package/lib/{OperationList-c6V_vcgz.js → OperationList-BLdHAQ39.js} +1594 -1525
- package/lib/OperationList-BLdHAQ39.js.map +1 -0
- package/lib/assets/{worker-DV9Ecqy9.js → worker-Cbp2r2BQ.js} +26 -14
- package/lib/assets/{worker-DV9Ecqy9.js.map → worker-Cbp2r2BQ.js.map} +1 -1
- package/lib/{createServer-C4C0OO0m.js → createServer-Bf5_6o6G.js} +796 -784
- package/lib/{createServer-C4C0OO0m.js.map → createServer-Bf5_6o6G.js.map} +1 -1
- package/lib/{index-DkwDHnit.js → index-BNx95gkf.js} +5 -4
- package/lib/{index-DkwDHnit.js.map → index-BNx95gkf.js.map} +1 -1
- package/lib/zudoku.auth-clerk.js +80 -52
- package/lib/zudoku.auth-clerk.js.map +1 -1
- package/lib/zudoku.auth-openid.js +37 -37
- package/lib/zudoku.auth-openid.js.map +1 -1
- package/lib/zudoku.components.js +89 -85
- package/lib/zudoku.components.js.map +1 -1
- package/lib/zudoku.openapi-worker.js +1 -1
- package/lib/zudoku.plugin-openapi.js +1 -1
- package/package.json +4 -4
- package/src/lib/authentication/components/SignOut.tsx +2 -1
- package/src/lib/authentication/providers/clerk.tsx +38 -7
- package/src/lib/authentication/providers/openid.tsx +1 -1
- package/src/lib/components/navigation/SidebarBadge.tsx +13 -1
- package/src/lib/components/navigation/SidebarCategory.tsx +3 -7
- package/src/lib/components/navigation/SidebarWrapper.tsx +1 -0
- package/src/lib/oas/graphql/index.ts +16 -7
- package/src/lib/oas/parser/upgrade/index.ts +19 -4
- package/src/lib/plugins/openapi/CollapsibleCode.tsx +1 -1
- package/src/lib/plugins/openapi/Endpoint.tsx +2 -0
- package/src/lib/plugins/openapi/ExampleDisplay.tsx +163 -0
- package/src/lib/plugins/openapi/OperationListItem.tsx +5 -3
- package/src/lib/plugins/openapi/ParameterListItem.tsx +1 -1
- package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +11 -37
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +49 -63
- package/src/lib/plugins/openapi/Sidecar.tsx +2 -4
- package/src/lib/plugins/openapi/index.tsx +1 -0
- package/lib/AuthenticationPlugin-DVLEc6cm.js.map +0 -1
- package/lib/OperationList-c6V_vcgz.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./jsx-runtime-Dx-03ztt.js";
|
|
2
2
|
import "./chunk-D52XG6IA-Dl7HLe6j.js";
|
|
3
|
-
import { o as a } from "./index-
|
|
3
|
+
import { o as a } from "./index-BNx95gkf.js";
|
|
4
4
|
import "./ZudokuContext-hmLMUdf2.js";
|
|
5
5
|
import "lucide-react";
|
|
6
6
|
import "./hook-CHq7pFyz.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zudoku",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://zudoku.dev",
|
|
6
6
|
"repository": {
|
|
@@ -147,7 +147,7 @@
|
|
|
147
147
|
"@sentry/node": "8.42.0",
|
|
148
148
|
"@sindresorhus/slugify": "2.2.1",
|
|
149
149
|
"@stefanprobst/rehype-extract-toc": "2.2.0",
|
|
150
|
-
"@tailwindcss/typography": "0.5.
|
|
150
|
+
"@tailwindcss/typography": "0.5.16",
|
|
151
151
|
"@tanstack/react-query": "5.62.3",
|
|
152
152
|
"@types/react": "19.0.1",
|
|
153
153
|
"@types/react-dom": "19.0.1",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"mdast-util-mdx": "3.0.0",
|
|
241
241
|
"react": "19.0.0",
|
|
242
242
|
"react-dom": "19.0.0",
|
|
243
|
-
"rollup-plugin-visualizer": "5.
|
|
243
|
+
"rollup-plugin-visualizer": "5.14.0",
|
|
244
244
|
"typescript": "5.7.2",
|
|
245
245
|
"vitest": "2.1.8"
|
|
246
246
|
},
|
|
@@ -250,7 +250,7 @@
|
|
|
250
250
|
},
|
|
251
251
|
"optionalDependencies": {
|
|
252
252
|
"@clerk/clerk-js": "^5.43.4",
|
|
253
|
-
"@sentry/react": "^8.
|
|
253
|
+
"@sentry/react": "^8.50.0"
|
|
254
254
|
},
|
|
255
255
|
"scripts": {
|
|
256
256
|
"build": "tsc --project tsconfig.json",
|
|
@@ -5,9 +5,10 @@ import { useZudoku } from "../../components/context/ZudokuContext.js";
|
|
|
5
5
|
export const SignOut = () => {
|
|
6
6
|
const context = useZudoku();
|
|
7
7
|
const navigate = useNavigate();
|
|
8
|
+
|
|
8
9
|
useEffect(() => {
|
|
9
10
|
void context.authentication?.signOut().then(() => navigate("/"));
|
|
10
|
-
}, [
|
|
11
|
+
}, []);
|
|
11
12
|
|
|
12
13
|
return null;
|
|
13
14
|
};
|
|
@@ -16,14 +16,19 @@ class ClerkAuthPlugin extends AuthenticationPlugin {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
if (clerk.session) {
|
|
19
|
+
const verifiedEmail = clerk.session.user.emailAddresses.find(
|
|
20
|
+
(email) => email.verification.status === "verified",
|
|
21
|
+
);
|
|
19
22
|
useAuthState.setState({
|
|
20
23
|
isAuthenticated: true,
|
|
21
24
|
isPending: false,
|
|
22
25
|
profile: {
|
|
23
26
|
sub: clerk.session.user.id,
|
|
24
27
|
name: clerk.session.user.fullName ?? undefined,
|
|
25
|
-
email:
|
|
26
|
-
|
|
28
|
+
email:
|
|
29
|
+
verifiedEmail?.emailAddress ??
|
|
30
|
+
clerk.session.user.emailAddresses[0]?.emailAddress,
|
|
31
|
+
emailVerified: verifiedEmail !== undefined,
|
|
27
32
|
pictureUrl: clerk.session.user.imageUrl,
|
|
28
33
|
},
|
|
29
34
|
});
|
|
@@ -45,19 +50,39 @@ const clerkAuth: AuthenticationProviderInitializer<
|
|
|
45
50
|
redirectToAfterSignUp = "/",
|
|
46
51
|
redirectToAfterSignIn = "/",
|
|
47
52
|
}) => {
|
|
48
|
-
let clerkApi: Clerk;
|
|
53
|
+
let clerkApi: Clerk | undefined;
|
|
49
54
|
const ensureLoaded = (async () => {
|
|
50
55
|
if (typeof window === "undefined") return;
|
|
51
56
|
const { Clerk } = await import("@clerk/clerk-js");
|
|
52
57
|
clerkApi = new Clerk(clerkPubKey);
|
|
53
58
|
|
|
54
59
|
await clerkApi.load();
|
|
60
|
+
|
|
61
|
+
if (clerkApi.user) {
|
|
62
|
+
const verifiedEmail = clerkApi.user.emailAddresses.find(
|
|
63
|
+
(email) => email.verification.status === "verified",
|
|
64
|
+
);
|
|
65
|
+
useAuthState.setState({
|
|
66
|
+
isAuthenticated: true,
|
|
67
|
+
isPending: false,
|
|
68
|
+
profile: {
|
|
69
|
+
sub: clerkApi.user.id,
|
|
70
|
+
name: clerkApi.user.fullName ?? undefined,
|
|
71
|
+
email:
|
|
72
|
+
verifiedEmail?.emailAddress ??
|
|
73
|
+
clerkApi.user.emailAddresses[0]?.emailAddress,
|
|
74
|
+
emailVerified: verifiedEmail !== undefined,
|
|
75
|
+
pictureUrl: clerkApi.user.imageUrl,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
55
80
|
return clerkApi;
|
|
56
81
|
})();
|
|
57
82
|
|
|
58
83
|
async function getAccessToken() {
|
|
59
84
|
await ensureLoaded;
|
|
60
|
-
if (!clerkApi
|
|
85
|
+
if (!clerkApi?.session) {
|
|
61
86
|
throw new Error("No session available");
|
|
62
87
|
}
|
|
63
88
|
const response = await clerkApi.session.getToken();
|
|
@@ -71,20 +96,26 @@ const clerkAuth: AuthenticationProviderInitializer<
|
|
|
71
96
|
getAccessToken,
|
|
72
97
|
signOut: async () => {
|
|
73
98
|
await ensureLoaded;
|
|
74
|
-
await clerkApi
|
|
99
|
+
await clerkApi?.signOut({
|
|
75
100
|
redirectUrl: window.location.origin + redirectToAfterSignOut,
|
|
76
101
|
});
|
|
102
|
+
useAuthState.setState({
|
|
103
|
+
isAuthenticated: false,
|
|
104
|
+
isPending: false,
|
|
105
|
+
profile: null,
|
|
106
|
+
providerData: null,
|
|
107
|
+
});
|
|
77
108
|
},
|
|
78
109
|
signIn: async () => {
|
|
79
110
|
await ensureLoaded;
|
|
80
|
-
await clerkApi
|
|
111
|
+
await clerkApi?.redirectToSignIn({
|
|
81
112
|
signInForceRedirectUrl: window.location.origin + redirectToAfterSignIn,
|
|
82
113
|
signUpForceRedirectUrl: window.location.origin + redirectToAfterSignUp,
|
|
83
114
|
});
|
|
84
115
|
},
|
|
85
116
|
signUp: async () => {
|
|
86
117
|
await ensureLoaded;
|
|
87
|
-
await clerkApi
|
|
118
|
+
await clerkApi?.redirectToSignUp({
|
|
88
119
|
signInForceRedirectUrl: window.location.origin + redirectToAfterSignIn,
|
|
89
120
|
signUpForceRedirectUrl: window.location.origin + redirectToAfterSignUp,
|
|
90
121
|
});
|
|
@@ -307,7 +307,7 @@ export class OpenIDAuthenticationProvider implements AuthenticationProvider {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
const redirectUrl = new URL(url);
|
|
310
|
-
redirectUrl.pathname = this.
|
|
310
|
+
redirectUrl.pathname = this.callbackUrlPath;
|
|
311
311
|
redirectUrl.search = "";
|
|
312
312
|
|
|
313
313
|
const response = await oauth.authorizationCodeGrantRequest(
|
|
@@ -10,20 +10,32 @@ export const ColorMap = {
|
|
|
10
10
|
gray: "bg-gray-400 dark:bg-gray-600",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
export const ColorMapInvert = {
|
|
14
|
+
green: "text-green-500 dark:text-green-600",
|
|
15
|
+
blue: "text-sky-400 dark:text-sky-600",
|
|
16
|
+
yellow: "text-yellow-400 dark:text-yellow-600",
|
|
17
|
+
red: "text-red-400 dark:text-red-600",
|
|
18
|
+
purple: "text-purple-400 dark:text-purple-600",
|
|
19
|
+
indigo: "text-indigo-400 dark:text-indigo-600",
|
|
20
|
+
gray: "text-gray-400 dark:text-gray-600",
|
|
21
|
+
};
|
|
22
|
+
|
|
13
23
|
export const SidebarBadge = ({
|
|
14
24
|
color,
|
|
15
25
|
label,
|
|
16
26
|
className,
|
|
27
|
+
invert,
|
|
17
28
|
}: {
|
|
18
29
|
color: keyof typeof ColorMap;
|
|
19
30
|
label: string;
|
|
20
31
|
className?: string;
|
|
32
|
+
invert?: boolean;
|
|
21
33
|
}) => {
|
|
22
34
|
return (
|
|
23
35
|
<span
|
|
24
36
|
className={cn(
|
|
25
37
|
"mt-0.5 flex items-center duration-200 transition-opacity text-center uppercase font-mono text-[0.65rem] font-bold rounded text-background dark:text-zinc-50 h-4 px-1",
|
|
26
|
-
ColorMap[color],
|
|
38
|
+
invert ? ColorMapInvert[color] : ColorMap[color],
|
|
27
39
|
className,
|
|
28
40
|
)}
|
|
29
41
|
>
|
|
@@ -66,7 +66,7 @@ export const SidebarCategory = ({
|
|
|
66
66
|
className={navigationListItem({
|
|
67
67
|
isActive: false,
|
|
68
68
|
className: [
|
|
69
|
-
"text-start",
|
|
69
|
+
"text-start font-medium",
|
|
70
70
|
isCollapsible
|
|
71
71
|
? "cursor-pointer"
|
|
72
72
|
: "cursor-default hover:bg-transparent",
|
|
@@ -112,14 +112,10 @@ export const SidebarCategory = ({
|
|
|
112
112
|
className={cn(
|
|
113
113
|
// CollapsibleContent class is used to animate and it should only be applied when the user has triggered the toggle
|
|
114
114
|
hasInteracted && "CollapsibleContent",
|
|
115
|
-
"
|
|
115
|
+
"my-1",
|
|
116
116
|
)}
|
|
117
117
|
>
|
|
118
|
-
<ul
|
|
119
|
-
className={
|
|
120
|
-
"relative after:absolute after:-left-[--padding-nav-item] after:translate-x-[1.5px] after:top-0 after:bottom-0 after:w-px after:bg-border"
|
|
121
|
-
}
|
|
122
|
-
>
|
|
118
|
+
<ul className={"relative"}>
|
|
123
119
|
{category.items.map((item) => (
|
|
124
120
|
<SidebarItem
|
|
125
121
|
key={
|
|
@@ -15,6 +15,7 @@ export const SidebarWrapper = forwardRef<
|
|
|
15
15
|
"scrollbar peer hidden lg:flex flex-col fixed text-sm overflow-y-auto shrink-0 border-r pr-10",
|
|
16
16
|
"-mx-[--padding-nav-item] pb-20 pt-[--padding-content-top]",
|
|
17
17
|
"w-[--side-nav-width] h-[calc(100%-var(--header-height))] scroll-pt-2 gap-2",
|
|
18
|
+
!pushMainContent && "border-r-0",
|
|
18
19
|
className,
|
|
19
20
|
)}
|
|
20
21
|
ref={ref}
|
|
@@ -329,9 +329,12 @@ const OperationItem = builder
|
|
|
329
329
|
([mediaType, content]) => ({
|
|
330
330
|
mediaType,
|
|
331
331
|
schema: content.schema,
|
|
332
|
-
examples:
|
|
333
|
-
([name, value]) => ({
|
|
334
|
-
|
|
332
|
+
examples: content.examples
|
|
333
|
+
? Object.entries(content.examples).map(([name, value]) => ({
|
|
334
|
+
name,
|
|
335
|
+
...(typeof value === "string" ? { value } : value),
|
|
336
|
+
}))
|
|
337
|
+
: [],
|
|
335
338
|
encoding: Object.entries(content.encoding ?? {}).map(
|
|
336
339
|
([name, value]) => ({ name, ...value }),
|
|
337
340
|
),
|
|
@@ -351,9 +354,12 @@ const OperationItem = builder
|
|
|
351
354
|
([mediaType, { schema, examples }]) => ({
|
|
352
355
|
mediaType,
|
|
353
356
|
schema,
|
|
354
|
-
examples:
|
|
355
|
-
([name, value]) => ({
|
|
356
|
-
|
|
357
|
+
examples: examples
|
|
358
|
+
? Object.entries(examples).map(([name, value]) => ({
|
|
359
|
+
name,
|
|
360
|
+
...(typeof value === "string" ? { value } : value),
|
|
361
|
+
}))
|
|
362
|
+
: [],
|
|
357
363
|
}),
|
|
358
364
|
),
|
|
359
365
|
headers: response.headers,
|
|
@@ -379,7 +385,10 @@ const OperationItem = builder
|
|
|
379
385
|
const Schema = builder.objectRef<OpenAPIDocument>("Schema").implement({
|
|
380
386
|
fields: (t) => ({
|
|
381
387
|
openapi: t.string({ resolve: (root) => root.openapi }),
|
|
382
|
-
url: t.string({
|
|
388
|
+
url: t.string({
|
|
389
|
+
resolve: (root) => root.servers?.at(0)?.url,
|
|
390
|
+
nullable: true,
|
|
391
|
+
}),
|
|
383
392
|
servers: t.field({
|
|
384
393
|
type: [ServerItem],
|
|
385
394
|
resolve: (root) => root.servers ?? [],
|
|
@@ -42,12 +42,27 @@ export const upgradeSchema = (schema: RecordAny): OpenAPIDocument => {
|
|
|
42
42
|
|
|
43
43
|
schema = traverse(schema, (sub) => {
|
|
44
44
|
if (sub.example !== undefined) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
const isExampleObject =
|
|
46
|
+
typeof sub.example === "object" &&
|
|
47
|
+
(sub.example.summary !== undefined ||
|
|
48
|
+
sub.example.description !== undefined ||
|
|
49
|
+
sub.example.value !== undefined ||
|
|
50
|
+
sub.example.externalValue !== undefined);
|
|
51
|
+
|
|
52
|
+
const exampleValue = isExampleObject
|
|
53
|
+
? sub.example
|
|
54
|
+
: { value: sub.example };
|
|
55
|
+
|
|
56
|
+
if (!sub.examples) {
|
|
57
|
+
sub.examples = { default: exampleValue };
|
|
58
|
+
} else {
|
|
59
|
+
sub.examples = {
|
|
60
|
+
default: exampleValue,
|
|
61
|
+
...sub.examples,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
48
64
|
delete sub.example;
|
|
49
65
|
}
|
|
50
|
-
|
|
51
66
|
return sub;
|
|
52
67
|
});
|
|
53
68
|
|
|
@@ -49,7 +49,7 @@ export const CollapsibleCode = ({
|
|
|
49
49
|
)}
|
|
50
50
|
>
|
|
51
51
|
{!open && isOverflowing && (
|
|
52
|
-
<div className=" absolute inset-0 bg-gradient-to-b from-transparent to-zinc-50/
|
|
52
|
+
<div className=" absolute inset-0 bg-gradient-to-b from-transparent to-zinc-50/60 dark:to-zinc-950/90 z-10 group-hover:to-transparent"></div>
|
|
53
53
|
)}
|
|
54
54
|
<div ref={contentRef}>{children}</div>
|
|
55
55
|
{!open && isOverflowing && (
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
|
|
3
|
+
import { type SchemaObject } from "../../oas/graphql/index.js";
|
|
4
|
+
import { CollapsibleCode } from "./CollapsibleCode.js";
|
|
5
|
+
import type { OperationListItemResult } from "./OperationList.js";
|
|
6
|
+
import * as SidecarBox from "./SidecarBox.js";
|
|
7
|
+
import { SimpleSelect } from "./SimpleSelect.js";
|
|
8
|
+
import { generateSchemaExample } from "./util/generateSchemaExample.js";
|
|
9
|
+
|
|
10
|
+
export type Content = NonNullable<
|
|
11
|
+
NonNullable<OperationListItemResult["requestBody"]>["content"]
|
|
12
|
+
>;
|
|
13
|
+
export type Example = NonNullable<
|
|
14
|
+
NonNullable<Content[number]["examples"]>
|
|
15
|
+
>[number];
|
|
16
|
+
|
|
17
|
+
const formatExample = (example: unknown) => {
|
|
18
|
+
if (example == null) return "No example";
|
|
19
|
+
|
|
20
|
+
if (typeof example === "string" || typeof example !== "object") {
|
|
21
|
+
return String(example).trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return JSON.stringify(example, null, 2);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getLanguageForContentType = (mediaType?: string) =>
|
|
28
|
+
mediaType
|
|
29
|
+
? ({
|
|
30
|
+
"application/json": "json",
|
|
31
|
+
"application/xml": "xml",
|
|
32
|
+
"application/x-yaml": "yaml",
|
|
33
|
+
"text/csv": "csv",
|
|
34
|
+
"application/javascript": "javascript",
|
|
35
|
+
"application/graphql": "graphql",
|
|
36
|
+
"text/plain": "plain",
|
|
37
|
+
"application/x-www-form-urlencoded": "plain",
|
|
38
|
+
"multipart/form-data": "plain",
|
|
39
|
+
"application/x-protobuf": "plain",
|
|
40
|
+
}[mediaType] ?? "plain")
|
|
41
|
+
: "plain";
|
|
42
|
+
|
|
43
|
+
const getExampleName = (example: Example) => {
|
|
44
|
+
if (example.summary) return example.summary;
|
|
45
|
+
if (example.name) return example.name;
|
|
46
|
+
if (example.description) return example.description;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getExampleValue = (example?: Example) => {
|
|
50
|
+
if (!example) return undefined;
|
|
51
|
+
|
|
52
|
+
if (example.value !== undefined) return example.value;
|
|
53
|
+
if (example.externalValue) return example.externalValue;
|
|
54
|
+
if (example.name) return example;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export interface UseExampleDisplayProps {
|
|
58
|
+
content: Content;
|
|
59
|
+
description?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const useSidecarExamples = ({
|
|
63
|
+
content,
|
|
64
|
+
description,
|
|
65
|
+
}: UseExampleDisplayProps) => {
|
|
66
|
+
const [selectedContentTypeIndex, setSelectedContentTypeIndex] = useState(0);
|
|
67
|
+
const [selectedExampleIndex, setSelectedExampleIndex] = useState(0);
|
|
68
|
+
|
|
69
|
+
const selectedContent = content[selectedContentTypeIndex];
|
|
70
|
+
const examples = (selectedContent?.examples ?? []) as Example[];
|
|
71
|
+
const hasExamples = examples.length > 0;
|
|
72
|
+
|
|
73
|
+
const selectedExample = hasExamples
|
|
74
|
+
? examples[selectedExampleIndex]
|
|
75
|
+
: undefined;
|
|
76
|
+
|
|
77
|
+
const example = hasExamples
|
|
78
|
+
? getExampleValue(selectedExample)
|
|
79
|
+
: selectedContent?.schema
|
|
80
|
+
? generateSchemaExample(selectedContent.schema as SchemaObject)
|
|
81
|
+
: undefined;
|
|
82
|
+
|
|
83
|
+
const formattedExample = formatExample(example);
|
|
84
|
+
const language = getLanguageForContentType(selectedContent?.mediaType);
|
|
85
|
+
|
|
86
|
+
const SidecarBody = () => (
|
|
87
|
+
<SidecarBox.Body className="p-0">
|
|
88
|
+
{selectedExample?.externalValue ? (
|
|
89
|
+
<div className="p-2">
|
|
90
|
+
<a
|
|
91
|
+
href={selectedExample.externalValue}
|
|
92
|
+
target="_blank"
|
|
93
|
+
rel="noopener noreferrer"
|
|
94
|
+
className="text-xs text-primary hover:underline"
|
|
95
|
+
>
|
|
96
|
+
View External Example →
|
|
97
|
+
</a>
|
|
98
|
+
</div>
|
|
99
|
+
) : (
|
|
100
|
+
<CollapsibleCode>
|
|
101
|
+
<SyntaxHighlight
|
|
102
|
+
language={language}
|
|
103
|
+
noBackground
|
|
104
|
+
copyable
|
|
105
|
+
className="[--scrollbar-color:gray] text-xs max-h-[500px] p-2"
|
|
106
|
+
code={formattedExample}
|
|
107
|
+
/>
|
|
108
|
+
</CollapsibleCode>
|
|
109
|
+
)}
|
|
110
|
+
{selectedExample?.description && (
|
|
111
|
+
<div className="border-t text-xs px-2 py-1">
|
|
112
|
+
{selectedExample.description}
|
|
113
|
+
</div>
|
|
114
|
+
)}
|
|
115
|
+
</SidecarBox.Body>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
const SidebarFooter = () => (
|
|
119
|
+
<SidecarBox.Footer className="flex items-center text-xs gap-2 justify-between py-1">
|
|
120
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
121
|
+
{content.length > 1 ? (
|
|
122
|
+
<div className="flex items-center gap-1">
|
|
123
|
+
<SimpleSelect
|
|
124
|
+
className="max-w-[200px]"
|
|
125
|
+
value={selectedContentTypeIndex.toString()}
|
|
126
|
+
onChange={(e) =>
|
|
127
|
+
setSelectedContentTypeIndex(Number(e.target.value))
|
|
128
|
+
}
|
|
129
|
+
options={content.map((c, index) => ({
|
|
130
|
+
value: index.toString(),
|
|
131
|
+
label: c.mediaType,
|
|
132
|
+
}))}
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
) : (
|
|
136
|
+
<span className="font-mono text-[11px]">{content[0]?.mediaType}</span>
|
|
137
|
+
)}
|
|
138
|
+
{description && (
|
|
139
|
+
<span className="text-muted-foreground truncate">{description}</span>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
{examples.length > 1 && (
|
|
143
|
+
<div className="flex items-center gap-1">
|
|
144
|
+
<SimpleSelect
|
|
145
|
+
className="max-w-[180px]"
|
|
146
|
+
value={selectedExampleIndex.toString()}
|
|
147
|
+
onChange={(e) => setSelectedExampleIndex(Number(e.target.value))}
|
|
148
|
+
options={examples.map((example, index) => ({
|
|
149
|
+
value: index.toString(),
|
|
150
|
+
label: getExampleName(example) ?? `Example ${index + 1}`,
|
|
151
|
+
}))}
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
</SidecarBox.Footer>
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
SidecarBody,
|
|
160
|
+
SidebarFooter,
|
|
161
|
+
hasContent: hasExamples || content.length > 0,
|
|
162
|
+
};
|
|
163
|
+
};
|
|
@@ -57,9 +57,11 @@ export const OperationListItem = ({
|
|
|
57
57
|
}
|
|
58
58
|
}}
|
|
59
59
|
>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
{serverUrl && (
|
|
61
|
+
<div className="text-neutral-400 dark:text-neutral-500 truncate">
|
|
62
|
+
{serverUrl}
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
63
65
|
<div className="text-neutral-900 dark:text-neutral-200">
|
|
64
66
|
{operation.path}
|
|
65
67
|
</div>
|
|
@@ -45,7 +45,7 @@ export const ParameterListItem = ({
|
|
|
45
45
|
)}
|
|
46
46
|
</code>
|
|
47
47
|
{parameter.required && (
|
|
48
|
-
<span className="py-px px-1.5 font-medium bg-primary/75 text-
|
|
48
|
+
<span className="py-px px-1.5 font-medium bg-primary/75 text-primary-foreground rounded-lg">
|
|
49
49
|
required
|
|
50
50
|
</span>
|
|
51
51
|
)}
|
|
@@ -1,44 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { type SchemaObject } from "../../oas/graphql/index.js";
|
|
3
|
-
import { CollapsibleCode } from "./CollapsibleCode.js";
|
|
4
|
-
import type { OperationListItemResult } from "./OperationList.js";
|
|
1
|
+
import { Content, useSidecarExamples } from "./ExampleDisplay.js";
|
|
5
2
|
import * as SidecarBox from "./SidecarBox.js";
|
|
6
|
-
import { generateSchemaExample } from "./util/generateSchemaExample.js";
|
|
7
3
|
|
|
8
|
-
type Content = NonNullable<
|
|
9
|
-
NonNullable<OperationListItemResult["requestBody"]>["content"]
|
|
10
|
-
>;
|
|
11
|
-
|
|
12
|
-
// @todo should we handle multiple content types?
|
|
13
4
|
export const RequestBodySidecarBox = ({ content }: { content: Content }) => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const example =
|
|
19
|
-
firstContent?.examples?.at(0)?.value ??
|
|
20
|
-
(firstContent?.schema
|
|
21
|
-
? generateSchemaExample(firstContent.schema as SchemaObject)
|
|
22
|
-
: "");
|
|
5
|
+
const { SidecarBody, SidebarFooter, hasContent } = useSidecarExamples({
|
|
6
|
+
content,
|
|
7
|
+
});
|
|
23
8
|
|
|
24
9
|
return (
|
|
25
|
-
|
|
26
|
-
<SidecarBox.
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<SyntaxHighlight
|
|
33
|
-
language={example ? "json" : "plain"}
|
|
34
|
-
noBackground
|
|
35
|
-
copyable
|
|
36
|
-
className="[--scrollbar-color:gray] text-xs max-h-[500px] p-2"
|
|
37
|
-
code={example ? JSON.stringify(example, null, 2) : "No example"}
|
|
38
|
-
/>
|
|
39
|
-
</CollapsibleCode>
|
|
40
|
-
</SidecarBox.Body>
|
|
41
|
-
</SidecarBox.Root>
|
|
42
|
-
</>
|
|
10
|
+
<SidecarBox.Root>
|
|
11
|
+
<SidecarBox.Head className="text-xs flex justify-between items-center">
|
|
12
|
+
<span className="font-mono">Request Body Example</span>
|
|
13
|
+
</SidecarBox.Head>
|
|
14
|
+
<SidecarBody />
|
|
15
|
+
{hasContent && <SidebarFooter />}
|
|
16
|
+
</SidecarBox.Root>
|
|
43
17
|
);
|
|
44
18
|
};
|