@gitbook/react-openapi 1.5.1 → 1.5.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/CHANGELOG.md +27 -0
- package/dist/InteractiveSection.js +59 -0
- package/dist/Markdown.js +10 -0
- package/dist/OpenAPICodeSample.js +219 -0
- package/dist/OpenAPICodeSampleInteractive.js +62 -0
- package/dist/OpenAPICodeSampleSelector.js +45 -0
- package/dist/OpenAPICopyButton.js +44 -0
- package/dist/OpenAPIDisclosure.js +28 -0
- package/dist/OpenAPIDisclosureGroup.js +89 -0
- package/dist/OpenAPIExample.js +41 -0
- package/dist/OpenAPIMediaType.js +58 -0
- package/dist/OpenAPIOperation.d.ts +15 -0
- package/dist/OpenAPIOperation.js +30 -0
- package/dist/OpenAPIOperationContext.d.ts +20 -0
- package/dist/OpenAPIOperationContext.js +30 -0
- package/dist/OpenAPIPath.js +25 -0
- package/dist/OpenAPIPathItem.js +22 -0
- package/dist/OpenAPIPathMultipleServers.js +43 -0
- package/dist/OpenAPIPrefillContextProvider.d.ts +26 -0
- package/dist/OpenAPIPrefillContextProvider.js +25 -0
- package/dist/OpenAPIRequestBody.js +28 -0
- package/dist/OpenAPIRequestBodyHeaderType.js +23 -0
- package/dist/OpenAPIRequiredScopes.js +67 -0
- package/dist/OpenAPIResponse.js +39 -0
- package/dist/OpenAPIResponseExample.js +75 -0
- package/dist/OpenAPIResponseExampleContent.js +61 -0
- package/dist/OpenAPIResponses.js +61 -0
- package/dist/OpenAPISchema.js +373 -0
- package/dist/OpenAPISchemaName.js +45 -0
- package/dist/OpenAPISchemaServer.js +13 -0
- package/dist/OpenAPISecurities.js +98 -0
- package/dist/OpenAPISelect.js +45 -0
- package/dist/OpenAPISpec.js +73 -0
- package/dist/OpenAPITooltip.js +23 -0
- package/dist/OpenAPIWebhook.d.ts +15 -0
- package/dist/OpenAPIWebhook.js +28 -0
- package/dist/OpenAPIWebhookExample.js +40 -0
- package/dist/ScalarApiButton.js +90 -0
- package/dist/StaticSection.js +37 -0
- package/dist/code-samples.js +305 -0
- package/dist/common/OpenAPIColumnSpec.js +23 -0
- package/dist/common/OpenAPIOperationDescription.js +18 -0
- package/dist/common/OpenAPIStability.js +17 -0
- package/dist/common/OpenAPISummary.js +27 -0
- package/dist/contentTypeChecks.js +34 -0
- package/dist/context.d.ts +74 -0
- package/dist/context.js +29 -0
- package/dist/decycle.js +41 -0
- package/dist/dereference.js +24 -0
- package/dist/formatPath.js +25 -0
- package/dist/generateSchemaExample.js +198 -0
- package/dist/getDisclosureLabel.js +17 -0
- package/dist/getOrCreateDisclosureStoreByKey.js +31 -0
- package/dist/getOrCreateStoreByKey.js +22 -0
- package/dist/index.d.ts +11 -662
- package/dist/index.js +9 -3871
- package/dist/json2xml.js +12 -0
- package/dist/resolveOpenAPIOperation.d.ts +15 -0
- package/dist/resolveOpenAPIOperation.js +105 -0
- package/dist/resolveOpenAPIWebhook.d.ts +15 -0
- package/dist/resolveOpenAPIWebhook.js +52 -0
- package/dist/schemas/OpenAPISchemaItem.js +26 -0
- package/dist/schemas/OpenAPISchemas.d.ts +19 -0
- package/dist/schemas/OpenAPISchemas.js +57 -0
- package/dist/schemas/resolveOpenAPISchemas.d.ts +15 -0
- package/dist/schemas/resolveOpenAPISchemas.js +17 -0
- package/dist/stringifyOpenAPI.js +14 -0
- package/dist/translate.js +43 -0
- package/dist/translations/de.js +50 -0
- package/dist/translations/en.d.ts +49 -0
- package/dist/translations/en.js +50 -0
- package/dist/translations/es.js +50 -0
- package/dist/translations/fr.js +50 -0
- package/dist/translations/index.d.ts +426 -0
- package/dist/translations/index.js +31 -0
- package/dist/translations/ja.js +50 -0
- package/dist/translations/nl.js +50 -0
- package/dist/translations/no.js +50 -0
- package/dist/translations/pt-br.js +50 -0
- package/dist/translations/types.d.ts +7 -0
- package/dist/translations/zh.js +50 -0
- package/dist/types.d.ts +38 -0
- package/dist/util/example.js +84 -0
- package/dist/util/server.js +38 -0
- package/dist/util/tryit-prefill.js +143 -0
- package/dist/utils.js +163 -0
- package/package.json +28 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @gitbook/react-openapi
|
|
2
2
|
|
|
3
|
+
## 1.5.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b4a021a: Add heredoc support for cURL JSON body
|
|
8
|
+
- a512c90: Re-arrange OpenAPI Scopes for OAuth2
|
|
9
|
+
- df1966d: Bump Scalar
|
|
10
|
+
- b45feaf: Disable OpenAPI "Try it" when no servers are defined
|
|
11
|
+
- 10995e0: Use NPM Trusted publishing for publishing the package.
|
|
12
|
+
- f9f8011: Add alt text support to card covers
|
|
13
|
+
- 8ce7322: Add OpenAPI servers selection
|
|
14
|
+
- 2c3066e: Improve OAuth2 scopes handling in OpenAPI
|
|
15
|
+
- Updated dependencies [df1966d]
|
|
16
|
+
- Updated dependencies [10995e0]
|
|
17
|
+
- Updated dependencies [10995e0]
|
|
18
|
+
- @gitbook/openapi-parser@3.0.5
|
|
19
|
+
- @gitbook/expr@1.2.4
|
|
20
|
+
|
|
21
|
+
## 1.5.2
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 6142d6b: Mark as sideEffects, fix all package bundles
|
|
26
|
+
- Updated dependencies [6142d6b]
|
|
27
|
+
- @gitbook/openapi-parser@3.0.4
|
|
28
|
+
- @gitbook/expr@1.2.3
|
|
29
|
+
|
|
3
30
|
## 1.5.1
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { Section, SectionBody, SectionHeader, SectionHeaderContent } from "./StaticSection.js";
|
|
5
|
+
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
|
|
6
|
+
import clsx from "classnames";
|
|
7
|
+
import { useRef } from "react";
|
|
8
|
+
import { mergeProps, useButton, useDisclosure, useFocusRing } from "react-aria";
|
|
9
|
+
import { useDisclosureState } from "react-stately";
|
|
10
|
+
|
|
11
|
+
//#region src/InteractiveSection.tsx
|
|
12
|
+
/**
|
|
13
|
+
* To optimize rendering, most of the components are server-components,
|
|
14
|
+
* and the interactiveness is mainly handled by a few key components like this one.
|
|
15
|
+
*/
|
|
16
|
+
function InteractiveSection(props) {
|
|
17
|
+
const { id, className, toggeable = false, defaultOpened = true, tabs = [], defaultTab = tabs[0]?.key, header, overlay, toggleIcon = "▶", selectIcon, stateKey = "interactive-section" } = props;
|
|
18
|
+
const state = useDisclosureState({ defaultExpanded: defaultOpened });
|
|
19
|
+
const panelRef = useRef(null);
|
|
20
|
+
const triggerRef = useRef(null);
|
|
21
|
+
const { buttonProps: triggerProps, panelProps } = useDisclosure({}, state, panelRef);
|
|
22
|
+
const { buttonProps } = useButton(triggerProps, triggerRef);
|
|
23
|
+
const { isFocusVisible, focusProps } = useFocusRing();
|
|
24
|
+
const store = useSelectState(stateKey, defaultTab);
|
|
25
|
+
const selectedTab = tabs.find((tab) => tab.key === store.key) ?? tabs[0];
|
|
26
|
+
return <Section id={id} className={clsx("openapi-section", toggeable ? "openapi-section-toggeable" : null, className, toggeable ? `${className}-${state.isExpanded ? "opened" : "closed"}` : null)}>
|
|
27
|
+
{header ? <SectionHeader onClick={() => {
|
|
28
|
+
if (toggeable) state.toggle();
|
|
29
|
+
}} className={className}>
|
|
30
|
+
<SectionHeaderContent className={className}>
|
|
31
|
+
{selectedTab?.body && toggeable ? <button {...mergeProps(buttonProps, focusProps)} ref={triggerRef} className={clsx("openapi-section-toggle", `${className}-toggle`)} style={{ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500) / 0.4)" : "none" }}>
|
|
32
|
+
{toggleIcon}
|
|
33
|
+
</button> : null}
|
|
34
|
+
{header}
|
|
35
|
+
</SectionHeaderContent>
|
|
36
|
+
{}
|
|
37
|
+
<div className={clsx("openapi-section-header-controls", `${className}-header-controls`)} onClick={(event) => {
|
|
38
|
+
event.stopPropagation();
|
|
39
|
+
}}>
|
|
40
|
+
{tabs.length > 0 ? <OpenAPISelect stateKey={stateKey} items={tabs} onChange={() => {
|
|
41
|
+
state.expand();
|
|
42
|
+
}} icon={selectIcon} placement="bottom end">
|
|
43
|
+
{tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
|
|
44
|
+
{tab.label}
|
|
45
|
+
</OpenAPISelectItem>)}
|
|
46
|
+
</OpenAPISelect> : null}
|
|
47
|
+
</div>
|
|
48
|
+
</SectionHeader> : null}
|
|
49
|
+
{(!toggeable || state.isExpanded) && selectedTab?.body ? <SectionBody ref={panelRef} {...panelProps} className={className}>
|
|
50
|
+
{selectedTab?.body}
|
|
51
|
+
</SectionBody> : null}
|
|
52
|
+
{overlay ? <div className={clsx("openapi-section-overlay", `${className}-overlay`)}>
|
|
53
|
+
{overlay}
|
|
54
|
+
</div> : null}
|
|
55
|
+
</Section>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
export { InteractiveSection };
|
package/dist/Markdown.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import clsx from "classnames";
|
|
2
|
+
|
|
3
|
+
//#region src/Markdown.tsx
|
|
4
|
+
function Markdown(props) {
|
|
5
|
+
const { source, className } = props;
|
|
6
|
+
return <div className={clsx("openapi-markdown", className)} dangerouslySetInnerHTML={{ __html: source }} />;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
export { Markdown };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { stringifyOpenAPI } from "./stringifyOpenAPI.js";
|
|
2
|
+
import { checkIsReference, extractOperationSecurityInfo } from "./utils.js";
|
|
3
|
+
import { getOpenAPIClientContext } from "./context.js";
|
|
4
|
+
import { generateMediaTypeExamples, generateSchemaExample } from "./generateSchemaExample.js";
|
|
5
|
+
import { OpenAPIMediaTypeExamplesBody, OpenAPIMediaTypeExamplesSelector } from "./OpenAPICodeSampleInteractive.js";
|
|
6
|
+
import { getDefaultServerURL } from "./util/server.js";
|
|
7
|
+
import { OpenAPICodeSampleBody } from "./OpenAPICodeSampleSelector.js";
|
|
8
|
+
import { resolvePrefillCodePlaceholderFromSecurityScheme, resolveURLWithPrefillCodePlaceholdersFromServer } from "./util/tryit-prefill.js";
|
|
9
|
+
import { ScalarApiButton } from "./ScalarApiButton.js";
|
|
10
|
+
import { codeSampleGenerators, parseHostAndPath } from "./code-samples.js";
|
|
11
|
+
|
|
12
|
+
//#region src/OpenAPICodeSample.tsx
|
|
13
|
+
const CUSTOM_CODE_SAMPLES_KEYS = [
|
|
14
|
+
"x-custom-examples",
|
|
15
|
+
"x-code-samples",
|
|
16
|
+
"x-codeSamples"
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Display code samples to execute the operation.
|
|
20
|
+
* It supports the Redocly custom syntax as well (https://redocly.com/docs/api-reference-docs/specification-extensions/x-code-samples/)
|
|
21
|
+
*/
|
|
22
|
+
function OpenAPICodeSample(props) {
|
|
23
|
+
const { data, context } = props;
|
|
24
|
+
if (data.operation["x-codeSamples"] === false) return null;
|
|
25
|
+
const customCodeSamples = getCustomCodeSamples(props);
|
|
26
|
+
if (data["x-codeSamples"] === false && !customCodeSamples) return null;
|
|
27
|
+
const samples = customCodeSamples ?? generateCodeSamples(props);
|
|
28
|
+
if (samples.length === 0) return null;
|
|
29
|
+
return <OpenAPICodeSampleBody context={getOpenAPIClientContext(context)} data={data} items={samples} selectIcon={context.icons.chevronDown} />;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate code samples for the operation.
|
|
33
|
+
*/
|
|
34
|
+
function generateCodeSamples(props) {
|
|
35
|
+
const { data, context } = props;
|
|
36
|
+
const searchParams = new URLSearchParams();
|
|
37
|
+
const headersObject = {};
|
|
38
|
+
(Array.isArray(data.operation.parameters) ? data.operation.parameters : []).forEach((param) => {
|
|
39
|
+
if (!param) return;
|
|
40
|
+
if (param.in === "header" && param.required) {
|
|
41
|
+
const example = param.schema ? generateSchemaExample(param.schema, { mode: "write" }) : void 0;
|
|
42
|
+
if (example !== void 0 && param.name) headersObject[param.name] = typeof example !== "string" ? stringifyOpenAPI(example) : example;
|
|
43
|
+
} else if (param.in === "query" && param.required) {
|
|
44
|
+
const example = param.schema ? generateSchemaExample(param.schema, { mode: "write" }) : void 0;
|
|
45
|
+
if (example !== void 0 && param.name) searchParams.append(param.name, String(Array.isArray(example) ? example[0] : example));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
const requestBody = !checkIsReference(data.operation.requestBody) ? data.operation.requestBody : void 0;
|
|
49
|
+
const defaultServerUrl = getDefaultServerURL(data.servers);
|
|
50
|
+
let serverUrlPath = defaultServerUrl ? parseHostAndPath(defaultServerUrl).path : "";
|
|
51
|
+
serverUrlPath = serverUrlPath === "/" ? "" : serverUrlPath;
|
|
52
|
+
const serverUrlOrigin = (data.servers[0] ? resolveURLWithPrefillCodePlaceholdersFromServer(data.servers[0], defaultServerUrl) : defaultServerUrl).replaceAll(serverUrlPath, "");
|
|
53
|
+
const path = serverUrlPath + data.path + (searchParams.size ? `?${searchParams.toString()}` : "");
|
|
54
|
+
const genericHeaders = {
|
|
55
|
+
...getSecurityHeaders({
|
|
56
|
+
securityRequirement: data.operation.security,
|
|
57
|
+
securities: data.securities
|
|
58
|
+
}),
|
|
59
|
+
...headersObject
|
|
60
|
+
};
|
|
61
|
+
const mediaTypeRendererFactories = Object.entries(requestBody?.content ?? {}).map(([mediaType, mediaTypeObject]) => {
|
|
62
|
+
return (generator) => {
|
|
63
|
+
const mediaTypeHeaders = {
|
|
64
|
+
...genericHeaders,
|
|
65
|
+
"Content-Type": mediaType
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
mediaType,
|
|
69
|
+
element: context.renderCodeBlock({
|
|
70
|
+
code: generator.generate({
|
|
71
|
+
url: {
|
|
72
|
+
origin: serverUrlOrigin,
|
|
73
|
+
path
|
|
74
|
+
},
|
|
75
|
+
method: data.method,
|
|
76
|
+
body: void 0,
|
|
77
|
+
headers: mediaTypeHeaders
|
|
78
|
+
}),
|
|
79
|
+
syntax: generator.syntax
|
|
80
|
+
}),
|
|
81
|
+
examples: generateMediaTypeExamples(mediaTypeObject, { mode: "write" }).map((example) => ({
|
|
82
|
+
example,
|
|
83
|
+
element: context.renderCodeBlock({
|
|
84
|
+
code: generator.generate({
|
|
85
|
+
url: {
|
|
86
|
+
origin: serverUrlOrigin,
|
|
87
|
+
path
|
|
88
|
+
},
|
|
89
|
+
method: data.method,
|
|
90
|
+
body: example.value,
|
|
91
|
+
headers: mediaTypeHeaders
|
|
92
|
+
}),
|
|
93
|
+
syntax: generator.syntax
|
|
94
|
+
})
|
|
95
|
+
}))
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
return codeSampleGenerators.map((generator) => {
|
|
100
|
+
if (mediaTypeRendererFactories.length > 0) {
|
|
101
|
+
const renderers = mediaTypeRendererFactories.map((generate) => generate(generator));
|
|
102
|
+
return {
|
|
103
|
+
key: `default-${generator.id}`,
|
|
104
|
+
label: generator.label,
|
|
105
|
+
body: <OpenAPIMediaTypeExamplesBody method={data.method} path={data.path} renderers={renderers} blockKey={context.blockKey} />,
|
|
106
|
+
footer: <OpenAPICodeSampleFooter renderers={renderers} data={data} context={context} />
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
key: `default-${generator.id}`,
|
|
111
|
+
label: generator.label,
|
|
112
|
+
body: context.renderCodeBlock({
|
|
113
|
+
code: generator.generate({
|
|
114
|
+
url: {
|
|
115
|
+
origin: serverUrlOrigin,
|
|
116
|
+
path
|
|
117
|
+
},
|
|
118
|
+
method: data.method,
|
|
119
|
+
body: void 0,
|
|
120
|
+
headers: genericHeaders
|
|
121
|
+
}),
|
|
122
|
+
syntax: generator.syntax
|
|
123
|
+
}),
|
|
124
|
+
footer: <OpenAPICodeSampleFooter data={data} renderers={[]} context={context} />
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
function OpenAPICodeSampleFooter(props) {
|
|
129
|
+
const { data, context, renderers } = props;
|
|
130
|
+
const { method, path, securities, servers } = data;
|
|
131
|
+
const { specUrl } = context;
|
|
132
|
+
const hideTryItPanel = data["x-hideTryItPanel"] || data.operation["x-hideTryItPanel"];
|
|
133
|
+
const hasMultipleMediaTypes = renderers.length > 1 || renderers.some((renderer) => renderer.examples.length > 0);
|
|
134
|
+
if (hideTryItPanel && !hasMultipleMediaTypes) return null;
|
|
135
|
+
if (!validateHttpMethod(method) || !hasMultipleMediaTypes && servers.length === 0) return null;
|
|
136
|
+
return <div className="openapi-codesample-footer">
|
|
137
|
+
{hasMultipleMediaTypes ? <OpenAPIMediaTypeExamplesSelector method={data.method} path={data.path} renderers={renderers} selectIcon={context.icons.chevronDown} blockKey={context.blockKey} /> : <span />}
|
|
138
|
+
{!hideTryItPanel && servers.length > 0 && <ScalarApiButton context={getOpenAPIClientContext(context)} method={method} path={path} securities={securities} servers={servers} specUrl={specUrl} />}
|
|
139
|
+
</div>;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get custom code samples for the operation.
|
|
143
|
+
*/
|
|
144
|
+
function getCustomCodeSamples(props) {
|
|
145
|
+
const { data, context } = props;
|
|
146
|
+
let customCodeSamples = null;
|
|
147
|
+
CUSTOM_CODE_SAMPLES_KEYS.forEach((key) => {
|
|
148
|
+
const customSamples = data.operation[key];
|
|
149
|
+
if (customSamples && Array.isArray(customSamples)) customCodeSamples = customSamples.filter((sample) => {
|
|
150
|
+
return typeof sample.source === "string" && typeof sample.lang === "string";
|
|
151
|
+
}).map((sample, index) => ({
|
|
152
|
+
key: `custom-sample-${sample.lang}-${index}`,
|
|
153
|
+
label: sample.label || sample.lang,
|
|
154
|
+
body: context.renderCodeBlock({
|
|
155
|
+
code: sample.source,
|
|
156
|
+
syntax: sample.lang
|
|
157
|
+
}),
|
|
158
|
+
footer: <OpenAPICodeSampleFooter renderers={[]} data={data} context={context} />
|
|
159
|
+
}));
|
|
160
|
+
});
|
|
161
|
+
return customCodeSamples;
|
|
162
|
+
}
|
|
163
|
+
function getSecurityHeaders(args) {
|
|
164
|
+
const { securityRequirement, securities } = args;
|
|
165
|
+
const operationSecurityInfo = extractOperationSecurityInfo({
|
|
166
|
+
securityRequirement,
|
|
167
|
+
securities
|
|
168
|
+
});
|
|
169
|
+
if (operationSecurityInfo.length === 0) return {};
|
|
170
|
+
const selectedSecurity = operationSecurityInfo.at(0);
|
|
171
|
+
if (!selectedSecurity) return {};
|
|
172
|
+
const headers = {};
|
|
173
|
+
for (const security of selectedSecurity.schemes) switch (security.type) {
|
|
174
|
+
case "http": {
|
|
175
|
+
let scheme = security.scheme;
|
|
176
|
+
const format = resolvePrefillCodePlaceholderFromSecurityScheme({
|
|
177
|
+
security,
|
|
178
|
+
defaultPlaceholderValue: scheme?.includes("basic") ? "username:password" : "YOUR_SECRET_TOKEN"
|
|
179
|
+
});
|
|
180
|
+
if (scheme?.includes("bearer")) scheme = "Bearer";
|
|
181
|
+
else if (scheme?.includes("basic")) scheme = "Basic";
|
|
182
|
+
else if (scheme?.includes("token")) scheme = "Token";
|
|
183
|
+
headers.Authorization = `${scheme} ${format}`;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "apiKey": {
|
|
187
|
+
if (security.in !== "header") break;
|
|
188
|
+
const name = security.name ?? "Authorization";
|
|
189
|
+
headers[name] = resolvePrefillCodePlaceholderFromSecurityScheme({
|
|
190
|
+
security,
|
|
191
|
+
defaultPlaceholderValue: "YOUR_API_KEY"
|
|
192
|
+
});
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
case "oauth2":
|
|
196
|
+
headers.Authorization = `Bearer ${resolvePrefillCodePlaceholderFromSecurityScheme({
|
|
197
|
+
security,
|
|
198
|
+
defaultPlaceholderValue: "YOUR_OAUTH2_TOKEN"
|
|
199
|
+
})}`;
|
|
200
|
+
break;
|
|
201
|
+
default: break;
|
|
202
|
+
}
|
|
203
|
+
return headers;
|
|
204
|
+
}
|
|
205
|
+
function validateHttpMethod(method) {
|
|
206
|
+
return [
|
|
207
|
+
"get",
|
|
208
|
+
"post",
|
|
209
|
+
"put",
|
|
210
|
+
"delete",
|
|
211
|
+
"patch",
|
|
212
|
+
"head",
|
|
213
|
+
"options",
|
|
214
|
+
"trace"
|
|
215
|
+
].includes(method);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
//#endregion
|
|
219
|
+
export { OpenAPICodeSample };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { createStateKey } from "./utils.js";
|
|
5
|
+
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
|
|
6
|
+
|
|
7
|
+
//#region src/OpenAPICodeSampleInteractive.tsx
|
|
8
|
+
function OpenAPIMediaTypeExamplesSelector(props) {
|
|
9
|
+
const { method, path, renderers, selectIcon, blockKey } = props;
|
|
10
|
+
if (!renderers[0]) throw new Error("No renderers provided");
|
|
11
|
+
const stateKey = createStateKey("request-body-media-type", blockKey);
|
|
12
|
+
const state = useSelectState(stateKey, renderers[0].mediaType);
|
|
13
|
+
const selected = renderers.find((r) => r.mediaType === state.key) || renderers[0];
|
|
14
|
+
return <div className="openapi-codesample-selectors">
|
|
15
|
+
<MediaTypeSelector selectIcon={selectIcon} stateKey={stateKey} renderers={renderers} />
|
|
16
|
+
<ExamplesSelector selectIcon={selectIcon} method={method} path={path} renderer={selected} />
|
|
17
|
+
</div>;
|
|
18
|
+
}
|
|
19
|
+
function MediaTypeSelector(props) {
|
|
20
|
+
const { renderers, stateKey, selectIcon } = props;
|
|
21
|
+
if (renderers.length < 2) return null;
|
|
22
|
+
const items = renderers.map((renderer) => ({
|
|
23
|
+
key: renderer.mediaType,
|
|
24
|
+
label: renderer.mediaType
|
|
25
|
+
}));
|
|
26
|
+
return <OpenAPISelect items={items} icon={selectIcon} stateKey={stateKey} placement="bottom start">
|
|
27
|
+
{items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
|
|
28
|
+
{item.label}
|
|
29
|
+
</OpenAPISelectItem>)}
|
|
30
|
+
</OpenAPISelect>;
|
|
31
|
+
}
|
|
32
|
+
function ExamplesSelector(props) {
|
|
33
|
+
const { method, path, renderer, selectIcon } = props;
|
|
34
|
+
if (renderer.examples.length < 2) return null;
|
|
35
|
+
const items = renderer.examples.map((example, index) => ({
|
|
36
|
+
key: index,
|
|
37
|
+
label: example.example.summary || `Example ${index + 1}`
|
|
38
|
+
}));
|
|
39
|
+
return <OpenAPISelect items={items} icon={selectIcon} stateKey={`media-type-sample-${renderer.mediaType}-${method}-${path}`} placement="bottom start">
|
|
40
|
+
{items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
|
|
41
|
+
{item.label}
|
|
42
|
+
</OpenAPISelectItem>)}
|
|
43
|
+
</OpenAPISelect>;
|
|
44
|
+
}
|
|
45
|
+
function OpenAPIMediaTypeExamplesBody(props) {
|
|
46
|
+
const { renderers, method, path, blockKey } = props;
|
|
47
|
+
if (!renderers[0]) throw new Error("No renderers provided");
|
|
48
|
+
const mediaTypeState = useSelectState(createStateKey("request-body-media-type", blockKey), renderers[0].mediaType);
|
|
49
|
+
const selected = renderers.find((r) => r.mediaType === mediaTypeState.key) ?? renderers[0];
|
|
50
|
+
if (selected.examples.length === 0) return selected.element;
|
|
51
|
+
return <ExamplesBody method={method} path={path} renderer={selected} />;
|
|
52
|
+
}
|
|
53
|
+
function ExamplesBody(props) {
|
|
54
|
+
const { method, path, renderer } = props;
|
|
55
|
+
const exampleState = useSelectState(`media-type-sample-${renderer.mediaType}-${method}-${path}`, renderer.mediaType);
|
|
56
|
+
const example = renderer.examples[Number(exampleState.key)] ?? renderer.examples[0];
|
|
57
|
+
if (!example) throw new Error(`No example found for key ${exampleState.key}`);
|
|
58
|
+
return example.element;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
//#endregion
|
|
62
|
+
export { OpenAPIMediaTypeExamplesBody, OpenAPIMediaTypeExamplesSelector };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { StaticSection } from "./StaticSection.js";
|
|
5
|
+
import { getOrCreateStoreByKey } from "./getOrCreateStoreByKey.js";
|
|
6
|
+
import { OpenAPISelect, OpenAPISelectItem } from "./OpenAPISelect.js";
|
|
7
|
+
import { OpenAPIPath } from "./OpenAPIPath.js";
|
|
8
|
+
import { useCallback } from "react";
|
|
9
|
+
import { useStore } from "zustand";
|
|
10
|
+
|
|
11
|
+
//#region src/OpenAPICodeSampleSelector.tsx
|
|
12
|
+
function useCodeSampleState(initialKey = "default") {
|
|
13
|
+
const store = useStore(getOrCreateStoreByKey("codesample", initialKey));
|
|
14
|
+
return {
|
|
15
|
+
key: store.key,
|
|
16
|
+
setKey: useCallback((key) => store.setKey(key), [store.setKey])
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function OpenAPICodeSampleHeader(props) {
|
|
20
|
+
const { data, items, selectIcon, context } = props;
|
|
21
|
+
return <>
|
|
22
|
+
<OpenAPIPath context={context} canCopy={false} withServer={false} data={data} />
|
|
23
|
+
{items.length > 1 ? <OpenAPISelect icon={selectIcon} items={items} stateKey="codesample" placement="bottom end">
|
|
24
|
+
{items.map((item) => <OpenAPISelectItem key={item.key} id={item.key} value={item}>
|
|
25
|
+
{item.label}
|
|
26
|
+
</OpenAPISelectItem>)}
|
|
27
|
+
</OpenAPISelect> : items[0] ? <span className="openapi-codesample-label">{items[0].label}</span> : null}
|
|
28
|
+
</>;
|
|
29
|
+
}
|
|
30
|
+
function OpenAPICodeSampleBody(props) {
|
|
31
|
+
const { items, data, selectIcon, context } = props;
|
|
32
|
+
if (!items[0]) throw new Error("No items provided");
|
|
33
|
+
const state = useCodeSampleState(items[0]?.key);
|
|
34
|
+
const selected = items.find((item) => item.key === state.key) || items[0];
|
|
35
|
+
if (!selected) return null;
|
|
36
|
+
return <StaticSection header={<OpenAPICodeSampleHeader context={context} selectIcon={selectIcon} data={data} items={items} />} className="openapi-codesample">
|
|
37
|
+
<div id={selected.key} className="openapi-codesample-panel">
|
|
38
|
+
{selected.body ? selected.body : null}
|
|
39
|
+
{selected.footer ? selected.footer : null}
|
|
40
|
+
</div>
|
|
41
|
+
</StaticSection>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { OpenAPICodeSampleBody };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { t } from "./translate.js";
|
|
5
|
+
import { OpenAPITooltip } from "./OpenAPITooltip.js";
|
|
6
|
+
import clsx from "classnames";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import { Button } from "react-aria-components";
|
|
9
|
+
|
|
10
|
+
//#region src/OpenAPICopyButton.tsx
|
|
11
|
+
function OpenAPICopyButton(props) {
|
|
12
|
+
const { value, label, children, onPress, className, context, withTooltip = true } = props;
|
|
13
|
+
const [copied, setCopied] = useState(false);
|
|
14
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
15
|
+
const handleCopy = () => {
|
|
16
|
+
if (!value) return;
|
|
17
|
+
navigator.clipboard.writeText(value).then(() => {
|
|
18
|
+
setIsOpen(true);
|
|
19
|
+
setCopied(true);
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
setCopied(false);
|
|
22
|
+
setIsOpen(false);
|
|
23
|
+
}, 2e3);
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
return <OpenAPITooltip isDisabled={!withTooltip} isOpen={isOpen} onOpenChange={setIsOpen}>
|
|
27
|
+
<Button type="button" preventFocusOnPress onPress={(e) => {
|
|
28
|
+
handleCopy();
|
|
29
|
+
onPress?.(e);
|
|
30
|
+
}} className={clsx("openapi-copy-button", className)} {...props}>
|
|
31
|
+
{children}
|
|
32
|
+
</Button>
|
|
33
|
+
|
|
34
|
+
<OpenAPITooltip.Content isOpen={isOpen} onOpenChange={setIsOpen}>
|
|
35
|
+
{copied ? <>
|
|
36
|
+
{context.icons.check}
|
|
37
|
+
{t(context.translation, "copied")}
|
|
38
|
+
</> : label || t(context.translation, "copy_to_clipboard")}
|
|
39
|
+
</OpenAPITooltip.Content>
|
|
40
|
+
</OpenAPITooltip>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { OpenAPICopyButton };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import clsx from "classnames";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { Button, Disclosure, DisclosurePanel } from "react-aria-components";
|
|
7
|
+
|
|
8
|
+
//#region src/OpenAPIDisclosure.tsx
|
|
9
|
+
/**
|
|
10
|
+
* Display an interactive OpenAPI disclosure.
|
|
11
|
+
*/
|
|
12
|
+
function OpenAPIDisclosure(props) {
|
|
13
|
+
const { icon, header, label, children, className, defaultExpanded = false } = props;
|
|
14
|
+
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
|
|
15
|
+
return <Disclosure className={clsx("openapi-disclosure", className)} isExpanded={isExpanded} onExpandedChange={setIsExpanded}>
|
|
16
|
+
<Button slot="trigger" className="openapi-disclosure-trigger" style={({ isFocusVisible }) => ({ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500) / 0.4)" : "none" })}>
|
|
17
|
+
{header}
|
|
18
|
+
<div className="openapi-disclosure-trigger-label">
|
|
19
|
+
{label ? <span>{typeof label === "function" ? label(isExpanded) : label}</span> : null}
|
|
20
|
+
{icon}
|
|
21
|
+
</div>
|
|
22
|
+
</Button>
|
|
23
|
+
{isExpanded ? <DisclosurePanel className="openapi-disclosure-panel">{children}</DisclosurePanel> : null}
|
|
24
|
+
</Disclosure>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
export { OpenAPIDisclosure };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import { OpenAPISelect, OpenAPISelectItem, useSelectState } from "./OpenAPISelect.js";
|
|
5
|
+
import { getOrCreateDisclosureStoreByKey } from "./getOrCreateDisclosureStoreByKey.js";
|
|
6
|
+
import clsx from "classnames";
|
|
7
|
+
import { createContext, useContext, useRef } from "react";
|
|
8
|
+
import { useStore } from "zustand";
|
|
9
|
+
import { mergeProps, useButton, useDisclosure, useFocusRing, useId as useId$1 } from "react-aria";
|
|
10
|
+
import { useDisclosureGroupState, useDisclosureState } from "react-stately";
|
|
11
|
+
|
|
12
|
+
//#region src/OpenAPIDisclosureGroup.tsx
|
|
13
|
+
const DisclosureGroupStateContext = createContext(null);
|
|
14
|
+
function useDisclosureGroupStore(stateKey = "disclosure-group", initialKeys) {
|
|
15
|
+
return useStore(getOrCreateDisclosureStoreByKey(stateKey, initialKeys));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Display an interactive OpenAPI disclosure group.
|
|
19
|
+
*/
|
|
20
|
+
function OpenAPIDisclosureGroup(props) {
|
|
21
|
+
const { icon, groups, selectStateKey, stateKey, selectIcon, className, expandedKeys, defaultExpandedKeys, onExpandedChange } = props;
|
|
22
|
+
const { expandedKeys: storeExpandedKeys, setExpandedKeys } = useDisclosureGroupStore(stateKey, expandedKeys || defaultExpandedKeys ? new Set(expandedKeys || defaultExpandedKeys) : void 0);
|
|
23
|
+
const state = useDisclosureGroupState({
|
|
24
|
+
...props,
|
|
25
|
+
expandedKeys: storeExpandedKeys,
|
|
26
|
+
onExpandedChange: (keys) => {
|
|
27
|
+
setExpandedKeys(keys);
|
|
28
|
+
onExpandedChange?.(keys);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
return <DisclosureGroupStateContext.Provider value={state}>
|
|
32
|
+
{groups.map((group) => <DisclosureItem className={className} selectStateKey={selectStateKey} selectIcon={selectIcon} icon={icon} key={group.key} group={group} />)}
|
|
33
|
+
</DisclosureGroupStateContext.Provider>;
|
|
34
|
+
}
|
|
35
|
+
function DisclosureItem(props) {
|
|
36
|
+
const { icon, group, selectStateKey, selectIcon, className } = props;
|
|
37
|
+
const defaultId = useId$1();
|
|
38
|
+
const id = group.key || defaultId;
|
|
39
|
+
const groupState = useContext(DisclosureGroupStateContext);
|
|
40
|
+
const isExpanded = groupState?.expandedKeys.has(id) || false;
|
|
41
|
+
const state = useDisclosureState({
|
|
42
|
+
isExpanded,
|
|
43
|
+
onExpandedChange() {
|
|
44
|
+
if (groupState) groupState.toggleKey(id);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const panelRef = useRef(null);
|
|
48
|
+
const triggerRef = useRef(null);
|
|
49
|
+
const isDisabled = groupState?.isDisabled || !group.tabs?.length || false;
|
|
50
|
+
const { buttonProps: triggerProps, panelProps } = useDisclosure({
|
|
51
|
+
...props,
|
|
52
|
+
isExpanded,
|
|
53
|
+
isDisabled
|
|
54
|
+
}, state, panelRef);
|
|
55
|
+
const { buttonProps } = useButton(triggerProps, triggerRef);
|
|
56
|
+
const { isFocusVisible, focusProps } = useFocusRing();
|
|
57
|
+
const store = useSelectState(selectStateKey, group.tabs?.[0]?.key || "");
|
|
58
|
+
const selectedTab = group.tabs?.find((tab) => tab.key === store.key) || group.tabs?.[0];
|
|
59
|
+
return <div className={clsx("openapi-disclosure-group", className)} aria-expanded={state.isExpanded}>
|
|
60
|
+
<div slot="trigger" ref={triggerRef} {...mergeProps(buttonProps, focusProps)} aria-disabled={isDisabled} style={{ outline: isFocusVisible ? "2px solid rgb(var(--primary-color-500)/0.4)" : "none" }} className="openapi-disclosure-group-trigger">
|
|
61
|
+
<div className="openapi-disclosure-group-icon">
|
|
62
|
+
{icon || <svg viewBox="0 0 24 24" className="openapi-disclosure-group-icon">
|
|
63
|
+
<path d="m8.25 4.5 7.5 7.5-7.5 7.5" />
|
|
64
|
+
</svg>}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div className="openapi-disclosure-group-label">
|
|
68
|
+
{group.label}
|
|
69
|
+
|
|
70
|
+
{group.tabs ? <div className="openapi-disclosure-group-mediatype" onClick={(e) => e.stopPropagation()}>
|
|
71
|
+
{group.tabs?.length > 1 ? <OpenAPISelect icon={selectIcon} stateKey={selectStateKey} onChange={() => {
|
|
72
|
+
state.expand();
|
|
73
|
+
}} items={group.tabs} placement="bottom end">
|
|
74
|
+
{group.tabs.map((tab) => <OpenAPISelectItem key={tab.key} id={tab.key} value={tab}>
|
|
75
|
+
{tab.label}
|
|
76
|
+
</OpenAPISelectItem>)}
|
|
77
|
+
</OpenAPISelect> : group.tabs[0]?.label ? <span>{group.tabs[0].label}</span> : null}
|
|
78
|
+
</div> : null}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{state.isExpanded && selectedTab && <div className="openapi-disclosure-group-panel" ref={panelRef} {...panelProps}>
|
|
83
|
+
{selectedTab.body}
|
|
84
|
+
</div>}
|
|
85
|
+
</div>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
export { OpenAPIDisclosureGroup };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { json2xml } from "./json2xml.js";
|
|
2
|
+
import { stringifyOpenAPI } from "./stringifyOpenAPI.js";
|
|
3
|
+
import { t } from "./translate.js";
|
|
4
|
+
import yaml from "js-yaml";
|
|
5
|
+
|
|
6
|
+
//#region src/OpenAPIExample.tsx
|
|
7
|
+
/**
|
|
8
|
+
* Display an example.
|
|
9
|
+
*/
|
|
10
|
+
function OpenAPIExample(props) {
|
|
11
|
+
const { example, context, syntax } = props;
|
|
12
|
+
const code = stringifyExample({
|
|
13
|
+
example,
|
|
14
|
+
syntax
|
|
15
|
+
});
|
|
16
|
+
if (code === null) return <OpenAPIEmptyExample context={context} />;
|
|
17
|
+
return context.renderCodeBlock({
|
|
18
|
+
code,
|
|
19
|
+
syntax
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function stringifyExample(args) {
|
|
23
|
+
const { example, syntax } = args;
|
|
24
|
+
if (!example.value) return null;
|
|
25
|
+
if (typeof example.value === "string") return example.value;
|
|
26
|
+
if (syntax === "xml") return json2xml(example.value);
|
|
27
|
+
if (syntax === "yaml") return yaml.dump(example.value).replace(/'/g, "").replace(/\\n/g, "\n");
|
|
28
|
+
return stringifyOpenAPI(example.value, null, 2);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Empty response example.
|
|
32
|
+
*/
|
|
33
|
+
function OpenAPIEmptyExample(props) {
|
|
34
|
+
const { context } = props;
|
|
35
|
+
return <pre className="openapi-example-empty">
|
|
36
|
+
<p>{t(context.translation, "no_content")}</p>
|
|
37
|
+
</pre>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
export { OpenAPIEmptyExample, OpenAPIExample };
|