@lukoweb/apitogo 0.1.1 → 0.1.3
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/cli/cli.js +168 -57
- package/dist/declarations/lib/ui/Command.d.ts +1 -1
- package/dist/declarations/lib/util/MdxComponents.d.ts +1 -1
- package/package.json +2 -1
- package/src/app/main.tsx +150 -150
- package/src/config/loader.ts +245 -245
- package/src/index.ts +33 -33
- package/src/lib/authentication/components/CallbackHandler.tsx +45 -45
- package/src/lib/authentication/components/SignIn.tsx +46 -46
- package/src/lib/authentication/components/SignUp.tsx +48 -48
- package/src/lib/authentication/providers/clerk.tsx +224 -224
- package/src/lib/authentication/ui/AuthCard.tsx +7 -7
- package/src/lib/authentication/ui/EmailLinkCallbackUi.tsx +129 -125
- package/src/lib/authentication/ui/EmailLinkSentUi.tsx +105 -101
- package/src/lib/authentication/ui/EmailLinkSignInUi.tsx +100 -96
- package/src/lib/authentication/ui/EmailVerificationUi.tsx +128 -124
- package/src/lib/authentication/ui/ZudokuAuthUi.tsx +620 -616
- package/src/lib/components/AiAssistantMenuItems.tsx +102 -102
- package/src/lib/components/Autocomplete.tsx +123 -123
- package/src/lib/components/DeveloperHint.tsx +39 -35
- package/src/lib/components/Header.tsx +260 -260
- package/src/lib/components/Layout.tsx +53 -53
- package/src/lib/components/Main.tsx +51 -51
- package/src/lib/components/Mermaid.tsx +74 -70
- package/src/lib/components/MobileTopNavigation.tsx +276 -276
- package/src/lib/components/MultiSelect.tsx +86 -82
- package/src/lib/components/TopNavigation.tsx +103 -103
- package/src/lib/components/index.ts +26 -26
- package/src/lib/components/navigation/NavigationCategory.tsx +157 -157
- package/src/lib/components/navigation/NavigationFilterInput.tsx +35 -35
- package/src/lib/components/navigation/NavigationItem.tsx +181 -177
- package/src/lib/core/RouteGuard.tsx +193 -193
- package/src/lib/errors/ErrorMessage.tsx +50 -46
- package/src/lib/plugins/api-catalog/Catalog.tsx +80 -80
- package/src/lib/plugins/api-keys/CreateApiKey.tsx +128 -124
- package/src/lib/plugins/api-keys/CreateApiKeyDialog.tsx +49 -49
- package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +72 -72
- package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -342
- package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +127 -127
- package/src/lib/plugins/markdown/MdxPage.tsx +285 -285
- package/src/lib/plugins/openapi/ApiHeader.tsx +96 -96
- package/src/lib/plugins/openapi/CollapsibleCode.tsx +88 -88
- package/src/lib/plugins/openapi/DownloadSchemaButton.tsx +94 -94
- package/src/lib/plugins/openapi/GeneratedExampleSidecarBox.tsx +52 -52
- package/src/lib/plugins/openapi/OperationListItem.tsx +183 -183
- package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +63 -63
- package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +120 -117
- package/src/lib/plugins/openapi/SchemaInfo.tsx +344 -344
- package/src/lib/plugins/openapi/SchemaList.tsx +113 -113
- package/src/lib/plugins/openapi/Sidecar.tsx +312 -309
- package/src/lib/plugins/openapi/SidecarExamples.tsx +177 -174
- package/src/lib/plugins/openapi/components/EnumValues.tsx +58 -58
- package/src/lib/plugins/openapi/components/ResponseContent.tsx +117 -114
- package/src/lib/plugins/openapi/playground/BodyPanel.tsx +274 -271
- package/src/lib/plugins/openapi/playground/CollapsibleHeader.tsx +51 -51
- package/src/lib/plugins/openapi/playground/ExamplesDropdown.tsx +60 -60
- package/src/lib/plugins/openapi/playground/Headers.tsx +188 -181
- package/src/lib/plugins/openapi/playground/IdentityDialog.tsx +75 -75
- package/src/lib/plugins/openapi/playground/IdentitySelector.tsx +38 -38
- package/src/lib/plugins/openapi/playground/ParamsGrid.tsx +45 -45
- package/src/lib/plugins/openapi/playground/Playground.tsx +602 -599
- package/src/lib/plugins/openapi/playground/PlaygroundDialog.tsx +56 -56
- package/src/lib/plugins/openapi/playground/QueryParams.tsx +151 -148
- package/src/lib/plugins/openapi/playground/RequestLoginDialog.tsx +70 -70
- package/src/lib/plugins/openapi/playground/request-panel/MultipartField.tsx +91 -91
- package/src/lib/plugins/openapi/playground/result-panel/AudioPlayer.tsx +50 -50
- package/src/lib/plugins/openapi/playground/result-panel/ResponseTab.tsx +355 -355
- package/src/lib/plugins/openapi/playground/result-panel/ResultPanel.tsx +106 -102
- package/src/lib/plugins/openapi/schema/SchemaPropertyItem.tsx +189 -184
- package/src/lib/plugins/openapi/schema/SchemaView.tsx +299 -299
- package/src/lib/plugins/search-pagefind/IndexingDialog.tsx +162 -162
- package/src/lib/plugins/search-pagefind/PagefindSearch.tsx +202 -202
- package/src/lib/plugins/search-pagefind/ResultList.tsx +122 -118
- package/src/lib/ui/ActionButton.tsx +21 -21
- package/src/lib/ui/Command.tsx +191 -191
- package/src/lib/util/MdxComponents.tsx +150 -150
- package/src/vite/config.ts +234 -231
- package/src/vite/dev-server.ts +291 -291
- package/src/vite/plugin-api-keys.ts +50 -50
- package/src/vite/plugin-api.ts +320 -318
- package/src/vite/plugin-auth.ts +38 -38
- package/src/vite/plugin-component.ts +45 -39
- package/src/vite/plugin-config.ts +75 -75
- package/src/vite/plugin-custom-pages.ts +36 -36
- package/src/vite/plugin-docs.ts +202 -202
- package/src/vite/plugin-markdown-export.ts +214 -214
- package/src/vite/plugin-mdx.ts +149 -149
- package/src/vite/plugin-navigation.ts +106 -106
- package/src/vite/plugin-search.ts +47 -47
- package/src/vite/plugin.ts +42 -42
- package/src/vite/prerender/prerender.ts +233 -233
|
@@ -1,344 +1,344 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { Markdown } from "../../components/Markdown.js";
|
|
15
|
-
import { PagefindSearchMeta } from "../../components/PagefindSearchMeta.js";
|
|
16
|
-
import { Badge } from "../../ui/Badge.js";
|
|
17
|
-
import { Button } from "../../ui/Button.js";
|
|
18
|
-
import { Card, CardContent } from "../../ui/Card.js";
|
|
19
|
-
import {
|
|
20
|
-
Item,
|
|
21
|
-
ItemActions,
|
|
22
|
-
ItemContent,
|
|
23
|
-
ItemDescription,
|
|
24
|
-
ItemTitle,
|
|
25
|
-
} from "../../ui/Item.js";
|
|
26
|
-
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/Popover.js";
|
|
27
|
-
import { slugify } from "../../util/slugify.js";
|
|
28
|
-
import { ApiHeader } from "./ApiHeader.js";
|
|
29
|
-
import { useCreateQuery } from "./client/useCreateQuery.js";
|
|
30
|
-
import { useOasConfig } from "./context.js";
|
|
31
|
-
import type { SchemaInfoQuery as SchemaInfoQueryType } from "./graphql/graphql.js";
|
|
32
|
-
import { graphql } from "./graphql/index.js";
|
|
33
|
-
import { useWarmupSchema } from "./util/useWarmupSchema.js";
|
|
34
|
-
|
|
35
|
-
const SchemaInfoQuery = graphql(/* GraphQL */ `
|
|
36
|
-
query SchemaInfo($input: JSON!, $type: SchemaType!) {
|
|
37
|
-
schema(input: $input, type: $type) {
|
|
38
|
-
servers {
|
|
39
|
-
url
|
|
40
|
-
description
|
|
41
|
-
}
|
|
42
|
-
license {
|
|
43
|
-
name
|
|
44
|
-
url
|
|
45
|
-
identifier
|
|
46
|
-
}
|
|
47
|
-
termsOfService
|
|
48
|
-
externalDocs {
|
|
49
|
-
description
|
|
50
|
-
url
|
|
51
|
-
}
|
|
52
|
-
contact {
|
|
53
|
-
name
|
|
54
|
-
url
|
|
55
|
-
email
|
|
56
|
-
}
|
|
57
|
-
description
|
|
58
|
-
summary
|
|
59
|
-
title
|
|
60
|
-
url
|
|
61
|
-
version
|
|
62
|
-
tags {
|
|
63
|
-
name
|
|
64
|
-
description
|
|
65
|
-
}
|
|
66
|
-
components {
|
|
67
|
-
schemas {
|
|
68
|
-
name
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
webhooks {
|
|
72
|
-
name
|
|
73
|
-
method
|
|
74
|
-
summary
|
|
75
|
-
description
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
`);
|
|
80
|
-
|
|
81
|
-
const InfoLink = ({
|
|
82
|
-
href,
|
|
83
|
-
icon,
|
|
84
|
-
children,
|
|
85
|
-
}: PropsWithChildren<{ href?: string; icon?: ReactNode }>) => (
|
|
86
|
-
<a
|
|
87
|
-
href={href}
|
|
88
|
-
className="inline-flex items-center gap-2 opacity-65 hover:opacity-100 [&_svg]:shrink-0 [&_svg]:size-3.5"
|
|
89
|
-
target="_blank"
|
|
90
|
-
rel="noopener noreferrer"
|
|
91
|
-
>
|
|
92
|
-
{icon}
|
|
93
|
-
<span className="truncate grow-0">{children}</span>
|
|
94
|
-
</a>
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const InfoCardContent = ({
|
|
98
|
-
schema,
|
|
99
|
-
}: {
|
|
100
|
-
schema: SchemaInfoQueryType["schema"];
|
|
101
|
-
}) => {
|
|
102
|
-
const hasInfoLinks = !!(
|
|
103
|
-
schema.license ||
|
|
104
|
-
schema.termsOfService ||
|
|
105
|
-
schema.externalDocs
|
|
106
|
-
);
|
|
107
|
-
const hasContact = !!(
|
|
108
|
-
schema.contact?.name ||
|
|
109
|
-
schema.contact?.email ||
|
|
110
|
-
schema.contact?.url
|
|
111
|
-
);
|
|
112
|
-
const hasServers = schema.servers.length > 0;
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<CardContent className="flex flex-col gap-3 text-sm">
|
|
116
|
-
{hasInfoLinks && (
|
|
117
|
-
<div className="flex flex-col gap-1.5">
|
|
118
|
-
{schema.license && (
|
|
119
|
-
<InfoLink href={schema.license.url ?? undefined}>
|
|
120
|
-
{schema.license.name}
|
|
121
|
-
</InfoLink>
|
|
122
|
-
)}
|
|
123
|
-
{schema.termsOfService && (
|
|
124
|
-
<InfoLink href={schema.termsOfService}>Terms of Service</InfoLink>
|
|
125
|
-
)}
|
|
126
|
-
{schema.externalDocs && (
|
|
127
|
-
<InfoLink href={schema.externalDocs.url}>
|
|
128
|
-
{schema.externalDocs.description ?? "Documentation"}
|
|
129
|
-
</InfoLink>
|
|
130
|
-
)}
|
|
131
|
-
</div>
|
|
132
|
-
)}
|
|
133
|
-
{hasInfoLinks && (hasContact || hasServers) && <Separator />}
|
|
134
|
-
{hasContact && (
|
|
135
|
-
<div className="flex flex-col gap-1.5">
|
|
136
|
-
<span className="text-xs text-muted-foreground font-medium uppercase tracking-wide">
|
|
137
|
-
Contact
|
|
138
|
-
</span>
|
|
139
|
-
{schema.contact?.name && <span>{schema.contact.name}</span>}
|
|
140
|
-
{schema.contact?.email && (
|
|
141
|
-
<InfoLink
|
|
142
|
-
href={`mailto:${schema.contact.email}`}
|
|
143
|
-
icon={<MailIcon />}
|
|
144
|
-
>
|
|
145
|
-
{schema.contact.email}
|
|
146
|
-
</InfoLink>
|
|
147
|
-
)}
|
|
148
|
-
{schema.contact?.url && (
|
|
149
|
-
<InfoLink href={schema.contact.url} icon={<GlobeIcon />}>
|
|
150
|
-
{schema.contact.url}
|
|
151
|
-
</InfoLink>
|
|
152
|
-
)}
|
|
153
|
-
</div>
|
|
154
|
-
)}
|
|
155
|
-
{hasContact && hasServers && <Separator />}
|
|
156
|
-
{hasServers && (
|
|
157
|
-
<div className="flex flex-col gap-1.5">
|
|
158
|
-
<span className="text-xs text-muted-foreground font-medium uppercase tracking-wide">
|
|
159
|
-
Servers
|
|
160
|
-
</span>
|
|
161
|
-
{schema.servers.map((server) => (
|
|
162
|
-
<div key={server.url}>
|
|
163
|
-
<code className="text-xs select-all break-all">{server.url}</code>
|
|
164
|
-
{server.description && (
|
|
165
|
-
<p className="text-muted-foreground text-xs">
|
|
166
|
-
{server.description}
|
|
167
|
-
</p>
|
|
168
|
-
)}
|
|
169
|
-
</div>
|
|
170
|
-
))}
|
|
171
|
-
</div>
|
|
172
|
-
)}
|
|
173
|
-
</CardContent>
|
|
174
|
-
);
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
export const SchemaInfo = () => {
|
|
178
|
-
const { input, type } = useOasConfig();
|
|
179
|
-
const query = useCreateQuery(SchemaInfoQuery, { input, type });
|
|
180
|
-
const {
|
|
181
|
-
data: { schema },
|
|
182
|
-
} = useSuspenseQuery(query);
|
|
183
|
-
const { title, description } = schema;
|
|
184
|
-
|
|
185
|
-
useWarmupSchema();
|
|
186
|
-
|
|
187
|
-
const hasCardContent = !!(
|
|
188
|
-
schema.contact?.name ||
|
|
189
|
-
schema.contact?.email ||
|
|
190
|
-
schema.contact?.url ||
|
|
191
|
-
schema.servers.length > 0 ||
|
|
192
|
-
schema.license ||
|
|
193
|
-
schema.termsOfService ||
|
|
194
|
-
schema.externalDocs
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
const tags = schema.tags.flatMap(({ name, description }) =>
|
|
198
|
-
name ? { name, description } : [],
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<div
|
|
203
|
-
className="pt-(--padding-content-top)"
|
|
204
|
-
data-pagefind-filter="section:openapi"
|
|
205
|
-
data-pagefind-meta="section:openapi"
|
|
206
|
-
>
|
|
207
|
-
<PagefindSearchMeta name="category">{title}</PagefindSearchMeta>
|
|
208
|
-
<Helmet>
|
|
209
|
-
{title && <title>{title}</title>}
|
|
210
|
-
{description && <meta name="description" content={description} />}
|
|
211
|
-
</Helmet>
|
|
212
|
-
|
|
213
|
-
<div className="mb-8 flex flex-col gap-4">
|
|
214
|
-
<ApiHeader heading={title} headingId="description" />
|
|
215
|
-
|
|
216
|
-
<div className="grid grid-cols-1 xl:grid-cols-[1fr_minmax(250px,380px)] gap-8">
|
|
217
|
-
{hasCardContent && (
|
|
218
|
-
<div className="xl:hidden sticky top-(--top-nav-height) lg:top-(--scroll-padding) z-10 row-start-1 col-start-1 justify-self-end self-start">
|
|
219
|
-
<Popover>
|
|
220
|
-
<PopoverTrigger asChild>
|
|
221
|
-
<Button
|
|
222
|
-
variant="outline"
|
|
223
|
-
size="icon"
|
|
224
|
-
className="shadow-sm rounded-full"
|
|
225
|
-
>
|
|
226
|
-
<InfoIcon />
|
|
227
|
-
</Button>
|
|
228
|
-
</PopoverTrigger>
|
|
229
|
-
<PopoverContent
|
|
230
|
-
align="end"
|
|
231
|
-
className="xl:hidden w-full max-w-full md:max-w-sm"
|
|
232
|
-
>
|
|
233
|
-
<InfoCardContent schema={schema} />
|
|
234
|
-
</PopoverContent>
|
|
235
|
-
</Popover>
|
|
236
|
-
</div>
|
|
237
|
-
)}
|
|
238
|
-
<div className="flex flex-col gap-6 row-start-1 col-start-1">
|
|
239
|
-
{schema.summary && (
|
|
240
|
-
<p className="text-lg text-muted-foreground">{schema.summary}</p>
|
|
241
|
-
)}
|
|
242
|
-
{schema.description && (
|
|
243
|
-
<Markdown
|
|
244
|
-
className="prose-img:max-w-prose prose-sm max-w-full lg:max-w-2xl"
|
|
245
|
-
content={schema.description}
|
|
246
|
-
/>
|
|
247
|
-
)}
|
|
248
|
-
{tags.length > 0 && (
|
|
249
|
-
<div>
|
|
250
|
-
<div className="flex items-center gap-2 text-sm uppercase tracking-wide text-muted-foreground mb-4">
|
|
251
|
-
<TagIcon size={14} />
|
|
252
|
-
Tags
|
|
253
|
-
</div>
|
|
254
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
255
|
-
{tags.map((tag) => (
|
|
256
|
-
<Item key={tag.name} variant="outline" asChild>
|
|
257
|
-
<Link to={slugify(tag.name)}>
|
|
258
|
-
<ItemContent>
|
|
259
|
-
<ItemTitle>{tag.name}</ItemTitle>
|
|
260
|
-
{tag.description && (
|
|
261
|
-
<ItemDescription asChild>
|
|
262
|
-
<Markdown
|
|
263
|
-
components={{
|
|
264
|
-
// Because the description is wrapped in a <p> and a <Link> already
|
|
265
|
-
p: ({ children }) => children,
|
|
266
|
-
a: (props) => <span {...props} />,
|
|
267
|
-
}}
|
|
268
|
-
content={tag.description}
|
|
269
|
-
className="prose-sm text-pretty"
|
|
270
|
-
/>
|
|
271
|
-
</ItemDescription>
|
|
272
|
-
)}
|
|
273
|
-
</ItemContent>
|
|
274
|
-
</Link>
|
|
275
|
-
</Item>
|
|
276
|
-
))}
|
|
277
|
-
</div>
|
|
278
|
-
</div>
|
|
279
|
-
)}
|
|
280
|
-
{(schema.components?.schemas?.length ?? 0) > 0 && (
|
|
281
|
-
<div>
|
|
282
|
-
<div className="flex items-center gap-2 text-sm uppercase tracking-wide text-muted-foreground mb-4">
|
|
283
|
-
<BracesIcon size={14} />
|
|
284
|
-
Schemas
|
|
285
|
-
</div>
|
|
286
|
-
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
|
|
287
|
-
{schema.components?.schemas?.map((s) => (
|
|
288
|
-
<Item key={s.name} variant="outline" title={s.name} asChild>
|
|
289
|
-
<Link to={`~schemas#${slugify(s.name)}`}>
|
|
290
|
-
<span className="text-sm font-medium leading-snug truncate">
|
|
291
|
-
{s.name}
|
|
292
|
-
</span>
|
|
293
|
-
</Link>
|
|
294
|
-
</Item>
|
|
295
|
-
))}
|
|
296
|
-
</div>
|
|
297
|
-
</div>
|
|
298
|
-
)}
|
|
299
|
-
{schema.webhooks.length > 0 && (
|
|
300
|
-
<div>
|
|
301
|
-
<div className="flex items-center gap-2 text-sm uppercase tracking-wide text-muted-foreground mb-4">
|
|
302
|
-
<WebhookIcon size={14} />
|
|
303
|
-
Webhooks
|
|
304
|
-
</div>
|
|
305
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
306
|
-
{schema.webhooks.map((webhook) => (
|
|
307
|
-
<Item
|
|
308
|
-
key={`${webhook.name}-${webhook.method}`}
|
|
309
|
-
variant="outline"
|
|
310
|
-
>
|
|
311
|
-
<ItemContent>
|
|
312
|
-
<ItemTitle>{webhook.name}</ItemTitle>
|
|
313
|
-
{(webhook.summary || webhook.description) && (
|
|
314
|
-
<ItemDescription>
|
|
315
|
-
{webhook.summary ?? webhook.description}
|
|
316
|
-
</ItemDescription>
|
|
317
|
-
)}
|
|
318
|
-
</ItemContent>
|
|
319
|
-
<ItemActions>
|
|
320
|
-
<Badge
|
|
321
|
-
variant="muted"
|
|
322
|
-
className="text-[10px] font-mono"
|
|
323
|
-
>
|
|
324
|
-
{webhook.method}
|
|
325
|
-
</Badge>
|
|
326
|
-
</ItemActions>
|
|
327
|
-
</Item>
|
|
328
|
-
))}
|
|
329
|
-
</div>
|
|
330
|
-
</div>
|
|
331
|
-
)}
|
|
332
|
-
</div>
|
|
333
|
-
{hasCardContent && (
|
|
334
|
-
<div className="hidden xl:block">
|
|
335
|
-
<Card className="sticky top-(--scroll-padding)">
|
|
336
|
-
<InfoCardContent schema={schema} />
|
|
337
|
-
</Card>
|
|
338
|
-
</div>
|
|
339
|
-
)}
|
|
340
|
-
</div>
|
|
341
|
-
</div>
|
|
342
|
-
</div>
|
|
343
|
-
);
|
|
344
|
-
};
|
|
1
|
+
import { Separator } from "@lukoweb/apitogo/ui/Separator.js";
|
|
2
|
+
import { useSuspenseQuery } from "@tanstack/react-query";
|
|
3
|
+
import { Helmet } from "@zudoku/react-helmet-async";
|
|
4
|
+
import {
|
|
5
|
+
BracesIcon,
|
|
6
|
+
GlobeIcon,
|
|
7
|
+
InfoIcon,
|
|
8
|
+
MailIcon,
|
|
9
|
+
TagIcon,
|
|
10
|
+
WebhookIcon,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import type { PropsWithChildren, ReactNode } from "react";
|
|
13
|
+
import { Link } from "react-router";
|
|
14
|
+
import { Markdown } from "../../components/Markdown.js";
|
|
15
|
+
import { PagefindSearchMeta } from "../../components/PagefindSearchMeta.js";
|
|
16
|
+
import { Badge } from "../../ui/Badge.js";
|
|
17
|
+
import { Button } from "../../ui/Button.js";
|
|
18
|
+
import { Card, CardContent } from "../../ui/Card.js";
|
|
19
|
+
import {
|
|
20
|
+
Item,
|
|
21
|
+
ItemActions,
|
|
22
|
+
ItemContent,
|
|
23
|
+
ItemDescription,
|
|
24
|
+
ItemTitle,
|
|
25
|
+
} from "../../ui/Item.js";
|
|
26
|
+
import { Popover, PopoverContent, PopoverTrigger } from "../../ui/Popover.js";
|
|
27
|
+
import { slugify } from "../../util/slugify.js";
|
|
28
|
+
import { ApiHeader } from "./ApiHeader.js";
|
|
29
|
+
import { useCreateQuery } from "./client/useCreateQuery.js";
|
|
30
|
+
import { useOasConfig } from "./context.js";
|
|
31
|
+
import type { SchemaInfoQuery as SchemaInfoQueryType } from "./graphql/graphql.js";
|
|
32
|
+
import { graphql } from "./graphql/index.js";
|
|
33
|
+
import { useWarmupSchema } from "./util/useWarmupSchema.js";
|
|
34
|
+
|
|
35
|
+
const SchemaInfoQuery = graphql(/* GraphQL */ `
|
|
36
|
+
query SchemaInfo($input: JSON!, $type: SchemaType!) {
|
|
37
|
+
schema(input: $input, type: $type) {
|
|
38
|
+
servers {
|
|
39
|
+
url
|
|
40
|
+
description
|
|
41
|
+
}
|
|
42
|
+
license {
|
|
43
|
+
name
|
|
44
|
+
url
|
|
45
|
+
identifier
|
|
46
|
+
}
|
|
47
|
+
termsOfService
|
|
48
|
+
externalDocs {
|
|
49
|
+
description
|
|
50
|
+
url
|
|
51
|
+
}
|
|
52
|
+
contact {
|
|
53
|
+
name
|
|
54
|
+
url
|
|
55
|
+
email
|
|
56
|
+
}
|
|
57
|
+
description
|
|
58
|
+
summary
|
|
59
|
+
title
|
|
60
|
+
url
|
|
61
|
+
version
|
|
62
|
+
tags {
|
|
63
|
+
name
|
|
64
|
+
description
|
|
65
|
+
}
|
|
66
|
+
components {
|
|
67
|
+
schemas {
|
|
68
|
+
name
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
webhooks {
|
|
72
|
+
name
|
|
73
|
+
method
|
|
74
|
+
summary
|
|
75
|
+
description
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
`);
|
|
80
|
+
|
|
81
|
+
const InfoLink = ({
|
|
82
|
+
href,
|
|
83
|
+
icon,
|
|
84
|
+
children,
|
|
85
|
+
}: PropsWithChildren<{ href?: string; icon?: ReactNode }>) => (
|
|
86
|
+
<a
|
|
87
|
+
href={href}
|
|
88
|
+
className="inline-flex items-center gap-2 opacity-65 hover:opacity-100 [&_svg]:shrink-0 [&_svg]:size-3.5"
|
|
89
|
+
target="_blank"
|
|
90
|
+
rel="noopener noreferrer"
|
|
91
|
+
>
|
|
92
|
+
{icon}
|
|
93
|
+
<span className="truncate grow-0">{children}</span>
|
|
94
|
+
</a>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const InfoCardContent = ({
|
|
98
|
+
schema,
|
|
99
|
+
}: {
|
|
100
|
+
schema: SchemaInfoQueryType["schema"];
|
|
101
|
+
}) => {
|
|
102
|
+
const hasInfoLinks = !!(
|
|
103
|
+
schema.license ||
|
|
104
|
+
schema.termsOfService ||
|
|
105
|
+
schema.externalDocs
|
|
106
|
+
);
|
|
107
|
+
const hasContact = !!(
|
|
108
|
+
schema.contact?.name ||
|
|
109
|
+
schema.contact?.email ||
|
|
110
|
+
schema.contact?.url
|
|
111
|
+
);
|
|
112
|
+
const hasServers = schema.servers.length > 0;
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<CardContent className="flex flex-col gap-3 text-sm">
|
|
116
|
+
{hasInfoLinks && (
|
|
117
|
+
<div className="flex flex-col gap-1.5">
|
|
118
|
+
{schema.license && (
|
|
119
|
+
<InfoLink href={schema.license.url ?? undefined}>
|
|
120
|
+
{schema.license.name}
|
|
121
|
+
</InfoLink>
|
|
122
|
+
)}
|
|
123
|
+
{schema.termsOfService && (
|
|
124
|
+
<InfoLink href={schema.termsOfService}>Terms of Service</InfoLink>
|
|
125
|
+
)}
|
|
126
|
+
{schema.externalDocs && (
|
|
127
|
+
<InfoLink href={schema.externalDocs.url}>
|
|
128
|
+
{schema.externalDocs.description ?? "Documentation"}
|
|
129
|
+
</InfoLink>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
{hasInfoLinks && (hasContact || hasServers) && <Separator />}
|
|
134
|
+
{hasContact && (
|
|
135
|
+
<div className="flex flex-col gap-1.5">
|
|
136
|
+
<span className="text-xs text-muted-foreground font-medium uppercase tracking-wide">
|
|
137
|
+
Contact
|
|
138
|
+
</span>
|
|
139
|
+
{schema.contact?.name && <span>{schema.contact.name}</span>}
|
|
140
|
+
{schema.contact?.email && (
|
|
141
|
+
<InfoLink
|
|
142
|
+
href={`mailto:${schema.contact.email}`}
|
|
143
|
+
icon={<MailIcon />}
|
|
144
|
+
>
|
|
145
|
+
{schema.contact.email}
|
|
146
|
+
</InfoLink>
|
|
147
|
+
)}
|
|
148
|
+
{schema.contact?.url && (
|
|
149
|
+
<InfoLink href={schema.contact.url} icon={<GlobeIcon />}>
|
|
150
|
+
{schema.contact.url}
|
|
151
|
+
</InfoLink>
|
|
152
|
+
)}
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
{hasContact && hasServers && <Separator />}
|
|
156
|
+
{hasServers && (
|
|
157
|
+
<div className="flex flex-col gap-1.5">
|
|
158
|
+
<span className="text-xs text-muted-foreground font-medium uppercase tracking-wide">
|
|
159
|
+
Servers
|
|
160
|
+
</span>
|
|
161
|
+
{schema.servers.map((server) => (
|
|
162
|
+
<div key={server.url}>
|
|
163
|
+
<code className="text-xs select-all break-all">{server.url}</code>
|
|
164
|
+
{server.description && (
|
|
165
|
+
<p className="text-muted-foreground text-xs">
|
|
166
|
+
{server.description}
|
|
167
|
+
</p>
|
|
168
|
+
)}
|
|
169
|
+
</div>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
</CardContent>
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export const SchemaInfo = () => {
|
|
178
|
+
const { input, type } = useOasConfig();
|
|
179
|
+
const query = useCreateQuery(SchemaInfoQuery, { input, type });
|
|
180
|
+
const {
|
|
181
|
+
data: { schema },
|
|
182
|
+
} = useSuspenseQuery(query);
|
|
183
|
+
const { title, description } = schema;
|
|
184
|
+
|
|
185
|
+
useWarmupSchema();
|
|
186
|
+
|
|
187
|
+
const hasCardContent = !!(
|
|
188
|
+
schema.contact?.name ||
|
|
189
|
+
schema.contact?.email ||
|
|
190
|
+
schema.contact?.url ||
|
|
191
|
+
schema.servers.length > 0 ||
|
|
192
|
+
schema.license ||
|
|
193
|
+
schema.termsOfService ||
|
|
194
|
+
schema.externalDocs
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const tags = schema.tags.flatMap(({ name, description }) =>
|
|
198
|
+
name ? { name, description } : [],
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<div
|
|
203
|
+
className="pt-(--padding-content-top)"
|
|
204
|
+
data-pagefind-filter="section:openapi"
|
|
205
|
+
data-pagefind-meta="section:openapi"
|
|
206
|
+
>
|
|
207
|
+
<PagefindSearchMeta name="category">{title}</PagefindSearchMeta>
|
|
208
|
+
<Helmet>
|
|
209
|
+
{title && <title>{title}</title>}
|
|
210
|
+
{description && <meta name="description" content={description} />}
|
|
211
|
+
</Helmet>
|
|
212
|
+
|
|
213
|
+
<div className="mb-8 flex flex-col gap-4">
|
|
214
|
+
<ApiHeader heading={title} headingId="description" />
|
|
215
|
+
|
|
216
|
+
<div className="grid grid-cols-1 xl:grid-cols-[1fr_minmax(250px,380px)] gap-8">
|
|
217
|
+
{hasCardContent && (
|
|
218
|
+
<div className="xl:hidden sticky top-(--top-nav-height) lg:top-(--scroll-padding) z-10 row-start-1 col-start-1 justify-self-end self-start">
|
|
219
|
+
<Popover>
|
|
220
|
+
<PopoverTrigger asChild>
|
|
221
|
+
<Button
|
|
222
|
+
variant="outline"
|
|
223
|
+
size="icon"
|
|
224
|
+
className="shadow-sm rounded-full"
|
|
225
|
+
>
|
|
226
|
+
<InfoIcon />
|
|
227
|
+
</Button>
|
|
228
|
+
</PopoverTrigger>
|
|
229
|
+
<PopoverContent
|
|
230
|
+
align="end"
|
|
231
|
+
className="xl:hidden w-full max-w-full md:max-w-sm"
|
|
232
|
+
>
|
|
233
|
+
<InfoCardContent schema={schema} />
|
|
234
|
+
</PopoverContent>
|
|
235
|
+
</Popover>
|
|
236
|
+
</div>
|
|
237
|
+
)}
|
|
238
|
+
<div className="flex flex-col gap-6 row-start-1 col-start-1">
|
|
239
|
+
{schema.summary && (
|
|
240
|
+
<p className="text-lg text-muted-foreground">{schema.summary}</p>
|
|
241
|
+
)}
|
|
242
|
+
{schema.description && (
|
|
243
|
+
<Markdown
|
|
244
|
+
className="prose-img:max-w-prose prose-sm max-w-full lg:max-w-2xl"
|
|
245
|
+
content={schema.description}
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
248
|
+
{tags.length > 0 && (
|
|
249
|
+
<div>
|
|
250
|
+
<div className="flex items-center gap-2 text-sm uppercase tracking-wide text-muted-foreground mb-4">
|
|
251
|
+
<TagIcon size={14} />
|
|
252
|
+
Tags
|
|
253
|
+
</div>
|
|
254
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
255
|
+
{tags.map((tag) => (
|
|
256
|
+
<Item key={tag.name} variant="outline" asChild>
|
|
257
|
+
<Link to={slugify(tag.name)}>
|
|
258
|
+
<ItemContent>
|
|
259
|
+
<ItemTitle>{tag.name}</ItemTitle>
|
|
260
|
+
{tag.description && (
|
|
261
|
+
<ItemDescription asChild>
|
|
262
|
+
<Markdown
|
|
263
|
+
components={{
|
|
264
|
+
// Because the description is wrapped in a <p> and a <Link> already
|
|
265
|
+
p: ({ children }) => children,
|
|
266
|
+
a: (props) => <span {...props} />,
|
|
267
|
+
}}
|
|
268
|
+
content={tag.description}
|
|
269
|
+
className="prose-sm text-pretty"
|
|
270
|
+
/>
|
|
271
|
+
</ItemDescription>
|
|
272
|
+
)}
|
|
273
|
+
</ItemContent>
|
|
274
|
+
</Link>
|
|
275
|
+
</Item>
|
|
276
|
+
))}
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
280
|
+
{(schema.components?.schemas?.length ?? 0) > 0 && (
|
|
281
|
+
<div>
|
|
282
|
+
<div className="flex items-center gap-2 text-sm uppercase tracking-wide text-muted-foreground mb-4">
|
|
283
|
+
<BracesIcon size={14} />
|
|
284
|
+
Schemas
|
|
285
|
+
</div>
|
|
286
|
+
<div className="grid grid-cols-[repeat(auto-fill,minmax(200px,1fr))] gap-4">
|
|
287
|
+
{schema.components?.schemas?.map((s) => (
|
|
288
|
+
<Item key={s.name} variant="outline" title={s.name} asChild>
|
|
289
|
+
<Link to={`~schemas#${slugify(s.name)}`}>
|
|
290
|
+
<span className="text-sm font-medium leading-snug truncate">
|
|
291
|
+
{s.name}
|
|
292
|
+
</span>
|
|
293
|
+
</Link>
|
|
294
|
+
</Item>
|
|
295
|
+
))}
|
|
296
|
+
</div>
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
{schema.webhooks.length > 0 && (
|
|
300
|
+
<div>
|
|
301
|
+
<div className="flex items-center gap-2 text-sm uppercase tracking-wide text-muted-foreground mb-4">
|
|
302
|
+
<WebhookIcon size={14} />
|
|
303
|
+
Webhooks
|
|
304
|
+
</div>
|
|
305
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
306
|
+
{schema.webhooks.map((webhook) => (
|
|
307
|
+
<Item
|
|
308
|
+
key={`${webhook.name}-${webhook.method}`}
|
|
309
|
+
variant="outline"
|
|
310
|
+
>
|
|
311
|
+
<ItemContent>
|
|
312
|
+
<ItemTitle>{webhook.name}</ItemTitle>
|
|
313
|
+
{(webhook.summary || webhook.description) && (
|
|
314
|
+
<ItemDescription>
|
|
315
|
+
{webhook.summary ?? webhook.description}
|
|
316
|
+
</ItemDescription>
|
|
317
|
+
)}
|
|
318
|
+
</ItemContent>
|
|
319
|
+
<ItemActions>
|
|
320
|
+
<Badge
|
|
321
|
+
variant="muted"
|
|
322
|
+
className="text-[10px] font-mono"
|
|
323
|
+
>
|
|
324
|
+
{webhook.method}
|
|
325
|
+
</Badge>
|
|
326
|
+
</ItemActions>
|
|
327
|
+
</Item>
|
|
328
|
+
))}
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
)}
|
|
332
|
+
</div>
|
|
333
|
+
{hasCardContent && (
|
|
334
|
+
<div className="hidden xl:block">
|
|
335
|
+
<Card className="sticky top-(--scroll-padding)">
|
|
336
|
+
<InfoCardContent schema={schema} />
|
|
337
|
+
</Card>
|
|
338
|
+
</div>
|
|
339
|
+
)}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
);
|
|
344
|
+
};
|