@stoplight/elements 9.0.11 → 9.0.12-beta-0.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/.DS_Store +0 -0
- package/.storybook/main.js +6 -0
- package/.storybook/manager.js +1 -0
- package/.storybook/preview.jsx +3 -0
- package/dist/LICENSE +190 -0
- package/dist/README.md +19 -0
- package/dist/package.json +52 -0
- package/jest.config.js +7 -0
- package/package.json +76 -16
- package/src/__fixtures__/api-descriptions/Instagram.ts +1859 -0
- package/src/__fixtures__/api-descriptions/badgesForSchema.ts +36 -0
- package/src/__fixtures__/api-descriptions/simpleApiWithInternalOperations.ts +253 -0
- package/src/__fixtures__/api-descriptions/simpleApiWithoutDescription.ts +243 -0
- package/src/__fixtures__/api-descriptions/todosApiBundled.ts +430 -0
- package/src/__fixtures__/api-descriptions/zoomApiYaml.ts +6083 -0
- package/src/components/API/APIWithResponsiveSidebarLayout.tsx +125 -0
- package/src/components/API/APIWithSidebarLayout.tsx +158 -0
- package/src/components/API/APIWithStackedLayout.tsx +286 -0
- package/src/components/API/__tests__/utils.test.ts +1323 -0
- package/src/components/API/utils.ts +206 -0
- package/src/containers/API.spec.tsx +122 -0
- package/src/containers/API.stories.tsx +117 -0
- package/src/containers/API.tsx +277 -0
- package/src/containers/story-helper.tsx +53 -0
- package/src/hooks/useExportDocumentProps.spec.tsx +68 -0
- package/src/hooks/useExportDocumentProps.tsx +48 -0
- package/src/index.ts +6 -0
- package/src/styles.css +1 -0
- package/src/utils/oas/__tests__/oas.spec.ts +411 -0
- package/src/utils/oas/index.ts +192 -0
- package/src/utils/oas/oas2.ts +31 -0
- package/src/utils/oas/oas3.ts +54 -0
- package/src/utils/oas/types.ts +34 -0
- package/src/web-components/__stories__/Api.stories.tsx +63 -0
- package/src/web-components/components.ts +26 -0
- package/src/web-components/index.ts +3 -0
- package/tsconfig.build.json +18 -0
- package/tsconfig.json +7 -0
- package/web-components.config.js +1 -0
- package/styles.min.css +0 -1
- package/web-components.min.js +0 -2
- package/web-components.min.js.LICENSE.txt +0 -176
- /package/{__fixtures__ → dist/__fixtures__}/api-descriptions/Instagram.d.ts +0 -0
- /package/{__fixtures__ → dist/__fixtures__}/api-descriptions/badgesForSchema.d.ts +0 -0
- /package/{__fixtures__ → dist/__fixtures__}/api-descriptions/simpleApiWithInternalOperations.d.ts +0 -0
- /package/{__fixtures__ → dist/__fixtures__}/api-descriptions/simpleApiWithoutDescription.d.ts +0 -0
- /package/{__fixtures__ → dist/__fixtures__}/api-descriptions/todosApiBundled.d.ts +0 -0
- /package/{__fixtures__ → dist/__fixtures__}/api-descriptions/zoomApiYaml.d.ts +0 -0
- /package/{components → dist/components}/API/APIWithResponsiveSidebarLayout.d.ts +0 -0
- /package/{components → dist/components}/API/APIWithSidebarLayout.d.ts +0 -0
- /package/{components → dist/components}/API/APIWithStackedLayout.d.ts +0 -0
- /package/{components → dist/components}/API/utils.d.ts +0 -0
- /package/{containers → dist/containers}/API.d.ts +0 -0
- /package/{containers → dist/containers}/API.spec.d.ts +0 -0
- /package/{containers → dist/containers}/API.stories.d.ts +0 -0
- /package/{containers → dist/containers}/story-helper.d.ts +0 -0
- /package/{hooks → dist/hooks}/useExportDocumentProps.d.ts +0 -0
- /package/{hooks → dist/hooks}/useExportDocumentProps.spec.d.ts +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.esm.js → dist/index.esm.js} +0 -0
- /package/{index.js → dist/index.js} +0 -0
- /package/{index.mjs → dist/index.mjs} +0 -0
- /package/{utils → dist/utils}/oas/index.d.ts +0 -0
- /package/{utils → dist/utils}/oas/oas2.d.ts +0 -0
- /package/{utils → dist/utils}/oas/oas3.d.ts +0 -0
- /package/{utils → dist/utils}/oas/types.d.ts +0 -0
- /package/{web-components → dist/web-components}/components.d.ts +0 -0
- /package/{web-components → dist/web-components}/index.d.ts +0 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InlineRefResolverProvider,
|
|
3
|
+
NonIdealState,
|
|
4
|
+
RoutingProps,
|
|
5
|
+
useBundleRefsIntoDocument,
|
|
6
|
+
useParsedValue,
|
|
7
|
+
useResponsiveLayout,
|
|
8
|
+
withMosaicProvider,
|
|
9
|
+
withPersistenceBoundary,
|
|
10
|
+
withQueryClientProvider,
|
|
11
|
+
withRouter,
|
|
12
|
+
withStyles,
|
|
13
|
+
} from '@stoplight/elements-core';
|
|
14
|
+
import { ExtensionAddonRenderer } from '@stoplight/elements-core/components/Docs';
|
|
15
|
+
import { Box, Flex, Icon } from '@stoplight/mosaic';
|
|
16
|
+
import { flow } from 'lodash';
|
|
17
|
+
import * as React from 'react';
|
|
18
|
+
import { useQuery } from 'react-query';
|
|
19
|
+
import { useLocation } from 'react-router-dom';
|
|
20
|
+
|
|
21
|
+
import { APIWithResponsiveSidebarLayout } from '../components/API/APIWithResponsiveSidebarLayout';
|
|
22
|
+
import { APIWithSidebarLayout } from '../components/API/APIWithSidebarLayout';
|
|
23
|
+
import { APIWithStackedLayout } from '../components/API/APIWithStackedLayout';
|
|
24
|
+
import { useExportDocumentProps } from '../hooks/useExportDocumentProps';
|
|
25
|
+
import { transformOasToServiceNode } from '../utils/oas';
|
|
26
|
+
|
|
27
|
+
export type APIProps = APIPropsWithDocument | APIPropsWithUrl;
|
|
28
|
+
|
|
29
|
+
export type APIPropsWithUrl = {
|
|
30
|
+
/**
|
|
31
|
+
* Specify the URL of the input OAS2/3 document here.
|
|
32
|
+
*
|
|
33
|
+
* Mutually exclusive with `apiDescriptionDocument`.
|
|
34
|
+
*/
|
|
35
|
+
apiDescriptionUrl: string;
|
|
36
|
+
} & CommonAPIProps;
|
|
37
|
+
export type APIPropsWithDocument = {
|
|
38
|
+
/**
|
|
39
|
+
* You can specify the input OAS2/3 document here directly in JSON or YAML format.
|
|
40
|
+
*
|
|
41
|
+
* Mutually exclusive with `apiDescriptionUrl`.
|
|
42
|
+
*/
|
|
43
|
+
apiDescriptionDocument: string | object;
|
|
44
|
+
apiDescriptionUrl?: string;
|
|
45
|
+
} & CommonAPIProps;
|
|
46
|
+
|
|
47
|
+
export interface CommonAPIProps extends RoutingProps {
|
|
48
|
+
/**
|
|
49
|
+
* The API component supports two layout options.
|
|
50
|
+
*
|
|
51
|
+
* - Sidebar: Navigation on the left side, resembles Stoplight Platform.
|
|
52
|
+
* - Stacked: No sidebar, resembles the structure of Swagger UI.
|
|
53
|
+
*
|
|
54
|
+
* @default "sidebar"
|
|
55
|
+
*/
|
|
56
|
+
layout?: 'sidebar' | 'stacked' | 'responsive';
|
|
57
|
+
logo?: string;
|
|
58
|
+
|
|
59
|
+
hideTryIt?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Allows to hide RequestSamples component
|
|
62
|
+
* @default false
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
hideSamples?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Shows only operation document without right column
|
|
68
|
+
* @default false
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
hideTryItPanel?: boolean;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Allows hiding the Security info section
|
|
75
|
+
*/
|
|
76
|
+
hideSecurityInfo?: boolean;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Allows hiding the Server info section
|
|
80
|
+
*/
|
|
81
|
+
hideServerInfo?: boolean;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Hides schemas from being displayed in Table of Contents
|
|
85
|
+
*/
|
|
86
|
+
hideSchemas?: boolean;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Hides models and operations marked as internal
|
|
90
|
+
* @default false
|
|
91
|
+
*/
|
|
92
|
+
hideInternal?: boolean;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Hides export button from being displayed in overview page
|
|
96
|
+
* @default false
|
|
97
|
+
*/
|
|
98
|
+
hideExport?: boolean;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Fetch credentials policy for TryIt component
|
|
102
|
+
* For more information: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
|
|
103
|
+
* @default "omit"
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
tryItCredentialsPolicy?: 'omit' | 'include' | 'same-origin';
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Url of a CORS proxy that will be used to send requests in TryIt.
|
|
110
|
+
* Provided url will be prepended to an URL of an actual request.
|
|
111
|
+
* @default false
|
|
112
|
+
*/
|
|
113
|
+
tryItCorsProxy?: string;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The amount of references deep should be presented.
|
|
117
|
+
* @default undefined
|
|
118
|
+
*/
|
|
119
|
+
maxRefDepth?: number;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Allows to define renderers for vendor extensions
|
|
123
|
+
*/
|
|
124
|
+
renderExtensionAddon?: ExtensionAddonRenderer;
|
|
125
|
+
|
|
126
|
+
outerRouter?: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const propsAreWithDocument = (props: APIProps): props is APIPropsWithDocument => {
|
|
130
|
+
return props.hasOwnProperty('apiDescriptionDocument');
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export const APIImpl: React.FC<APIProps> = props => {
|
|
134
|
+
const {
|
|
135
|
+
layout = 'sidebar',
|
|
136
|
+
apiDescriptionUrl = '',
|
|
137
|
+
logo,
|
|
138
|
+
hideTryItPanel,
|
|
139
|
+
hideTryIt,
|
|
140
|
+
hideSamples,
|
|
141
|
+
hideSecurityInfo,
|
|
142
|
+
hideServerInfo,
|
|
143
|
+
hideSchemas,
|
|
144
|
+
hideInternal,
|
|
145
|
+
hideExport,
|
|
146
|
+
tryItCredentialsPolicy,
|
|
147
|
+
tryItCorsProxy,
|
|
148
|
+
maxRefDepth,
|
|
149
|
+
renderExtensionAddon,
|
|
150
|
+
basePath,
|
|
151
|
+
outerRouter = false,
|
|
152
|
+
} = props;
|
|
153
|
+
const location = useLocation();
|
|
154
|
+
const apiDescriptionDocument = propsAreWithDocument(props) ? props.apiDescriptionDocument : undefined;
|
|
155
|
+
const { isResponsiveLayoutEnabled } = useResponsiveLayout();
|
|
156
|
+
|
|
157
|
+
const { data: fetchedDocument, error } = useQuery(
|
|
158
|
+
[apiDescriptionUrl],
|
|
159
|
+
() =>
|
|
160
|
+
fetch(apiDescriptionUrl).then(res => {
|
|
161
|
+
if (res.ok) {
|
|
162
|
+
return res.text();
|
|
163
|
+
}
|
|
164
|
+
throw new Error(`Unable to load description document, status code: ${res.status}`);
|
|
165
|
+
}),
|
|
166
|
+
{
|
|
167
|
+
enabled: apiDescriptionUrl !== '' && !apiDescriptionDocument,
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const document = apiDescriptionDocument || fetchedDocument || '';
|
|
172
|
+
const parsedDocument = useParsedValue(document);
|
|
173
|
+
const bundledDocument = useBundleRefsIntoDocument(parsedDocument, { baseUrl: apiDescriptionUrl });
|
|
174
|
+
const serviceNode = React.useMemo(() => transformOasToServiceNode(bundledDocument), [bundledDocument]);
|
|
175
|
+
const exportProps = useExportDocumentProps({ originalDocument: document, bundledDocument });
|
|
176
|
+
|
|
177
|
+
if (error) {
|
|
178
|
+
return (
|
|
179
|
+
<Flex justify="center" alignItems="center" w="full" minH="screen">
|
|
180
|
+
<NonIdealState
|
|
181
|
+
title="Document could not be loaded"
|
|
182
|
+
description="The API description document could not be fetched. This could indicate connectivity problems, or issues with the server hosting the spec."
|
|
183
|
+
icon="exclamation-triangle"
|
|
184
|
+
/>
|
|
185
|
+
</Flex>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!bundledDocument) {
|
|
190
|
+
return (
|
|
191
|
+
<Flex justify="center" alignItems="center" w="full" minH="screen" color="light">
|
|
192
|
+
<Box as={Icon} icon={['fal', 'circle-notch']} size="3x" spin />
|
|
193
|
+
</Flex>
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!serviceNode) {
|
|
198
|
+
return (
|
|
199
|
+
<Flex justify="center" alignItems="center" w="full" minH="screen">
|
|
200
|
+
<NonIdealState
|
|
201
|
+
title="Failed to parse OpenAPI file"
|
|
202
|
+
description="Please make sure your OpenAPI file is valid and try again"
|
|
203
|
+
/>
|
|
204
|
+
</Flex>
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<InlineRefResolverProvider document={parsedDocument} maxRefDepth={maxRefDepth}>
|
|
210
|
+
{layout === 'stacked' && (
|
|
211
|
+
<APIWithStackedLayout
|
|
212
|
+
serviceNode={serviceNode}
|
|
213
|
+
hideTryIt={hideTryIt}
|
|
214
|
+
hideSamples={hideSamples}
|
|
215
|
+
hideTryItPanel={hideTryItPanel}
|
|
216
|
+
hideSecurityInfo={hideSecurityInfo}
|
|
217
|
+
hideServerInfo={hideServerInfo}
|
|
218
|
+
hideExport={hideExport}
|
|
219
|
+
exportProps={exportProps}
|
|
220
|
+
tryItCredentialsPolicy={tryItCredentialsPolicy}
|
|
221
|
+
tryItCorsProxy={tryItCorsProxy}
|
|
222
|
+
renderExtensionAddon={renderExtensionAddon}
|
|
223
|
+
location={location}
|
|
224
|
+
/>
|
|
225
|
+
)}
|
|
226
|
+
{layout === 'sidebar' && (
|
|
227
|
+
<APIWithSidebarLayout
|
|
228
|
+
logo={logo}
|
|
229
|
+
serviceNode={serviceNode}
|
|
230
|
+
hideTryItPanel={hideTryItPanel}
|
|
231
|
+
hideTryIt={hideTryIt}
|
|
232
|
+
hideSamples={hideSamples}
|
|
233
|
+
hideSecurityInfo={hideSecurityInfo}
|
|
234
|
+
hideServerInfo={hideServerInfo}
|
|
235
|
+
hideSchemas={hideSchemas}
|
|
236
|
+
hideInternal={hideInternal}
|
|
237
|
+
hideExport={hideExport}
|
|
238
|
+
exportProps={exportProps}
|
|
239
|
+
tryItCredentialsPolicy={tryItCredentialsPolicy}
|
|
240
|
+
tryItCorsProxy={tryItCorsProxy}
|
|
241
|
+
renderExtensionAddon={renderExtensionAddon}
|
|
242
|
+
basePath={basePath}
|
|
243
|
+
outerRouter={outerRouter}
|
|
244
|
+
/>
|
|
245
|
+
)}
|
|
246
|
+
{layout === 'responsive' && (
|
|
247
|
+
<APIWithResponsiveSidebarLayout
|
|
248
|
+
logo={logo}
|
|
249
|
+
serviceNode={serviceNode}
|
|
250
|
+
hideTryItPanel={hideTryItPanel}
|
|
251
|
+
hideTryIt={hideTryIt}
|
|
252
|
+
hideSamples={hideSamples}
|
|
253
|
+
hideSecurityInfo={hideSecurityInfo}
|
|
254
|
+
hideServerInfo={hideServerInfo}
|
|
255
|
+
hideSchemas={hideSchemas}
|
|
256
|
+
hideInternal={hideInternal}
|
|
257
|
+
hideExport={hideExport}
|
|
258
|
+
exportProps={exportProps}
|
|
259
|
+
tryItCredentialsPolicy={tryItCredentialsPolicy}
|
|
260
|
+
tryItCorsProxy={tryItCorsProxy}
|
|
261
|
+
renderExtensionAddon={renderExtensionAddon}
|
|
262
|
+
compact={isResponsiveLayoutEnabled}
|
|
263
|
+
basePath={basePath}
|
|
264
|
+
outerRouter={outerRouter}
|
|
265
|
+
/>
|
|
266
|
+
)}
|
|
267
|
+
</InlineRefResolverProvider>
|
|
268
|
+
);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const API = flow(
|
|
272
|
+
withRouter,
|
|
273
|
+
withStyles,
|
|
274
|
+
withPersistenceBoundary,
|
|
275
|
+
withMosaicProvider,
|
|
276
|
+
withQueryClientProvider,
|
|
277
|
+
)(APIImpl);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ElementsOptionsProvider } from '@stoplight/elements-core/context/Options';
|
|
2
|
+
import { MarkdownViewer } from '@stoplight/markdown-viewer';
|
|
3
|
+
import { Box } from '@stoplight/mosaic';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Renders the known x-enum-Description vendor extension
|
|
8
|
+
* @returns React.ReactElement
|
|
9
|
+
*/
|
|
10
|
+
// eslint-disable-next-line storybook/prefer-pascal-case
|
|
11
|
+
export const renderExtensionRenderer = (props: any) => {
|
|
12
|
+
const { nestingLevel, schemaNode: node, vendorExtensions } = props;
|
|
13
|
+
|
|
14
|
+
// If the nesting level is 0, we are at the root of the schema and should not render anything
|
|
15
|
+
if (nestingLevel === 0) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// This implementation of the extension renderer only supports the `x-enum-descriptions`-extension
|
|
20
|
+
if ('x-enum-descriptions' in vendorExtensions) {
|
|
21
|
+
const { 'x-enum-descriptions': enumDescriptions = {} } = vendorExtensions;
|
|
22
|
+
|
|
23
|
+
let value = `| Enum value | Description |\n|---|---|\n`;
|
|
24
|
+
const enums = node.enum ?? [];
|
|
25
|
+
enums.forEach((name: string) => {
|
|
26
|
+
const description = enumDescriptions[name as string];
|
|
27
|
+
value += `| ${name} | ${description} |\n`;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box mb={2}>
|
|
32
|
+
<Box
|
|
33
|
+
mt={2}
|
|
34
|
+
as={MarkdownViewer}
|
|
35
|
+
markdown={value}
|
|
36
|
+
style={{
|
|
37
|
+
fontSize: 12,
|
|
38
|
+
}}
|
|
39
|
+
/>
|
|
40
|
+
</Box>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @private
|
|
49
|
+
* Helper function to wrap the options context for the Docs subcomponents
|
|
50
|
+
*/
|
|
51
|
+
export const wrapOptionsContext = (story: any) => {
|
|
52
|
+
return <ElementsOptionsProvider renderExtensionAddon={renderExtensionRenderer}>{story}</ElementsOptionsProvider>;
|
|
53
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import '@testing-library/jest-dom';
|
|
2
|
+
|
|
3
|
+
import { safeStringify } from '@stoplight/yaml';
|
|
4
|
+
import { act, renderHook } from '@testing-library/react-hooks';
|
|
5
|
+
import { saveAs } from 'file-saver';
|
|
6
|
+
|
|
7
|
+
import { InstagramAPI as bundledJson } from '../__fixtures__/api-descriptions/Instagram';
|
|
8
|
+
import { simpleApiWithoutDescription as json } from '../__fixtures__/api-descriptions/simpleApiWithoutDescription';
|
|
9
|
+
import { todosApiBundled as bundledYaml } from '../__fixtures__/api-descriptions/todosApiBundled';
|
|
10
|
+
import { zoomApiYaml as yaml } from '../__fixtures__/api-descriptions/zoomApiYaml';
|
|
11
|
+
import { useExportDocumentProps } from './useExportDocumentProps';
|
|
12
|
+
|
|
13
|
+
jest.mock('file-saver');
|
|
14
|
+
|
|
15
|
+
describe('useExportDocumentProps', () => {
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
jest.resetAllMocks();
|
|
18
|
+
});
|
|
19
|
+
it('exports json document', () => {
|
|
20
|
+
const data = renderHook(() =>
|
|
21
|
+
useExportDocumentProps({
|
|
22
|
+
originalDocument: json,
|
|
23
|
+
bundledDocument: bundledJson,
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
act(() => {
|
|
28
|
+
data.result.current.original.onPress();
|
|
29
|
+
data.result.current.bundled.onPress();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const expectedOriginalDocument = new Blob([JSON.stringify(json, null, 2)], {
|
|
33
|
+
type: 'application/json',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const expectedBundledDocument = new Blob([JSON.stringify(bundledJson, null, 2)], {
|
|
37
|
+
type: 'application/json',
|
|
38
|
+
});
|
|
39
|
+
expect(saveAs).toBeCalledTimes(2);
|
|
40
|
+
expect(saveAs).toHaveBeenCalledWith(expectedOriginalDocument, 'document.json');
|
|
41
|
+
expect(saveAs).toHaveBeenCalledWith(expectedBundledDocument, 'document.json');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('exports yaml document', () => {
|
|
45
|
+
const data = renderHook(() =>
|
|
46
|
+
useExportDocumentProps({
|
|
47
|
+
originalDocument: safeStringify(yaml),
|
|
48
|
+
bundledDocument: bundledYaml,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
act(() => {
|
|
53
|
+
data.result.current.original.onPress();
|
|
54
|
+
data.result.current.bundled.onPress();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const expectedOriginalDocument = new Blob([safeStringify(yaml)], {
|
|
58
|
+
type: 'application/yaml',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const expectedBundledDocument = new Blob([safeStringify(bundledYaml)], {
|
|
62
|
+
type: 'application/yaml',
|
|
63
|
+
});
|
|
64
|
+
expect(saveAs).toBeCalledTimes(2);
|
|
65
|
+
expect(saveAs).toHaveBeenCalledWith(expectedOriginalDocument, 'document.yaml');
|
|
66
|
+
expect(saveAs).toHaveBeenCalledWith(expectedBundledDocument, 'document.yaml');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { safeStringify } from '@stoplight/yaml';
|
|
2
|
+
import saver from 'file-saver';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
|
|
5
|
+
import { isJson } from '../utils/oas';
|
|
6
|
+
|
|
7
|
+
export function useExportDocumentProps({
|
|
8
|
+
originalDocument,
|
|
9
|
+
bundledDocument,
|
|
10
|
+
}: {
|
|
11
|
+
originalDocument: string | object;
|
|
12
|
+
bundledDocument: unknown;
|
|
13
|
+
}) {
|
|
14
|
+
const isJsonDocument = typeof originalDocument === 'object' || (!!originalDocument && isJson(originalDocument));
|
|
15
|
+
|
|
16
|
+
const exportDocument = React.useCallback(
|
|
17
|
+
(document: string) => {
|
|
18
|
+
const type = isJsonDocument ? 'json' : 'yaml';
|
|
19
|
+
const blob = new Blob([document], {
|
|
20
|
+
type: `application/${type}`,
|
|
21
|
+
});
|
|
22
|
+
saver.saveAs(blob, `document.${type}`);
|
|
23
|
+
},
|
|
24
|
+
[isJsonDocument],
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const exportOriginalDocument = React.useCallback(() => {
|
|
28
|
+
const stringifiedDocument =
|
|
29
|
+
typeof originalDocument === 'object' ? JSON.stringify(originalDocument, null, 2) : originalDocument || '';
|
|
30
|
+
exportDocument(stringifiedDocument);
|
|
31
|
+
}, [originalDocument, exportDocument]);
|
|
32
|
+
|
|
33
|
+
const exportBundledDocument = React.useCallback(() => {
|
|
34
|
+
const stringifiedDocument = isJsonDocument
|
|
35
|
+
? JSON.stringify(bundledDocument, null, 2)
|
|
36
|
+
: safeStringify(bundledDocument);
|
|
37
|
+
exportDocument(stringifiedDocument);
|
|
38
|
+
}, [bundledDocument, isJsonDocument, exportDocument]);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
original: {
|
|
42
|
+
onPress: exportOriginalDocument,
|
|
43
|
+
},
|
|
44
|
+
bundled: {
|
|
45
|
+
onPress: exportBundledDocument,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { APIWithStackedLayout } from './components/API/APIWithStackedLayout';
|
|
2
|
+
export type { APIProps } from './containers/API';
|
|
3
|
+
export { API } from './containers/API';
|
|
4
|
+
export { useExportDocumentProps } from './hooks/useExportDocumentProps';
|
|
5
|
+
export { transformOasToServiceNode } from './utils/oas';
|
|
6
|
+
export type { ServiceNode } from './utils/oas/types';
|
package/src/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "../../elements-core/src/styles.css";
|