mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-05 12:50:18 +03:00
Merge tag 'v2.0.0-rc.63' into sections-at-the-end
This commit is contained in:
commit
7d58d3e9fd
|
@ -17,7 +17,7 @@ module.exports = {
|
||||||
version: 'detect',
|
version: 'detect',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: ['@typescript-eslint', 'import'],
|
plugins: ['react', 'react-hooks', '@typescript-eslint', 'import'],
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
@ -31,6 +31,8 @@ module.exports = {
|
||||||
'@typescript-eslint/no-var-requires': 'off',
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
|
|
||||||
'react/prop-types': 'off',
|
'react/prop-types': 'off',
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'warn',
|
||||||
|
|
||||||
'import/no-extraneous-dependencies': 'error',
|
'import/no-extraneous-dependencies': 'error',
|
||||||
'import/no-internal-modules': [
|
'import/no-internal-modules': [
|
||||||
|
|
69
.github/workflows/publish-cli.yml
vendored
69
.github/workflows/publish-cli.yml
vendored
|
@ -5,53 +5,52 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bundle:
|
bundle:
|
||||||
needs: [ check-version-cli ]
|
needs: [check-version-cli]
|
||||||
if: needs.check-version-cli.outputs.changed == 'true'
|
if: needs.check-version-cli.outputs.changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||||
key: npm-${{ hashFiles('package-lock.json') }}
|
key: npm-${{ hashFiles('package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
npm-${{ hashFiles('package-lock.json') }}
|
npm-${{ hashFiles('package-lock.json') }}
|
||||||
npm-
|
npm-
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run bundle
|
- run: npm run bundle
|
||||||
- name: Store bundle artifact
|
- name: Store bundle artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: bundles-cli
|
name: bundles-cli
|
||||||
path: bundles
|
path: bundles
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
unit-tests:
|
unit-tests:
|
||||||
needs: [ check-version-cli ]
|
needs: [check-version-cli]
|
||||||
if: needs.check-version-cli.outputs.changed == 'true'
|
if: needs.check-version-cli.outputs.changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm test
|
- run: npm test
|
||||||
e2e-tests:
|
e2e-tests:
|
||||||
needs: [ bundle ]
|
needs: [bundle]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Download bundled artifact
|
- name: Download bundled artifact
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: bundles-cli
|
name: bundles-cli
|
||||||
path: bundles
|
path: bundles
|
||||||
- run: npm run e2e
|
- run: npm run e2e
|
||||||
bundle-cli:
|
bundle-cli:
|
||||||
needs: [ check-version-cli ]
|
needs: [check-version-cli]
|
||||||
if: needs.check-version-cli.outputs.changed == 'true'
|
if: needs.check-version-cli.outputs.changed == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
@ -99,7 +98,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: "14.x"
|
node-version: '14.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Download cli bundled artifact
|
- name: Download cli bundled artifact
|
||||||
|
|
60
.github/workflows/publish.yml
vendored
60
.github/workflows/publish.yml
vendored
|
@ -9,42 +9,42 @@ jobs:
|
||||||
bundle:
|
bundle:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
- name: Cache node modules
|
- name: Cache node modules
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||||
key: npm-${{ hashFiles('package-lock.json') }}
|
key: npm-${{ hashFiles('package-lock.json') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
npm-${{ hashFiles('package-lock.json') }}
|
npm-${{ hashFiles('package-lock.json') }}
|
||||||
npm-
|
npm-
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run bundle
|
- run: npm run bundle
|
||||||
- name: Store bundle artifact
|
- name: Store bundle artifact
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: bundles
|
name: bundles
|
||||||
path: bundles
|
path: bundles
|
||||||
retention-days: 1
|
retention-days: 1
|
||||||
unit-tests:
|
unit-tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm test
|
- run: npm test
|
||||||
e2e-tests:
|
e2e-tests:
|
||||||
needs: [bundle]
|
needs: [bundle]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Download bundled artifact
|
- name: Download bundled artifact
|
||||||
uses: actions/download-artifact@v2
|
uses: actions/download-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: bundles
|
name: bundles
|
||||||
path: bundles
|
path: bundles
|
||||||
- run: npm run e2e
|
- run: npm run e2e
|
||||||
# disable this for now
|
# disable this for now
|
||||||
# deploy-demo:
|
# deploy-demo:
|
||||||
# needs: [bundle, unit-tests, e2e-tests]
|
# needs: [bundle, unit-tests, e2e-tests]
|
||||||
|
@ -76,7 +76,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: "14.x"
|
node-version: '14.x'
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Download bundled artifacts
|
- name: Download bundled artifacts
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -37,3 +37,4 @@ stats.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.eslintcache
|
||||||
|
|
44
CHANGELOG.md
44
CHANGELOG.md
|
@ -1,3 +1,47 @@
|
||||||
|
# [2.0.0-rc.63](https://github.com/Redocly/redoc/compare/v2.0.0-rc.61...v2.0.0-rc.63) (2022-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* scroll in sidebar ([b5b0d61](https://github.com/Redocly/redoc/commit/b5b0d61b3568ac2a8aaceafa96ffa6d2f86ed323))
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.62](https://github.com/Redocly/redoc/compare/v2.0.0-rc.61...v2.0.0-rc.62) (2022-01-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix field expand does not work ([#1875](https://github.com/Redocly/redoc/issues/1875))
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.61](https://github.com/Redocly/redoc/compare/v2.0.0-rc.60...v2.0.0-rc.61) (2022-01-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix crash in redoc-cli after migrating to esbuild ([#1872](https://github.com/Redocly/redoc/issues/1872))
|
||||||
|
|
||||||
|
# [2.0.0-rc.60](https://github.com/Redocly/redoc/compare/v2.0.0-rc.59...v2.0.0-rc.60) (2022-01-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add schema expansion level ([#1868](https://github.com/Redocly/redoc/issues/1868)) ([250d53a](https://github.com/Redocly/redoc/commit/250d53a59fb4bf881875ba466c5a7f3b55d80007))
|
||||||
|
* attachHeadingsDescriptions match headings incorrectly ([#1845](https://github.com/Redocly/redoc/issues/1845)) ([ea8573d](https://github.com/Redocly/redoc/commit/ea8573dbd78439be50aa2b38f1c83658c16783e3))
|
||||||
|
* definition name util ([#1865](https://github.com/Redocly/redoc/issues/1865)) ([95a7347](https://github.com/Redocly/redoc/commit/95a734793158d4749e98ee4a7e90e70713a04ced))
|
||||||
|
* No maxLength label is displayed for arrays of items [#1701](https://github.com/Redocly/redoc/issues/1701) ([#1765](https://github.com/Redocly/redoc/issues/1765)) ([6c7685e](https://github.com/Redocly/redoc/commit/6c7685e5fa04314328a445d7077600692c49489c))
|
||||||
|
* Response objects couldn't open ([#1867](https://github.com/Redocly/redoc/issues/1867)) ([18f943d](https://github.com/Redocly/redoc/commit/18f943d2b5668f1552d212dee1c3a2ed59054095))
|
||||||
|
* writeOnly params displaying in webhook ([#1866](https://github.com/Redocly/redoc/issues/1866)) ([5694913](https://github.com/Redocly/redoc/commit/5694913e71f0e8c3a5d9393f1b4ae92534127841))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **#1251:** Add file selector to demo application ([#1859](https://github.com/Redocly/redoc/issues/1859)) ([b74dcde](https://github.com/Redocly/redoc/commit/b74dcde42b45ebe5ae617f1ec3cfea2ea1aff922)), closes [#1251](https://github.com/Redocly/redoc/issues/1251) [#1251](https://github.com/Redocly/redoc/issues/1251) [#1251](https://github.com/Redocly/redoc/issues/1251)
|
||||||
|
* redoc-cli add host option ([#1598](https://github.com/Redocly/redoc/issues/1598)) ([fb104e6](https://github.com/Redocly/redoc/commit/fb104e696618b0b81439da134887830a0f2439ea))
|
||||||
|
* support examples in object schema ([#1832](https://github.com/Redocly/redoc/issues/1832)) ([c986f0e](https://github.com/Redocly/redoc/commit/c986f0ef1a38bc1e61cae70830d84de03b684b89))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [2.0.0-rc.59](https://github.com/Redocly/redoc/compare/v2.0.0-rc.58...v2.0.0-rc.59) (2021-12-09)
|
# [2.0.0-rc.59](https://github.com/Redocly/redoc/compare/v2.0.0-rc.58...v2.0.0-rc.59) (2021-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
|
12
cli/index.ts
12
cli/index.ts
|
@ -61,6 +61,12 @@ YargsParser.command(
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
yargs.option('h', {
|
||||||
|
alias: 'host',
|
||||||
|
type: 'string',
|
||||||
|
default: '127.0.0.1',
|
||||||
|
});
|
||||||
|
|
||||||
yargs.option('p', {
|
yargs.option('p', {
|
||||||
alias: 'port',
|
alias: 'port',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
@ -93,7 +99,7 @@ YargsParser.command(
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await serve(argv.port as number, argv.spec as string, config);
|
await serve(argv.host as string, argv.port as number, argv.spec as string, config);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
}
|
}
|
||||||
|
@ -167,7 +173,7 @@ YargsParser.command(
|
||||||
describe: 'ReDoc options, use dot notation, e.g. options.nativeScrollbars',
|
describe: 'ReDoc options, use dot notation, e.g. options.nativeScrollbars',
|
||||||
}).argv;
|
}).argv;
|
||||||
|
|
||||||
async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
async function serve(host: string, port: number, pathToSpec: string, options: Options = {}) {
|
||||||
let spec = await loadAndBundleSpec(isURL(pathToSpec) ? pathToSpec : resolve(pathToSpec));
|
let spec = await loadAndBundleSpec(isURL(pathToSpec) ? pathToSpec : resolve(pathToSpec));
|
||||||
let pageHTML = await getPageHTML(spec, pathToSpec, options);
|
let pageHTML = await getPageHTML(spec, pathToSpec, options);
|
||||||
const server = createServer((request, response) => {
|
const server = createServer((request, response) => {
|
||||||
|
@ -201,7 +207,7 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
server.listen(port, () => console.log(`Server started: http://127.0.0.1:${port}`));
|
server.listen(port, host, () => console.log(`Server started: http://${host}:${port}`));
|
||||||
|
|
||||||
if (options.watch && existsSync(pathToSpec)) {
|
if (options.watch && existsSync(pathToSpec)) {
|
||||||
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
||||||
|
|
626
cli/npm-shrinkwrap.json
generated
626
cli/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc-cli",
|
"name": "redoc-cli",
|
||||||
"version": "0.13.1",
|
"version": "0.13.6",
|
||||||
"description": "ReDoc's Command Line Interface",
|
"description": "ReDoc's Command Line Interface",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
"node-libs-browser": "^2.2.1",
|
"node-libs-browser": "^2.2.1",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"redoc": "2.0.0-rc.58",
|
"redoc": "2.0.0-rc.62",
|
||||||
"styled-components": "^5.3.0",
|
"styled-components": "^5.3.0",
|
||||||
"yargs": "^17.0.1"
|
"yargs": "^17.0.1"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,46 +1,5 @@
|
||||||
import * as webpack from 'webpack';
|
import * as webpack from 'webpack';
|
||||||
|
|
||||||
export function getBabelLoader({useBuiltIns, hot}: {useBuiltIns: boolean, hot?: boolean}) {
|
|
||||||
return {
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
babelrc: false,
|
|
||||||
sourceType: 'unambiguous',
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@babel/preset-env',
|
|
||||||
{
|
|
||||||
useBuiltIns: useBuiltIns ? 'usage' : false,
|
|
||||||
corejs: 3,
|
|
||||||
exclude: ['transform-typeof-symbol'],
|
|
||||||
targets: 'defaults',
|
|
||||||
modules: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
['@babel/preset-react', { development: false, runtime: 'classic' }],
|
|
||||||
'@babel/preset-typescript',
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
|
||||||
['@babel/plugin-proposal-class-properties', { loose: false }],
|
|
||||||
[
|
|
||||||
'@babel/plugin-transform-runtime',
|
|
||||||
{
|
|
||||||
corejs: false,
|
|
||||||
helpers: true,
|
|
||||||
// eslint-disable-next-line import/no-internal-modules
|
|
||||||
version: require('@babel/runtime/package.json').version,
|
|
||||||
regenerator: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@babel/plugin-proposal-optional-chaining',
|
|
||||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
|
||||||
hot ? 'react-hot-loader/babel' : undefined,
|
|
||||||
].filter(Boolean)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function webpackIgnore(regexp) {
|
export function webpackIgnore(regexp) {
|
||||||
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash/noop.js'));
|
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash.noop'));
|
||||||
}
|
}
|
||||||
|
|
2
custom.d.ts
vendored
2
custom.d.ts
vendored
|
@ -23,3 +23,5 @@ declare var reactHotLoaderGlobal: any;
|
||||||
interface Element {
|
interface Element {
|
||||||
scrollIntoViewIfNeeded(centerIfNeeded?: boolean): void;
|
scrollIntoViewIfNeeded(centerIfNeeded?: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GenericObject = Record<string, any>;
|
||||||
|
|
52
demo/components/FileInput.tsx
Normal file
52
demo/components/FileInput.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import * as yaml from 'js-yaml';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { ChangeEvent, RefObject, useRef } from 'react';
|
||||||
|
import styled from '../../src/styled-components';
|
||||||
|
|
||||||
|
const Button = styled.button`
|
||||||
|
background-color: #fff;
|
||||||
|
color: #333;
|
||||||
|
padding: 2px 10px;
|
||||||
|
touch-action: manipulation;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
font-size: 16px;
|
||||||
|
height: 28px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
vertical-align: middle;
|
||||||
|
line-height: 1;
|
||||||
|
outline: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
@media (max-width: 699px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function FileInput(props: { onUpload }) {
|
||||||
|
const hiddenFileInput: RefObject<HTMLInputElement> = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
if (hiddenFileInput && hiddenFileInput.current) {
|
||||||
|
hiddenFileInput.current.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const uploadFile = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = (event.target as HTMLInputElement).files![0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
props.onUpload(yaml.load(reader.result));
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Button onClick={handleClick}>Upload a file</Button>
|
||||||
|
<input type="file" style={{ display: 'none' }} onChange={uploadFile} ref={hiddenFileInput} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileInput;
|
|
@ -4,6 +4,7 @@ import styled from 'styled-components';
|
||||||
import { resolve as urlResolve } from 'url';
|
import { resolve as urlResolve } from 'url';
|
||||||
import { RedocStandalone } from '../src';
|
import { RedocStandalone } from '../src';
|
||||||
import ComboBox from './ComboBox';
|
import ComboBox from './ComboBox';
|
||||||
|
import FileInput from './components/FileInput';
|
||||||
|
|
||||||
const DEFAULT_SPEC = 'openapi.yaml';
|
const DEFAULT_SPEC = 'openapi.yaml';
|
||||||
const NEW_VERSION_SPEC = 'openapi-3-1.yaml';
|
const NEW_VERSION_SPEC = 'openapi-3-1.yaml';
|
||||||
|
@ -22,7 +23,7 @@ const demos = [
|
||||||
|
|
||||||
class DemoApp extends React.Component<
|
class DemoApp extends React.Component<
|
||||||
{},
|
{},
|
||||||
{ specUrl: string; dropdownOpen: boolean; cors: boolean }
|
{ spec: object | undefined; specUrl: string; dropdownOpen: boolean; cors: boolean }
|
||||||
> {
|
> {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -40,15 +41,24 @@ class DemoApp extends React.Component<
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
spec: undefined,
|
||||||
specUrl: url,
|
specUrl: url,
|
||||||
dropdownOpen: false,
|
dropdownOpen: false,
|
||||||
cors,
|
cors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleUploadFile = (spec: object) => {
|
||||||
|
this.setState({
|
||||||
|
spec,
|
||||||
|
specUrl: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
handleChange = (url: string) => {
|
handleChange = (url: string) => {
|
||||||
if (url === NEW_VERSION_SPEC) {
|
if (url === NEW_VERSION_SPEC) {
|
||||||
this.setState({ cors: false })
|
this.setState({ cors: false });
|
||||||
|
0;
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
specUrl: url,
|
specUrl: url,
|
||||||
|
@ -90,6 +100,7 @@ class DemoApp extends React.Component<
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<ControlsContainer>
|
<ControlsContainer>
|
||||||
|
<FileInput onUpload={this.handleUploadFile} />
|
||||||
<ComboBox
|
<ComboBox
|
||||||
placeholder={'URL to a spec to try'}
|
placeholder={'URL to a spec to try'}
|
||||||
options={demos}
|
options={demos}
|
||||||
|
@ -110,6 +121,7 @@ class DemoApp extends React.Component<
|
||||||
/>
|
/>
|
||||||
</Heading>
|
</Heading>
|
||||||
<RedocStandalone
|
<RedocStandalone
|
||||||
|
spec={this.state.spec}
|
||||||
specUrl={proxiedUrl}
|
specUrl={proxiedUrl}
|
||||||
options={{ scrollYOffset: 'nav', untrustedSpec: true }}
|
options={{ scrollYOffset: 'nav', untrustedSpec: true }}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
|
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import * as webpack from 'webpack';
|
import * as webpack from 'webpack';
|
||||||
import { getBabelLoader, webpackIgnore } from '../config/webpack-utils';
|
import { webpackIgnore } from '../config/webpack-utils';
|
||||||
|
|
||||||
const VERSION = JSON.stringify(require('../package.json').version);
|
const VERSION = JSON.stringify(require('../package.json').version);
|
||||||
const REVISION = JSON.stringify(
|
const REVISION = JSON.stringify(
|
||||||
|
@ -37,6 +37,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
port: 9090,
|
port: 9090,
|
||||||
hot: true,
|
hot: true,
|
||||||
historyApiFallback: true,
|
historyApiFallback: true,
|
||||||
|
open: true,
|
||||||
},
|
},
|
||||||
stats: {
|
stats: {
|
||||||
children: true,
|
children: true,
|
||||||
|
@ -45,6 +46,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
extensions: ['.ts', '.tsx', '.js', '.json'],
|
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||||
fallback: {
|
fallback: {
|
||||||
path: require.resolve('path-browserify'),
|
path: require.resolve('path-browserify'),
|
||||||
|
buffer: require.resolve('buffer'),
|
||||||
http: false,
|
http: false,
|
||||||
fs: false,
|
fs: false,
|
||||||
os: false,
|
os: false,
|
||||||
|
@ -71,33 +73,28 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
rules: [
|
rules: [
|
||||||
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.(tsx?|[cm]?js)$/,
|
||||||
use: [getBabelLoader({ useBuiltIns: true, hot: true })],
|
loader: 'esbuild-loader',
|
||||||
exclude: {
|
options: {
|
||||||
and: [/node_modules/],
|
loader: 'tsx',
|
||||||
not: {
|
target: 'es2015',
|
||||||
or: [
|
tsconfigRaw: require('../tsconfig.json'),
|
||||||
/swagger2openapi/,
|
|
||||||
/reftools/,
|
|
||||||
/openapi-sampler/,
|
|
||||||
/mobx/,
|
|
||||||
/oas-resolver/,
|
|
||||||
/oas-kit-common/,
|
|
||||||
/oas-schema-walker/,
|
|
||||||
/\@redocly\/openapi-core/,
|
|
||||||
/colorette/,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
exclude: [/node_modules/],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: {
|
use: [
|
||||||
loader: 'css-loader',
|
'style-loader',
|
||||||
options: {
|
'css-loader',
|
||||||
sourceMap: true,
|
{
|
||||||
|
loader: 'esbuild-loader',
|
||||||
|
options: {
|
||||||
|
loader: 'css',
|
||||||
|
minify: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -118,6 +115,9 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
? 'benchmark/index.html'
|
? 'benchmark/index.html'
|
||||||
: 'demo/index.html',
|
: 'demo/index.html',
|
||||||
}),
|
}),
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
Buffer: ['buffer', 'Buffer'],
|
||||||
|
}),
|
||||||
new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
|
new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
|
||||||
webpackIgnore(/js-yaml\/dumper\.js$/),
|
webpackIgnore(/js-yaml\/dumper\.js$/),
|
||||||
webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
|
webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
|
||||||
|
|
|
@ -4,9 +4,7 @@ describe('Menu', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have valid items count', () => {
|
it('should have valid items count', () => {
|
||||||
cy.get('.menu-content')
|
cy.get('.menu-content').find('li').should('have.length', 34);
|
||||||
.find('li')
|
|
||||||
.should('have.length', 34);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync active menu items while scroll', () => {
|
it('should sync active menu items while scroll', () => {
|
||||||
|
@ -35,9 +33,7 @@ describe('Menu', () => {
|
||||||
.should('have.text', 'Add a new pet to the store')
|
.should('have.text', 'Add a new pet to the store')
|
||||||
.should('be.visible');
|
.should('be.visible');
|
||||||
|
|
||||||
cy.contains('h1', 'Swagger Petstore')
|
cy.contains('h1', 'Swagger Petstore').scrollIntoView().wait(100);
|
||||||
.scrollIntoView()
|
|
||||||
.wait(100)
|
|
||||||
|
|
||||||
cy.contains('h1', 'Introduction')
|
cy.contains('h1', 'Introduction')
|
||||||
.scrollIntoView()
|
.scrollIntoView()
|
||||||
|
@ -59,10 +55,25 @@ describe('Menu', () => {
|
||||||
it('should deactivate tag when other is activated', () => {
|
it('should deactivate tag when other is activated', () => {
|
||||||
const petItem = () => cy.contains('[role=menuitem].-depth1', 'pet');
|
const petItem = () => cy.contains('[role=menuitem].-depth1', 'pet');
|
||||||
|
|
||||||
petItem()
|
petItem().click({ force: true }).should('have.class', 'active');
|
||||||
.click({ force: true })
|
|
||||||
.should('have.class', 'active');
|
|
||||||
cy.contains('[role=menuitem].-depth1', 'store').click({ force: true });
|
cy.contains('[role=menuitem].-depth1', 'store').click({ force: true });
|
||||||
petItem().should('not.have.class', 'active');
|
petItem().should('not.have.class', 'active');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to open a response object to see more details', () => {
|
||||||
|
cy.contains('h2', 'Find pet by ID')
|
||||||
|
.scrollIntoView()
|
||||||
|
.wait(100)
|
||||||
|
.parent()
|
||||||
|
.find('div h3')
|
||||||
|
.should('have.text', 'Responses')
|
||||||
|
.parent()
|
||||||
|
.find('div:first button')
|
||||||
|
.click()
|
||||||
|
.should('have.attr', 'aria-expanded', 'true')
|
||||||
|
.parent()
|
||||||
|
.find('div h5')
|
||||||
|
.then($h5 => $h5[0].firstChild!.nodeValue!.trim())
|
||||||
|
.should('eq', 'Response Schema:');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,7 +56,7 @@ describe('Search', () => {
|
||||||
|
|
||||||
it('should show proper message when no search results are found', () => {
|
it('should show proper message when no search results are found', () => {
|
||||||
getSearchResults().should('not.exist');
|
getSearchResults().should('not.exist');
|
||||||
getSearchInput().type('xzss', {force: true});
|
getSearchInput().type('xzss', { force: true });
|
||||||
getSearchResults().should('exist').should('contain', 'No results found');
|
getSearchResults().should('exist').should('contain', 'No results found');
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
|
|
2423
package-lock.json
generated
2423
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
38
package.json
38
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"version": "2.0.0-rc.59",
|
"version": "2.0.0-rc.63",
|
||||||
"description": "ReDoc",
|
"description": "ReDoc",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -49,30 +49,18 @@
|
||||||
"stats": "webpack --env production --env standalone --json --profile --mode=production > stats.json",
|
"stats": "webpack --env production --env standalone --json --profile --mode=production > stats.json",
|
||||||
"prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"",
|
"prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
|
||||||
"lint": "eslint 'src/**/*.{js,ts,tsx}'",
|
"lint": "eslint 'src/**/*.{js,ts,tsx}' --cache",
|
||||||
"benchmark": "node ./benchmark/benchmark.js",
|
"benchmark": "node ./benchmark/benchmark.js",
|
||||||
"start:demo": "webpack serve --hot --config demo/webpack.config.ts --mode=development",
|
"start:demo": "webpack serve --hot --config demo/webpack.config.ts --mode=development",
|
||||||
"compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs",
|
"compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs",
|
||||||
"build:demo": "webpack --mode=production --config demo/webpack.config.ts",
|
"build:demo": "webpack --mode=production --config demo/webpack.config.ts",
|
||||||
"deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read",
|
"deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read",
|
||||||
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause' --summary",
|
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause;CC-BY-4.0' --summary",
|
||||||
"docker:build": "docker build -f config/docker/Dockerfile -t redoc .",
|
"docker:build": "docker build -f config/docker/Dockerfile -t redoc .",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"pre-commit": "pretty-quick --staged && npm run lint"
|
"pre-commit": "pretty-quick --staged && npm run lint"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.14.3",
|
|
||||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
|
||||||
"@babel/plugin-proposal-decorators": "^7.14.2",
|
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.14.4",
|
|
||||||
"@babel/plugin-syntax-decorators": "^7.12.13",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
||||||
"@babel/plugin-syntax-jsx": "^7.10.4",
|
|
||||||
"@babel/plugin-syntax-typescript": "^7.10.4",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
|
||||||
"@babel/preset-env": "^7.14.4",
|
|
||||||
"@babel/preset-react": "^7.13.13",
|
|
||||||
"@babel/preset-typescript": "^7.13.0",
|
|
||||||
"@cypress/webpack-preprocessor": "^5.9.0",
|
"@cypress/webpack-preprocessor": "^5.9.0",
|
||||||
"@hot-loader/react-dom": "^17.0.1",
|
"@hot-loader/react-dom": "^17.0.1",
|
||||||
"@size-limit/preset-app": "^7.0.4",
|
"@size-limit/preset-app": "^7.0.4",
|
||||||
|
@ -82,10 +70,9 @@
|
||||||
"@types/enzyme-to-json": "^1.5.3",
|
"@types/enzyme-to-json": "^1.5.3",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/json-pointer": "^1.0.30",
|
"@types/json-pointer": "^1.0.30",
|
||||||
"@types/lodash": "^4.14.170",
|
|
||||||
"@types/lunr": "^2.3.3",
|
"@types/lunr": "^2.3.3",
|
||||||
"@types/mark.js": "^8.11.5",
|
"@types/mark.js": "^8.11.5",
|
||||||
"@types/marked": "^1.1.0",
|
"@types/marked": "^4.0.1",
|
||||||
"@types/node": "^15.6.1",
|
"@types/node": "^15.6.1",
|
||||||
"@types/prismjs": "^1.16.5",
|
"@types/prismjs": "^1.16.5",
|
||||||
"@types/prop-types": "^15.7.3",
|
"@types/prop-types": "^15.7.3",
|
||||||
|
@ -100,8 +87,6 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||||
"@typescript-eslint/parser": "^4.26.0",
|
"@typescript-eslint/parser": "^4.26.0",
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
||||||
"babel-loader": "^8.2.2",
|
|
||||||
"babel-plugin-styled-components": "^1.12.0",
|
|
||||||
"beautify-benchmark": "^0.2.4",
|
"beautify-benchmark": "^0.2.4",
|
||||||
"conventional-changelog-cli": "^2.0.34",
|
"conventional-changelog-cli": "^2.0.34",
|
||||||
"copy-webpack-plugin": "^9.0.0",
|
"copy-webpack-plugin": "^9.0.0",
|
||||||
|
@ -111,16 +96,18 @@
|
||||||
"cypress": "^7.4.0",
|
"cypress": "^7.4.0",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-to-json": "^3.6.2",
|
"enzyme-to-json": "^3.6.2",
|
||||||
|
"esbuild-loader": "^2.18.0",
|
||||||
"eslint": "^7.27.0",
|
"eslint": "^7.27.0",
|
||||||
"eslint-plugin-import": "^2.23.4",
|
"eslint-plugin-import": "^2.23.4",
|
||||||
"eslint-plugin-react": "^7.24.0",
|
"eslint-plugin-react": "^7.25.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.2.10",
|
"fork-ts-checker-webpack-plugin": "^6.2.10",
|
||||||
"html-webpack-plugin": "^5.3.1",
|
"html-webpack-plugin": "^5.3.1",
|
||||||
"husky": "^7.0.0",
|
"husky": "^7.0.0",
|
||||||
"jest": "^27.0.3",
|
"jest": "^27.0.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash.noop": "^3.0.1",
|
||||||
"mobx": "^6.3.2",
|
"mobx": "^6.3.2",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
"pretty-quick": "^3.0.0",
|
"pretty-quick": "^3.0.0",
|
||||||
|
@ -131,10 +118,9 @@
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"shelljs": "^0.8.4",
|
"shelljs": "^0.8.4",
|
||||||
"size-limit": "^7.0.4",
|
"size-limit": "^7.0.4",
|
||||||
"style-loader": "^2.0.0",
|
|
||||||
"styled-components": "^5.3.0",
|
"styled-components": "^5.3.0",
|
||||||
"ts-jest": "^27.0.2",
|
"ts-jest": "^27.0.2",
|
||||||
"ts-loader": "^8.0.1",
|
"ts-loader": "^9.2.6",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "~4.1.0",
|
"typescript": "~4.1.0",
|
||||||
"unfetch": "^4.2.0",
|
"unfetch": "^4.2.0",
|
||||||
|
@ -153,7 +139,6 @@
|
||||||
"styled-components": "^4.1.1 || ^5.1.1"
|
"styled-components": "^4.1.1 || ^5.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.14.0",
|
|
||||||
"@redocly/openapi-core": "^1.0.0-beta.54",
|
"@redocly/openapi-core": "^1.0.0-beta.54",
|
||||||
"@redocly/react-dropdown-aria": "^2.0.11",
|
"@redocly/react-dropdown-aria": "^2.0.11",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
|
@ -163,9 +148,9 @@
|
||||||
"json-pointer": "^0.6.1",
|
"json-pointer": "^0.6.1",
|
||||||
"lunr": "^2.3.9",
|
"lunr": "^2.3.9",
|
||||||
"mark.js": "^8.11.1",
|
"mark.js": "^8.11.1",
|
||||||
"marked": "^0.7.0",
|
"marked": "^4.0.10",
|
||||||
"mobx-react": "^7.2.0",
|
"mobx-react": "^7.2.0",
|
||||||
"openapi-sampler": "^1.0.1",
|
"openapi-sampler": "^1.1.1",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"perfect-scrollbar": "^1.5.1",
|
"perfect-scrollbar": "^1.5.1",
|
||||||
"polished": "^4.1.3",
|
"polished": "^4.1.3",
|
||||||
|
@ -174,6 +159,7 @@
|
||||||
"react-tabs": "^3.2.2",
|
"react-tabs": "^3.2.2",
|
||||||
"slugify": "~1.4.7",
|
"slugify": "~1.4.7",
|
||||||
"stickyfill": "^1.1.1",
|
"stickyfill": "^1.1.1",
|
||||||
|
"style-loader": "^3.3.1",
|
||||||
"swagger2openapi": "^7.0.6",
|
"swagger2openapi": "^7.0.6",
|
||||||
"url-template": "^2.0.8"
|
"url-template": "^2.0.8"
|
||||||
},
|
},
|
||||||
|
|
|
@ -96,7 +96,6 @@ export const ConstraintItem = styled(FieldLabel)`
|
||||||
margin: 0 ${theme.spacing.unit}px;
|
margin: 0 ${theme.spacing.unit}px;
|
||||||
padding: 0 ${theme.spacing.unit}px;
|
padding: 0 ${theme.spacing.unit}px;
|
||||||
border: 1px solid ${transparentize(0.9, theme.colors.primary.main)};
|
border: 1px solid ${transparentize(0.9, theme.colors.primary.main)};
|
||||||
font-family: ${theme.typography.code.fontFamily};
|
|
||||||
}`};
|
}`};
|
||||||
& + & {
|
& + & {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
|
@ -43,7 +43,7 @@ export function Link(props: { to: string; className?: string; children?: any })
|
||||||
if (!store) return;
|
if (!store) return;
|
||||||
navigate(store.menu.history, event, props.to);
|
navigate(store.menu.history, event, props.to);
|
||||||
},
|
},
|
||||||
[store],
|
[store, props.to],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!store) return null;
|
if (!store) return null;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar';
|
import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar';
|
||||||
import psStyles from 'perfect-scrollbar/css/perfect-scrollbar.css';
|
|
||||||
|
|
||||||
import { OptionsContext } from '../components/OptionsProvider';
|
import { OptionsContext } from '../components/OptionsProvider';
|
||||||
import styled, { createGlobalStyle } from '../styled-components';
|
import styled, { createGlobalStyle } from '../styled-components';
|
||||||
|
import { IS_BROWSER } from '../utils';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* perfect scrollbar umd bundle uses exports assignment while module uses default export
|
* perfect scrollbar umd bundle uses exports assignment while module uses default export
|
||||||
|
@ -14,7 +14,14 @@ import styled, { createGlobalStyle } from '../styled-components';
|
||||||
const PerfectScrollbarConstructor =
|
const PerfectScrollbarConstructor =
|
||||||
PerfectScrollbarNamespace.default || (PerfectScrollbarNamespace as any as PerfectScrollbarType);
|
PerfectScrollbarNamespace.default || (PerfectScrollbarNamespace as any as PerfectScrollbarType);
|
||||||
|
|
||||||
const PSStyling = createGlobalStyle`${psStyles && psStyles.toString()}`;
|
let psStyles = '';
|
||||||
|
if (IS_BROWSER) {
|
||||||
|
psStyles = require('perfect-scrollbar/css/perfect-scrollbar.css');
|
||||||
|
psStyles = (typeof psStyles.toString === 'function' && psStyles.toString()) || '';
|
||||||
|
psStyles = psStyles === '[object Object]' ? '' : psStyles;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PSStyling = createGlobalStyle`${psStyles}`;
|
||||||
|
|
||||||
const StyledScrollWrapper = styled.div`
|
const StyledScrollWrapper = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -59,7 +66,7 @@ export class PerfectScrollbar extends React.Component<PerfectScrollbarProps> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PSStyling />
|
{psStyles && <PSStyling />}
|
||||||
<StyledScrollWrapper className={`scrollbar-container ${className}`} ref={this.handleRef}>
|
<StyledScrollWrapper className={`scrollbar-container ${className}`} ref={this.handleRef}>
|
||||||
{children}
|
{children}
|
||||||
</StyledScrollWrapper>
|
</StyledScrollWrapper>
|
||||||
|
|
25
src/components/Fields/ArrayItemDetails.tsx
Normal file
25
src/components/Fields/ArrayItemDetails.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { TypeFormat, TypePrefix } from '../../common-elements/fields';
|
||||||
|
import { ConstraintsView } from './FieldContstraints';
|
||||||
|
import { Pattern } from './Pattern';
|
||||||
|
import { SchemaModel } from '../../services';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
|
export function ArrayItemDetails({ schema }: { schema: SchemaModel }) {
|
||||||
|
if (!schema || (schema.type === 'string' && !schema.constraints.length)) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrapper>
|
||||||
|
[ items
|
||||||
|
{schema.displayFormat && <TypeFormat>{` <${schema.displayFormat}> `}</TypeFormat>}
|
||||||
|
<ConstraintsView constraints={schema.constraints} />
|
||||||
|
<Pattern schema={schema} />
|
||||||
|
{schema.items && <ArrayItemDetails schema={schema.items} />} ]
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = styled(TypePrefix)`
|
||||||
|
margin: 0 5px;
|
||||||
|
vertical-align: text-top;
|
||||||
|
`;
|
|
@ -8,7 +8,7 @@ import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
|
||||||
|
|
||||||
export interface EnumValuesProps {
|
export interface EnumValuesProps {
|
||||||
values: string[];
|
values: string[];
|
||||||
type: string | string[];
|
isArrayType: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnumValuesState {
|
export interface EnumValuesState {
|
||||||
|
@ -27,7 +27,7 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { values, type } = this.props;
|
const { values, isArrayType } = this.props;
|
||||||
const { collapsed } = this.state;
|
const { collapsed } = this.state;
|
||||||
|
|
||||||
// TODO: provide context interface in more elegant way
|
// TODO: provide context interface in more elegant way
|
||||||
|
@ -55,7 +55,7 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel>
|
<FieldLabel>
|
||||||
{type === 'array' ? l('enumArray') : ''}{' '}
|
{isArrayType ? l('enumArray') : ''}{' '}
|
||||||
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||||
</FieldLabel>{' '}
|
</FieldLabel>{' '}
|
||||||
{displayedItems.map((value, idx) => {
|
{displayedItems.map((value, idx) => {
|
||||||
|
|
36
src/components/Fields/Examples.tsx
Normal file
36
src/components/Fields/Examples.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { FieldLabel, ExampleValue } from '../../common-elements/fields';
|
||||||
|
import { getSerializedValue } from '../../utils';
|
||||||
|
|
||||||
|
import { l } from '../../services/Labels';
|
||||||
|
import { FieldModel } from '../../services';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
|
export function Examples({ field }: { field: FieldModel }) {
|
||||||
|
if (!field.examples) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FieldLabel> {l('examples')}: </FieldLabel>
|
||||||
|
<ExamplesList>
|
||||||
|
{Object.values(field.examples).map((example, idx) => {
|
||||||
|
return (
|
||||||
|
<li key={idx}>
|
||||||
|
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> -{' '}
|
||||||
|
{example.summary || example.description}
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ExamplesList>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExamplesList = styled.ul`
|
||||||
|
margin-top: 1em;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style-position: inside;
|
||||||
|
`;
|
|
@ -32,7 +32,7 @@ export interface FieldProps extends SchemaOptions {
|
||||||
export class Field extends React.Component<FieldProps> {
|
export class Field extends React.Component<FieldProps> {
|
||||||
toggle = () => {
|
toggle = () => {
|
||||||
if (this.props.field.expanded === undefined && this.props.expandByDefault) {
|
if (this.props.field.expanded === undefined && this.props.expandByDefault) {
|
||||||
this.props.field.expanded = false;
|
this.props.field.collapse();
|
||||||
} else {
|
} else {
|
||||||
this.props.field.toggle();
|
this.props.field.toggle();
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,7 @@ export class Field extends React.Component<FieldProps> {
|
||||||
skipReadOnly={this.props.skipReadOnly}
|
skipReadOnly={this.props.skipReadOnly}
|
||||||
skipWriteOnly={this.props.skipWriteOnly}
|
skipWriteOnly={this.props.skipWriteOnly}
|
||||||
showTitle={this.props.showTitle}
|
showTitle={this.props.showTitle}
|
||||||
|
level={this.props.level}
|
||||||
/>
|
/>
|
||||||
</InnerPropertiesWrap>
|
</InnerPropertiesWrap>
|
||||||
</PropertyCellWithInner>
|
</PropertyCellWithInner>
|
||||||
|
|
|
@ -7,18 +7,18 @@ export interface FieldDetailProps {
|
||||||
raw?: boolean;
|
raw?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
function FieldDetailComponent({ value, label, raw }: FieldDetailProps) {
|
||||||
render() {
|
if (value === undefined) {
|
||||||
if (this.props.value === undefined) {
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = this.props.raw ? String(this.props.value) : JSON.stringify(this.props.value);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<FieldLabel> {this.props.label} </FieldLabel> <ExampleValue>{value}</ExampleValue>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stringifyValue = raw ? String(value) : JSON.stringify(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FieldLabel> {label} </FieldLabel> <ExampleValue>{stringifyValue}</ExampleValue>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FieldDetail = React.memo<FieldDetailProps>(FieldDetailComponent);
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PatternLabel,
|
|
||||||
RecursiveLabel,
|
RecursiveLabel,
|
||||||
TypeFormat,
|
TypeFormat,
|
||||||
TypeName,
|
TypeName,
|
||||||
TypePrefix,
|
TypePrefix,
|
||||||
TypeTitle,
|
TypeTitle,
|
||||||
ToggleButton,
|
|
||||||
FieldLabel,
|
|
||||||
ExampleValue,
|
|
||||||
} from '../../common-elements/fields';
|
} from '../../common-elements/fields';
|
||||||
import { serializeParameterValue } from '../../utils/openapi';
|
import { getSerializedValue } from '../../utils';
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { EnumValues } from './EnumValues';
|
import { EnumValues } from './EnumValues';
|
||||||
import { Extensions } from './Extensions';
|
import { Extensions } from './Extensions';
|
||||||
import { FieldProps } from './Field';
|
import { FieldProps } from './Field';
|
||||||
|
import { Examples } from './Examples';
|
||||||
import { ConstraintsView } from './FieldContstraints';
|
import { ConstraintsView } from './FieldContstraints';
|
||||||
import { FieldDetail } from './FieldDetail';
|
import { FieldDetail } from './FieldDetail';
|
||||||
|
|
||||||
|
@ -24,149 +21,92 @@ import { Badge } from '../../common-elements/';
|
||||||
|
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { FieldModel } from '../../services/models/Field';
|
import { Pattern } from './Pattern';
|
||||||
import styled from '../../styled-components';
|
import { ArrayItemDetails } from './ArrayItemDetails';
|
||||||
|
|
||||||
const MAX_PATTERN_LENGTH = 45;
|
function FieldDetailsComponent(props: FieldProps) {
|
||||||
|
const { enumSkipQuotes, hideSchemaTitles } = React.useContext(OptionsContext);
|
||||||
|
|
||||||
export class FieldDetails extends React.PureComponent<FieldProps, { patternShown: boolean }> {
|
const { showExamples, field, renderDiscriminatorSwitch } = props;
|
||||||
state = {
|
const { schema, description, deprecated, extensions, in: _in, const: _const } = field;
|
||||||
patternShown: false,
|
const isArrayType = schema.type === 'array';
|
||||||
};
|
|
||||||
|
|
||||||
static contextType = OptionsContext;
|
const rawDefault = enumSkipQuotes || _in === 'header'; // having quotes around header field default values is confusing and inappropriate
|
||||||
|
|
||||||
togglePattern = () => {
|
const renderedExamples = React.useMemo<JSX.Element | null>(() => {
|
||||||
this.setState({
|
if (showExamples && (field.example !== undefined || field.examples !== undefined)) {
|
||||||
patternShown: !this.state.patternShown,
|
if (field.examples !== undefined) {
|
||||||
});
|
return <Examples field={field} />;
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
|
||||||
const { patternShown } = this.state;
|
|
||||||
const { enumSkipQuotes, hideSchemaTitles, hideSchemaPattern } = this.context;
|
|
||||||
|
|
||||||
const { schema, description, example, deprecated, examples } = field;
|
|
||||||
|
|
||||||
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
|
|
||||||
|
|
||||||
let renderedExamples: JSX.Element | null = null;
|
|
||||||
|
|
||||||
if (showExamples && (example !== undefined || examples !== undefined)) {
|
|
||||||
if (examples !== undefined) {
|
|
||||||
renderedExamples = <Examples field={field} />;
|
|
||||||
} else {
|
} else {
|
||||||
const label = l('example') + ':';
|
return (
|
||||||
const raw = !!field.in;
|
<FieldDetail
|
||||||
renderedExamples = (
|
label={l('example') + ':'}
|
||||||
<FieldDetail label={label} value={getSerializedValue(field, field.example)} raw={raw} />
|
value={getSerializedValue(field, field.example)}
|
||||||
|
raw={Boolean(field.in)}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<TypePrefix>{schema.typePrefix}</TypePrefix>
|
|
||||||
<TypeName>{schema.displayType}</TypeName>
|
|
||||||
{schema.displayFormat && (
|
|
||||||
<TypeFormat>
|
|
||||||
{' '}
|
|
||||||
<
|
|
||||||
{schema.displayFormat}
|
|
||||||
>{' '}
|
|
||||||
</TypeFormat>
|
|
||||||
)}
|
|
||||||
{schema.contentEncoding && (
|
|
||||||
<TypeFormat>
|
|
||||||
{' '}
|
|
||||||
<
|
|
||||||
{schema.contentEncoding}
|
|
||||||
>{' '}
|
|
||||||
</TypeFormat>
|
|
||||||
)}
|
|
||||||
{schema.contentMediaType && (
|
|
||||||
<TypeFormat>
|
|
||||||
{' '}
|
|
||||||
<
|
|
||||||
{schema.contentMediaType}
|
|
||||||
>{' '}
|
|
||||||
</TypeFormat>
|
|
||||||
)}
|
|
||||||
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
|
||||||
<ConstraintsView constraints={schema.constraints} />
|
|
||||||
{schema.pattern && !hideSchemaPattern && (
|
|
||||||
<>
|
|
||||||
<PatternLabel>
|
|
||||||
{patternShown || schema.pattern.length < MAX_PATTERN_LENGTH
|
|
||||||
? schema.pattern
|
|
||||||
: `${schema.pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
|
|
||||||
</PatternLabel>
|
|
||||||
{schema.pattern.length > MAX_PATTERN_LENGTH && (
|
|
||||||
<ToggleButton onClick={this.togglePattern}>
|
|
||||||
{patternShown ? 'Hide pattern' : 'Show pattern'}
|
|
||||||
</ToggleButton>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
|
||||||
</div>
|
|
||||||
{deprecated && (
|
|
||||||
<div>
|
|
||||||
<Badge type="warning"> {l('deprecated')} </Badge>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
|
||||||
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
|
||||||
{renderedExamples}
|
|
||||||
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
|
||||||
<div>
|
|
||||||
<Markdown compact={true} source={description} />
|
|
||||||
</div>
|
|
||||||
{schema.externalDocs && (
|
|
||||||
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
|
|
||||||
)}
|
|
||||||
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null}
|
|
||||||
{(field.const && <FieldDetail label={l('const') + ':'} value={field.const} />) || null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Examples({ field }: { field: FieldModel }) {
|
|
||||||
if (!field.examples) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}, [field, showExamples]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<FieldLabel> {l('examples')}: </FieldLabel>
|
<div>
|
||||||
<ExamplesList>
|
<TypePrefix>{schema.typePrefix}</TypePrefix>
|
||||||
{Object.values(field.examples).map((example, idx) => {
|
<TypeName>{schema.displayType}</TypeName>
|
||||||
return (
|
{schema.displayFormat && (
|
||||||
<li key={idx}>
|
<TypeFormat>
|
||||||
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> -{' '}
|
{' '}
|
||||||
{example.summary || example.description}
|
<
|
||||||
</li>
|
{schema.displayFormat}
|
||||||
);
|
>{' '}
|
||||||
})}
|
</TypeFormat>
|
||||||
</ExamplesList>
|
)}
|
||||||
</>
|
{schema.contentEncoding && (
|
||||||
|
<TypeFormat>
|
||||||
|
{' '}
|
||||||
|
<
|
||||||
|
{schema.contentEncoding}
|
||||||
|
>{' '}
|
||||||
|
</TypeFormat>
|
||||||
|
)}
|
||||||
|
{schema.contentMediaType && (
|
||||||
|
<TypeFormat>
|
||||||
|
{' '}
|
||||||
|
<
|
||||||
|
{schema.contentMediaType}
|
||||||
|
>{' '}
|
||||||
|
</TypeFormat>
|
||||||
|
)}
|
||||||
|
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||||
|
<ConstraintsView constraints={schema.constraints} />
|
||||||
|
<Pattern schema={schema} />
|
||||||
|
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
||||||
|
{isArrayType && schema.items && <ArrayItemDetails schema={schema.items} />}
|
||||||
|
</div>
|
||||||
|
{deprecated && (
|
||||||
|
<div>
|
||||||
|
<Badge type="warning"> {l('deprecated')} </Badge>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
||||||
|
{!renderDiscriminatorSwitch && (
|
||||||
|
<EnumValues isArrayType={isArrayType} values={schema.enum} />
|
||||||
|
)}{' '}
|
||||||
|
{renderedExamples}
|
||||||
|
<Extensions extensions={{ ...extensions, ...schema.extensions }} />
|
||||||
|
<div>
|
||||||
|
<Markdown compact={true} source={description} />
|
||||||
|
</div>
|
||||||
|
{schema.externalDocs && (
|
||||||
|
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
|
||||||
|
)}
|
||||||
|
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(props)) || null}
|
||||||
|
{(_const && <FieldDetail label={l('const') + ':'} value={_const} />) || null}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSerializedValue(field: FieldModel, example: any) {
|
export const FieldDetails = React.memo<FieldProps>(FieldDetailsComponent);
|
||||||
if (field.in) {
|
|
||||||
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
|
|
||||||
return decodeURIComponent(serializeParameterValue(field, example));
|
|
||||||
} else {
|
|
||||||
return example;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExamplesList = styled.ul`
|
|
||||||
margin-top: 1em;
|
|
||||||
padding-left: 0;
|
|
||||||
list-style-position: inside;
|
|
||||||
`;
|
|
||||||
|
|
33
src/components/Fields/Pattern.tsx
Normal file
33
src/components/Fields/Pattern.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { PatternLabel, ToggleButton } from '../../common-elements/fields';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
import { SchemaModel } from '../../services';
|
||||||
|
|
||||||
|
const MAX_PATTERN_LENGTH = 45;
|
||||||
|
|
||||||
|
export function Pattern(props: { schema: SchemaModel }) {
|
||||||
|
const pattern = props.schema.pattern;
|
||||||
|
const { hideSchemaPattern } = React.useContext(OptionsContext);
|
||||||
|
const [isPatternShown, setIsPatternShown] = React.useState(false);
|
||||||
|
const togglePattern = React.useCallback(
|
||||||
|
() => setIsPatternShown(!isPatternShown),
|
||||||
|
[isPatternShown],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!pattern || hideSchemaPattern) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PatternLabel>
|
||||||
|
{isPatternShown || pattern.length < MAX_PATTERN_LENGTH
|
||||||
|
? pattern
|
||||||
|
: `${pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
|
||||||
|
</PatternLabel>
|
||||||
|
{pattern.length > MAX_PATTERN_LENGTH && (
|
||||||
|
<ToggleButton onClick={togglePattern}>
|
||||||
|
{isPatternShown ? 'Hide pattern' : 'Show pattern'}
|
||||||
|
</ToggleButton>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -79,7 +79,12 @@ export function BodyContent(props: {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{description !== undefined && <Markdown source={description} />}
|
{description !== undefined && <Markdown source={description} />}
|
||||||
<Schema skipReadOnly={isRequestType} key="schema" schema={schema} />
|
<Schema
|
||||||
|
skipReadOnly={isRequestType}
|
||||||
|
skipWriteOnly={!isRequestType}
|
||||||
|
key="schema"
|
||||||
|
schema={schema}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,39 +1,47 @@
|
||||||
import { observer } from 'mobx-react';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import { ResponseModel } from '../../services/models';
|
import type { ResponseModel, MediaTypeModel } from '../../services/models';
|
||||||
import { ResponseDetails } from './ResponseDetails';
|
import { ResponseDetails } from './ResponseDetails';
|
||||||
import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements';
|
import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements';
|
||||||
|
|
||||||
@observer
|
export interface ResponseViewProps {
|
||||||
export class ResponseView extends React.Component<{ response: ResponseModel }> {
|
response: ResponseModel;
|
||||||
toggle = () => {
|
|
||||||
this.props.response.toggle();
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { headers, type, summary, description, code, expanded, content } = this.props.response;
|
|
||||||
const mimes =
|
|
||||||
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined);
|
|
||||||
|
|
||||||
const empty = headers.length === 0 && mimes.length === 0 && !description;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<StyledResponseTitle
|
|
||||||
onClick={this.toggle}
|
|
||||||
type={type}
|
|
||||||
empty={empty}
|
|
||||||
title={summary || ''}
|
|
||||||
code={code}
|
|
||||||
opened={expanded}
|
|
||||||
/>
|
|
||||||
{expanded && !empty && (
|
|
||||||
<ResponseDetailsWrap>
|
|
||||||
<ResponseDetails response={this.props.response} />
|
|
||||||
</ResponseDetailsWrap>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ResponseView = observer(({ response }: ResponseViewProps): React.ReactElement => {
|
||||||
|
const { extensions, headers, type, summary, description, code, expanded, content } = response;
|
||||||
|
|
||||||
|
const mimes = React.useMemo<MediaTypeModel[]>(
|
||||||
|
() =>
|
||||||
|
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined),
|
||||||
|
[content],
|
||||||
|
);
|
||||||
|
|
||||||
|
const empty = React.useMemo<boolean>(
|
||||||
|
() =>
|
||||||
|
(!extensions || Object.keys(extensions).length === 0) &&
|
||||||
|
headers.length === 0 &&
|
||||||
|
mimes.length === 0 &&
|
||||||
|
!description,
|
||||||
|
[extensions, headers, mimes, description],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StyledResponseTitle
|
||||||
|
onClick={() => response.toggle()}
|
||||||
|
type={type}
|
||||||
|
empty={empty}
|
||||||
|
title={summary || ''}
|
||||||
|
code={code}
|
||||||
|
opened={expanded}
|
||||||
|
/>
|
||||||
|
{expanded && !empty && (
|
||||||
|
<ResponseDetailsWrap>
|
||||||
|
<ResponseDetails response={response} />
|
||||||
|
</ResponseDetailsWrap>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -7,15 +7,17 @@ import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||||
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
||||||
import { Schema } from '../Schema';
|
import { Schema } from '../Schema';
|
||||||
|
|
||||||
|
import { Extensions } from '../Fields/Extensions';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { ResponseHeaders } from './ResponseHeaders';
|
import { ResponseHeaders } from './ResponseHeaders';
|
||||||
|
|
||||||
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
|
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
|
||||||
render() {
|
render() {
|
||||||
const { description, headers, content } = this.props.response;
|
const { description, extensions, headers, content } = this.props.response;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{description && <Markdown source={description} />}
|
{description && <Markdown source={description} />}
|
||||||
|
<Extensions extensions={extensions} />
|
||||||
<ResponseHeaders headers={headers} />
|
<ResponseHeaders headers={headers} />
|
||||||
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
|
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
|
||||||
{({ schema }) => {
|
{({ schema }) => {
|
||||||
|
|
|
@ -14,27 +14,34 @@ export interface ResponseTitleProps {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ResponseTitle extends React.PureComponent<ResponseTitleProps> {
|
function ResponseTitleComponent({
|
||||||
render() {
|
title,
|
||||||
const { title, type, empty, code, opened, className, onClick } = this.props;
|
type,
|
||||||
return (
|
empty,
|
||||||
<button
|
code,
|
||||||
className={className}
|
opened,
|
||||||
onClick={(!empty && onClick) || undefined}
|
className,
|
||||||
aria-expanded={opened}
|
onClick,
|
||||||
disabled={empty}
|
}: ResponseTitleProps): React.ReactElement {
|
||||||
>
|
return (
|
||||||
{!empty && (
|
<button
|
||||||
<ShelfIcon
|
className={className}
|
||||||
size={'1.5em'}
|
onClick={(!empty && onClick) || undefined}
|
||||||
color={type}
|
aria-expanded={opened}
|
||||||
direction={opened ? 'down' : 'right'}
|
disabled={empty}
|
||||||
float={'left'}
|
>
|
||||||
/>
|
{!empty && (
|
||||||
)}
|
<ShelfIcon
|
||||||
<Code>{code} </Code>
|
size={'1.5em'}
|
||||||
<Markdown compact={true} inline={true} source={title} />
|
color={type}
|
||||||
</button>
|
direction={opened ? 'down' : 'right'}
|
||||||
);
|
float={'left'}
|
||||||
}
|
/>
|
||||||
|
)}
|
||||||
|
<Code>{code} </Code>
|
||||||
|
<Markdown compact={true} inline={true} source={title} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ResponseTitle = React.memo<ResponseTitleProps>(ResponseTitleComponent);
|
||||||
|
|
|
@ -11,14 +11,12 @@ export const StyledResponseTitle = styled(ResponseTitle)`
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
background-color: #f2f2f2;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
color: ${props => props.theme.colors.responses[props.type].color};
|
color: ${props => props.theme.colors.responses[props.type].color};
|
||||||
background-color: ${props => props.theme.colors.responses[props.type].backgroundColor};
|
background-color: ${props => props.theme.colors.responses[props.type].backgroundColor};
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: auto;
|
outline: auto ${props => props.theme.colors.responses[props.type].color};
|
||||||
outline-color: ${props => props.theme.colors.responses[props.type].color};
|
|
||||||
}
|
}
|
||||||
${props =>
|
${props =>
|
||||||
(props.empty &&
|
(props.empty &&
|
||||||
|
|
|
@ -18,37 +18,38 @@ export interface ObjectSchemaProps extends SchemaProps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
export const ObjectSchema = observer(
|
||||||
export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
({
|
||||||
static contextType = OptionsContext;
|
schema: { fields = [], title },
|
||||||
|
showTitle,
|
||||||
|
discriminator,
|
||||||
|
skipReadOnly,
|
||||||
|
skipWriteOnly,
|
||||||
|
level,
|
||||||
|
}: ObjectSchemaProps) => {
|
||||||
|
const { expandSingleSchemaField, showObjectSchemaExamples, schemaExpansionLevel } =
|
||||||
|
React.useContext(OptionsContext);
|
||||||
|
|
||||||
get parentSchema() {
|
const filteredFields = React.useMemo(
|
||||||
return this.props.discriminator!.parentSchema;
|
() =>
|
||||||
}
|
skipReadOnly || skipWriteOnly
|
||||||
|
? fields.filter(
|
||||||
|
item =>
|
||||||
|
!(
|
||||||
|
(skipReadOnly && item.schema.readOnly) ||
|
||||||
|
(skipWriteOnly && item.schema.writeOnly)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: fields,
|
||||||
|
[skipReadOnly, skipWriteOnly, fields],
|
||||||
|
);
|
||||||
|
|
||||||
render() {
|
const expandByDefault =
|
||||||
const {
|
(expandSingleSchemaField && filteredFields.length === 1) || schemaExpansionLevel >= level!;
|
||||||
schema: { fields = [] },
|
|
||||||
showTitle,
|
|
||||||
discriminator,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const needFilter = this.props.skipReadOnly || this.props.skipWriteOnly;
|
|
||||||
|
|
||||||
const filteredFields = needFilter
|
|
||||||
? fields.filter(item => {
|
|
||||||
return !(
|
|
||||||
(this.props.skipReadOnly && item.schema.readOnly) ||
|
|
||||||
(this.props.skipWriteOnly && item.schema.writeOnly)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: fields;
|
|
||||||
|
|
||||||
const expandByDefault = this.context.expandSingleSchemaField && filteredFields.length === 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PropertiesTable>
|
<PropertiesTable>
|
||||||
{showTitle && <PropertiesTableCaption>{this.props.schema.title}</PropertiesTableCaption>}
|
{showTitle && <PropertiesTableCaption>{title}</PropertiesTableCaption>}
|
||||||
<tbody>
|
<tbody>
|
||||||
{mapWithLast(filteredFields, (field, isLast) => {
|
{mapWithLast(filteredFields, (field, isLast) => {
|
||||||
return (
|
return (
|
||||||
|
@ -58,26 +59,26 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
||||||
field={field}
|
field={field}
|
||||||
expandByDefault={expandByDefault}
|
expandByDefault={expandByDefault}
|
||||||
renderDiscriminatorSwitch={
|
renderDiscriminatorSwitch={
|
||||||
(discriminator &&
|
discriminator?.fieldName === field.name
|
||||||
discriminator.fieldName === field.name &&
|
? () => (
|
||||||
(() => (
|
<DiscriminatorDropdown
|
||||||
<DiscriminatorDropdown
|
parent={discriminator!.parentSchema}
|
||||||
parent={this.parentSchema}
|
enumValues={field.schema.enum}
|
||||||
enumValues={field.schema.enum}
|
/>
|
||||||
/>
|
)
|
||||||
))) ||
|
: undefined
|
||||||
undefined
|
|
||||||
}
|
}
|
||||||
className={field.expanded ? 'expanded' : undefined}
|
className={field.expanded ? 'expanded' : undefined}
|
||||||
showExamples={false}
|
showExamples={showObjectSchemaExamples}
|
||||||
skipReadOnly={this.props.skipReadOnly}
|
skipReadOnly={skipReadOnly}
|
||||||
skipWriteOnly={this.props.skipWriteOnly}
|
skipWriteOnly={skipWriteOnly}
|
||||||
showTitle={this.props.showTitle}
|
showTitle={showTitle}
|
||||||
|
level={level}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</PropertiesTable>
|
</PropertiesTable>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
}
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ export interface SchemaOptions {
|
||||||
showTitle?: boolean;
|
showTitle?: boolean;
|
||||||
skipReadOnly?: boolean;
|
skipReadOnly?: boolean;
|
||||||
skipWriteOnly?: boolean;
|
skipWriteOnly?: boolean;
|
||||||
|
level?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SchemaProps extends SchemaOptions {
|
export interface SchemaProps extends SchemaOptions {
|
||||||
|
@ -25,7 +26,9 @@ export interface SchemaProps extends SchemaOptions {
|
||||||
@observer
|
@observer
|
||||||
export class Schema extends React.Component<Partial<SchemaProps>> {
|
export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
render() {
|
render() {
|
||||||
const { schema } = this.props;
|
const { schema, ...rest } = this.props;
|
||||||
|
const level = (rest.level || 0) + 1;
|
||||||
|
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return <em> Schema not provided </em>;
|
return <em> Schema not provided </em>;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +53,9 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ObjectSchema
|
<ObjectSchema
|
||||||
{...{ ...this.props, schema: oneOf![schema.activeOneOf] }}
|
{...rest}
|
||||||
|
level={level}
|
||||||
|
schema={oneOf![schema.activeOneOf]}
|
||||||
discriminator={{
|
discriminator={{
|
||||||
fieldName: discriminatorProp,
|
fieldName: discriminatorProp,
|
||||||
parentSchema: schema,
|
parentSchema: schema,
|
||||||
|
@ -60,16 +65,16 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oneOf !== undefined) {
|
if (oneOf !== undefined) {
|
||||||
return <OneOfSchema schema={schema} {...this.props} />;
|
return <OneOfSchema schema={schema} {...rest} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const types = Array.isArray(type) ? type : [type];
|
const types = Array.isArray(type) ? type : [type];
|
||||||
if (types.includes('object')) {
|
if (types.includes('object')) {
|
||||||
if (schema.fields?.length) {
|
if (schema.fields?.length) {
|
||||||
return <ObjectSchema {...(this.props as any)} />;
|
return <ObjectSchema {...(this.props as any)} level={level} />;
|
||||||
}
|
}
|
||||||
} else if (types.includes('array')) {
|
} else if (types.includes('array')) {
|
||||||
return <ArraySchema {...(this.props as any)} />;
|
return <ArraySchema {...(this.props as any)} level={level} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybe adjust FieldDetails to accept schema
|
// TODO: maybe adjust FieldDetails to accept schema
|
||||||
|
|
|
@ -56,6 +56,7 @@ export function StoreBuilder(props: StoreBuilderProps) {
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [resolvedSpec, specUrl, options]);
|
}, [resolvedSpec, specUrl, options]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
|
|
@ -26,7 +26,7 @@ describe('Components', () => {
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
const schemaViewElement = shallow(<Schema schema={schema} />).getElement();
|
const schemaViewElement = shallow(<Schema schema={schema} />).getElement();
|
||||||
expect(schemaViewElement.type).toEqual(ObjectSchema);
|
expect(schemaViewElement).toMatchSnapshot();
|
||||||
expect(schemaViewElement.props.discriminator).toBeDefined();
|
expect(schemaViewElement.props.discriminator).toBeDefined();
|
||||||
expect(schemaViewElement.props.discriminator.parentSchema).toBeDefined();
|
expect(schemaViewElement.props.discriminator.parentSchema).toBeDefined();
|
||||||
expect(schemaViewElement.props.discriminator.fieldName).toEqual('type');
|
expect(schemaViewElement.props.discriminator.fieldName).toEqual('type');
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,5 @@
|
||||||
import * as marked from 'marked';
|
import * as React from 'react';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
||||||
import { AppStore } from './AppStore';
|
import { AppStore } from './AppStore';
|
||||||
|
@ -56,10 +57,12 @@ export class MarkdownRenderer {
|
||||||
headings: MarkdownHeading[] = [];
|
headings: MarkdownHeading[] = [];
|
||||||
currentTopHeading: MarkdownHeading;
|
currentTopHeading: MarkdownHeading;
|
||||||
|
|
||||||
|
public parser: marked.Parser; // required initialization, `parser` is used by `marked.Renderer` instance under the hood
|
||||||
private headingEnhanceRenderer: marked.Renderer;
|
private headingEnhanceRenderer: marked.Renderer;
|
||||||
private originalHeadingRule: typeof marked.Renderer.prototype.heading;
|
private originalHeadingRule: typeof marked.Renderer.prototype.heading;
|
||||||
|
|
||||||
constructor(public options?: RedocNormalizedOptions) {
|
constructor(public options?: RedocNormalizedOptions) {
|
||||||
|
this.parser = new marked.Parser();
|
||||||
this.headingEnhanceRenderer = new marked.Renderer();
|
this.headingEnhanceRenderer = new marked.Renderer();
|
||||||
this.originalHeadingRule = this.headingEnhanceRenderer.heading.bind(
|
this.originalHeadingRule = this.headingEnhanceRenderer.heading.bind(
|
||||||
this.headingEnhanceRenderer,
|
this.headingEnhanceRenderer,
|
||||||
|
@ -98,7 +101,7 @@ export class MarkdownRenderer {
|
||||||
|
|
||||||
attachHeadingsDescriptions(rawText: string) {
|
attachHeadingsDescriptions(rawText: string) {
|
||||||
const buildRegexp = (heading: MarkdownHeading) => {
|
const buildRegexp = (heading: MarkdownHeading) => {
|
||||||
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}\s*\n`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const flatHeadings = this.flattenHeadings(this.headings);
|
const flatHeadings = this.flattenHeadings(this.headings);
|
||||||
|
@ -129,7 +132,7 @@ export class MarkdownRenderer {
|
||||||
level: 1 | 2 | 3 | 4 | 5 | 6,
|
level: 1 | 2 | 3 | 4 | 5 | 6,
|
||||||
raw: string,
|
raw: string,
|
||||||
slugger: marked.Slugger,
|
slugger: marked.Slugger,
|
||||||
) => {
|
): string => {
|
||||||
if (level === 1) {
|
if (level === 1) {
|
||||||
this.currentTopHeading = this.saveHeading(text, level);
|
this.currentTopHeading = this.saveHeading(text, level);
|
||||||
} else if (level === 2) {
|
} else if (level === 2) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||||
setSecuritySchemePrefix,
|
setSecuritySchemePrefix,
|
||||||
JsonPointer,
|
JsonPointer,
|
||||||
|
alphabeticallyByProp,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||||
import { GroupModel, OperationModel } from './models';
|
import { GroupModel, OperationModel } from './models';
|
||||||
|
@ -147,9 +148,11 @@ export class MenuBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of OperationsGroup items for the tags of the group or for all tags
|
* Returns array of OperationsGroup items for the tags of the group or for all tags
|
||||||
|
* @param parser
|
||||||
* @param tagsMap tags info returned from `getTagsWithOperations`
|
* @param tagsMap tags info returned from `getTagsWithOperations`
|
||||||
* @param parent parent item
|
* @param parent parent item
|
||||||
* @param group group which this tag belongs to. if not provided gets all tags
|
* @param group group which this tag belongs to. if not provided gets all tags
|
||||||
|
* @param options normalized options
|
||||||
*/
|
*/
|
||||||
static getTagsItems(
|
static getTagsItems(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
|
@ -200,14 +203,21 @@ export class MenuBuilder {
|
||||||
|
|
||||||
res.push(item);
|
res.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.sortTagsAlphabetically) {
|
||||||
|
res.sort(alphabeticallyByProp<GroupModel | OperationModel>('name'));
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of Operation items for the tag
|
* Returns array of Operation items for the tag
|
||||||
|
* @param parser
|
||||||
* @param parent parent OperationsGroup
|
* @param parent parent OperationsGroup
|
||||||
* @param tag tag info returned from `getTagsWithOperations`
|
* @param tag tag info returned from `getTagsWithOperations`
|
||||||
* @param depth items depth
|
* @param depth items depth
|
||||||
|
* @param options - normalized options
|
||||||
*/
|
*/
|
||||||
static getOperationsItems(
|
static getOperationsItems(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
|
@ -226,6 +236,11 @@ export class MenuBuilder {
|
||||||
operation.depth = depth;
|
operation.depth = depth;
|
||||||
res.push(operation);
|
res.push(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.sortOperationsAlphabetically) {
|
||||||
|
res.sort(alphabeticallyByProp<OperationModel>('name'));
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ export interface RedocRawOptions {
|
||||||
requiredPropsFirst?: boolean | string;
|
requiredPropsFirst?: boolean | string;
|
||||||
sortPropsAlphabetically?: boolean | string;
|
sortPropsAlphabetically?: boolean | string;
|
||||||
sortEnumValuesAlphabetically?: boolean | string;
|
sortEnumValuesAlphabetically?: boolean | string;
|
||||||
|
sortOperationsAlphabetically?: boolean | string;
|
||||||
|
sortTagsAlphabetically?: boolean | string;
|
||||||
noAutoAuth?: boolean | string;
|
noAutoAuth?: boolean | string;
|
||||||
nativeScrollbars?: boolean | string;
|
nativeScrollbars?: boolean | string;
|
||||||
pathInMiddlePanel?: boolean | string;
|
pathInMiddlePanel?: boolean | string;
|
||||||
|
@ -35,6 +37,8 @@ export interface RedocRawOptions {
|
||||||
simpleOneOfTypeLabel?: boolean | string;
|
simpleOneOfTypeLabel?: boolean | string;
|
||||||
payloadSampleIdx?: number;
|
payloadSampleIdx?: number;
|
||||||
expandSingleSchemaField?: boolean | string;
|
expandSingleSchemaField?: boolean | string;
|
||||||
|
schemaExpansionLevel?: number | string | 'all';
|
||||||
|
showObjectSchemaExamples?: boolean | string;
|
||||||
sectionsAtTheEnd?: string | string[];
|
sectionsAtTheEnd?: string | string[];
|
||||||
|
|
||||||
unstable_ignoreMimeParameters?: boolean;
|
unstable_ignoreMimeParameters?: boolean;
|
||||||
|
@ -72,6 +76,12 @@ function argValueToNumber(value: number | string | undefined): number | undefine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function argValueToExpandLevel(value?: number | string | undefined, defaultValue = 0): number {
|
||||||
|
if (value === 'all') return Infinity;
|
||||||
|
|
||||||
|
return argValueToNumber(value) || defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
export class RedocNormalizedOptions {
|
export class RedocNormalizedOptions {
|
||||||
static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) {
|
static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) {
|
||||||
if (value === 'all') {
|
if (value === 'all') {
|
||||||
|
@ -212,6 +222,8 @@ export class RedocNormalizedOptions {
|
||||||
requiredPropsFirst: boolean;
|
requiredPropsFirst: boolean;
|
||||||
sortPropsAlphabetically: boolean;
|
sortPropsAlphabetically: boolean;
|
||||||
sortEnumValuesAlphabetically: boolean;
|
sortEnumValuesAlphabetically: boolean;
|
||||||
|
sortOperationsAlphabetically: boolean;
|
||||||
|
sortTagsAlphabetically: boolean;
|
||||||
noAutoAuth: boolean;
|
noAutoAuth: boolean;
|
||||||
nativeScrollbars: boolean;
|
nativeScrollbars: boolean;
|
||||||
pathInMiddlePanel: boolean;
|
pathInMiddlePanel: boolean;
|
||||||
|
@ -229,6 +241,8 @@ export class RedocNormalizedOptions {
|
||||||
simpleOneOfTypeLabel: boolean;
|
simpleOneOfTypeLabel: boolean;
|
||||||
payloadSampleIdx: number;
|
payloadSampleIdx: number;
|
||||||
expandSingleSchemaField: boolean;
|
expandSingleSchemaField: boolean;
|
||||||
|
schemaExpansionLevel: number;
|
||||||
|
showObjectSchemaExamples: boolean;
|
||||||
sectionsAtTheEnd: string[];
|
sectionsAtTheEnd: string[];
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
|
@ -272,6 +286,8 @@ export class RedocNormalizedOptions {
|
||||||
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
||||||
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
|
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
|
||||||
this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically);
|
this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically);
|
||||||
|
this.sortOperationsAlphabetically = argValueToBoolean(raw.sortOperationsAlphabetically);
|
||||||
|
this.sortTagsAlphabetically = argValueToBoolean(raw.sortTagsAlphabetically);
|
||||||
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
|
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
|
||||||
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
||||||
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
||||||
|
@ -291,6 +307,8 @@ export class RedocNormalizedOptions {
|
||||||
this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel);
|
this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel);
|
||||||
this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx);
|
this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx);
|
||||||
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
|
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
|
||||||
|
this.schemaExpansionLevel = argValueToExpandLevel(raw.schemaExpansionLevel);
|
||||||
|
this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples);
|
||||||
this.sectionsAtTheEnd = RedocNormalizedOptions.normalizeSectionsAtTheEnd(raw.sectionsAtTheEnd);
|
this.sectionsAtTheEnd = RedocNormalizedOptions.normalizeSectionsAtTheEnd(raw.sectionsAtTheEnd);
|
||||||
|
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
|
@ -39,5 +39,19 @@ describe('Models', () => {
|
||||||
const resp = new ResponseModel({ ...props, code: 'default', defaultAsError: true });
|
const resp = new ResponseModel({ ...props, code: 'default', defaultAsError: true });
|
||||||
expect(resp.type).toEqual('error');
|
expect(resp.type).toEqual('error');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('ensure extensions are shown if showExtensions is true', () => {
|
||||||
|
const options = new RedocNormalizedOptions({ showExtensions: true });
|
||||||
|
const resp = new ResponseModel({
|
||||||
|
parser,
|
||||||
|
code: 'default',
|
||||||
|
defaultAsError: true,
|
||||||
|
infoOrRef: { 'x-example': { a: 1 } },
|
||||||
|
options,
|
||||||
|
isEvent: true,
|
||||||
|
});
|
||||||
|
expect(Object.keys(resp.extensions).length).toEqual(1);
|
||||||
|
expect(resp.extensions['x-example']).toEqual({ a: 1 });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,7 +41,7 @@ const DEFAULT_SERIALIZATION: Record<
|
||||||
*/
|
*/
|
||||||
export class FieldModel {
|
export class FieldModel {
|
||||||
@observable
|
@observable
|
||||||
expanded: boolean | undefined = false;
|
expanded: boolean | undefined = undefined;
|
||||||
|
|
||||||
schema: SchemaModel;
|
schema: SchemaModel;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -120,4 +120,14 @@ export class FieldModel {
|
||||||
toggle() {
|
toggle() {
|
||||||
this.expanded = !this.expanded;
|
this.expanded = !this.expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
collapse(): void {
|
||||||
|
this.expanded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
expand(): void {
|
||||||
|
this.expanded = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,7 @@ export class RequestBodyModel {
|
||||||
required: boolean;
|
required: boolean;
|
||||||
content?: MediaContentModel;
|
content?: MediaContentModel;
|
||||||
|
|
||||||
constructor(props: RequestBodyProps) {
|
constructor({ parser, infoOrRef, options, isEvent }: RequestBodyProps) {
|
||||||
const { parser, infoOrRef, options, isEvent } = props;
|
|
||||||
const isRequest = !isEvent;
|
const isRequest = !isEvent;
|
||||||
const info = parser.deref(infoOrRef);
|
const info = parser.deref(infoOrRef);
|
||||||
this.description = info.description || '';
|
this.description = info.description || '';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { action, observable, makeObservable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIResponse, Referenced } from '../../types';
|
import { OpenAPIResponse, Referenced } from '../../types';
|
||||||
|
|
||||||
import { getStatusCodeType } from '../../utils';
|
import { getStatusCodeType, extractExtensions } from '../../utils';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import { FieldModel } from './Field';
|
import { FieldModel } from './Field';
|
||||||
|
@ -27,10 +27,16 @@ export class ResponseModel {
|
||||||
description: string;
|
description: string;
|
||||||
type: string;
|
type: string;
|
||||||
headers: FieldModel[] = [];
|
headers: FieldModel[] = [];
|
||||||
|
extensions: Record<string, any>;
|
||||||
|
|
||||||
constructor(props: ResponseProps) {
|
constructor({
|
||||||
const { parser, code, defaultAsError, infoOrRef, options, isEvent } = props;
|
parser,
|
||||||
const isRequest = isEvent ? true : false;
|
code,
|
||||||
|
defaultAsError,
|
||||||
|
infoOrRef,
|
||||||
|
options,
|
||||||
|
isEvent: isRequest,
|
||||||
|
}: ResponseProps) {
|
||||||
makeObservable(this);
|
makeObservable(this);
|
||||||
|
|
||||||
this.expanded = options.expandResponses === 'all' || options.expandResponses[code];
|
this.expanded = options.expandResponses === 'all' || options.expandResponses[code];
|
||||||
|
@ -59,6 +65,10 @@ export class ResponseModel {
|
||||||
return new FieldModel(parser, { ...header, name }, '', options);
|
return new FieldModel(parser, { ...header, name }, '', options);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.showExtensions) {
|
||||||
|
this.extensions = extractExtensions(info, options.showExtensions);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -192,7 +192,7 @@ export interface OpenAPIRequestBody {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenAPIResponses {
|
export interface OpenAPIResponses {
|
||||||
[code: string]: OpenAPIResponse;
|
[code: string]: Referenced<OpenAPIResponse>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenAPIResponse
|
export interface OpenAPIResponse
|
||||||
|
|
50
src/utils/__tests__/object.test.ts
Normal file
50
src/utils/__tests__/object.test.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { objectHas, objectSet } from '../object';
|
||||||
|
|
||||||
|
describe('object utils', () => {
|
||||||
|
let obj;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
obj = {
|
||||||
|
a: {
|
||||||
|
b: {
|
||||||
|
c: {
|
||||||
|
d: 'd',
|
||||||
|
},
|
||||||
|
c1: 'c1',
|
||||||
|
},
|
||||||
|
b1: 'b1',
|
||||||
|
},
|
||||||
|
a1: 'a1',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('objectHas function', () => {
|
||||||
|
it('should check if the obj has path as string', () => {
|
||||||
|
expect(objectHas(obj, 'a.b.c')).toBeTruthy();
|
||||||
|
expect(objectHas(obj, 'a.b.c1')).toBeTruthy();
|
||||||
|
expect(objectHas(obj, 'a.b.c.d')).toBeTruthy();
|
||||||
|
expect(objectHas(obj, 'a.b.c1.d')).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check if the obj has path as array', () => {
|
||||||
|
expect(objectHas(obj, ['a', 'b', 'c'])).toBeTruthy();
|
||||||
|
expect(objectHas(obj, ['a', 'b', 'c1'])).toBeTruthy();
|
||||||
|
expect(objectHas(obj, ['a', 'b', 'c', 'd'])).toBeTruthy();
|
||||||
|
expect(objectHas(obj, ['a', 'b', 'c1', 'd'])).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('objectSet function', () => {
|
||||||
|
it('should set value by path as string', () => {
|
||||||
|
expect(objectHas(obj, 'a.b.c1.d')).toBeFalsy();
|
||||||
|
objectSet(obj, 'a.b.c1', { d: 'd' });
|
||||||
|
expect(objectHas(obj, 'a.b.c1.d')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set value by path as array', () => {
|
||||||
|
expect(objectHas(obj, ['a', 'b', 'c1', 'd'])).toBeFalsy();
|
||||||
|
objectSet(obj, ['a', 'b', 'c1'], { d: 'd' });
|
||||||
|
expect(objectHas(obj, ['a', 'b', 'c1', 'd'])).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -12,6 +12,7 @@ import {
|
||||||
sortByRequired,
|
sortByRequired,
|
||||||
humanizeNumberRange,
|
humanizeNumberRange,
|
||||||
getContentWithLegacyExamples,
|
getContentWithLegacyExamples,
|
||||||
|
getDefinitionName,
|
||||||
} from '../';
|
} from '../';
|
||||||
|
|
||||||
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||||
|
@ -1269,4 +1270,14 @@ describe('Utils', () => {
|
||||||
expect(content['text/plain']).toStrictEqual(info.content['text/plain']);
|
expect(content['text/plain']).toStrictEqual(info.content['text/plain']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getDefinitionName', () => {
|
||||||
|
test('should return the name if pointer match regex', () => {
|
||||||
|
expect(getDefinitionName('#/components/schemas/Call')).toEqual('Call');
|
||||||
|
});
|
||||||
|
test("should return the `undefined` if pointer not match regex or it's absent", () => {
|
||||||
|
expect(getDefinitionName('#/test/path/Call')).toBeUndefined();
|
||||||
|
expect(getDefinitionName()).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,3 +8,4 @@ export * from './dom';
|
||||||
export * from './decorators';
|
export * from './decorators';
|
||||||
export * from './debug';
|
export * from './debug';
|
||||||
export * from './memoize';
|
export * from './memoize';
|
||||||
|
export * from './sort';
|
||||||
|
|
28
src/utils/object.ts
Normal file
28
src/utils/object.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
export function objectHas(object: GenericObject, path: string | Array<string>): boolean {
|
||||||
|
let _path = <Array<string>>path;
|
||||||
|
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
_path = path.split('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return _path.every((key: string) => {
|
||||||
|
if (typeof object != 'object' || object === null || !(key in object)) return false;
|
||||||
|
object = object[key];
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function objectSet(object: GenericObject, path: string | Array<string>, value: any): void {
|
||||||
|
let _path = <Array<string>>path;
|
||||||
|
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
_path = path.split('.');
|
||||||
|
}
|
||||||
|
const limit = _path.length - 1;
|
||||||
|
for (let i = 0; i < limit; ++i) {
|
||||||
|
const key = _path[i];
|
||||||
|
object = object[key] ?? (object[key] = {});
|
||||||
|
}
|
||||||
|
const key = _path[limit];
|
||||||
|
object[key] = value;
|
||||||
|
}
|
|
@ -368,6 +368,15 @@ export function serializeParameterValue(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSerializedValue(field: FieldModel, example: any) {
|
||||||
|
if (field.in) {
|
||||||
|
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
|
||||||
|
return decodeURIComponent(serializeParameterValue(field, example));
|
||||||
|
} else {
|
||||||
|
return example;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function langFromMime(contentType: string): string {
|
export function langFromMime(contentType: string): string {
|
||||||
if (contentType.search(/xml/i) !== -1) {
|
if (contentType.search(/xml/i) !== -1) {
|
||||||
return 'xml';
|
return 'xml';
|
||||||
|
@ -375,14 +384,15 @@ export function langFromMime(contentType: string): string {
|
||||||
return 'clike';
|
return 'clike';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFINITION_NAME_REGEX = /^#\/components\/(schemas|pathItems)\/([^/]+)$/;
|
||||||
|
|
||||||
export function isNamedDefinition(pointer?: string): boolean {
|
export function isNamedDefinition(pointer?: string): boolean {
|
||||||
return /^#\/components\/(schemas|pathItems)\/[^\/]+$/.test(pointer || '');
|
return DEFINITION_NAME_REGEX.test(pointer || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefinitionName(pointer?: string): string | undefined {
|
export function getDefinitionName(pointer?: string): string | undefined {
|
||||||
if (!pointer) return undefined;
|
const [name] = pointer?.match(DEFINITION_NAME_REGEX)?.reverse() || [];
|
||||||
const match = pointer.match(/^#\/components\/(schemas|pathItems)\/([^\/]+)$/);
|
return name;
|
||||||
return match === null ? undefined : match[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanizeMultipleOfConstraint(multipleOf: number | undefined): string | undefined {
|
function humanizeMultipleOfConstraint(multipleOf: number | undefined): string | undefined {
|
||||||
|
|
21
src/utils/sort.ts
Normal file
21
src/utils/sort.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Function that returns a comparator for sorting objects by some specific key alphabetically.
|
||||||
|
*
|
||||||
|
* @param {String} property key of the object to sort, if starts from `-` - reverse
|
||||||
|
*/
|
||||||
|
export function alphabeticallyByProp<T>(property: string): (a: T, b: T) => number {
|
||||||
|
let sortOrder = 1;
|
||||||
|
|
||||||
|
if (property[0] === '-') {
|
||||||
|
sortOrder = -1;
|
||||||
|
property = property.substr(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (a: T, b: T) => {
|
||||||
|
if (sortOrder == -1) {
|
||||||
|
return b[property].localeCompare(a[property]);
|
||||||
|
} else {
|
||||||
|
return a[property].localeCompare(b[property]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
/* tslint:disable:no-implicit-dependencies */
|
import { objectHas, objectSet } from './object';
|
||||||
|
|
||||||
import { has, set } from 'lodash';
|
|
||||||
|
|
||||||
function traverseComponent(root, fn) {
|
function traverseComponent(root, fn) {
|
||||||
if (!root) {
|
if (!root) {
|
||||||
|
@ -20,8 +18,8 @@ export function filterPropsDeep<T extends object>(component: T, paths: string[])
|
||||||
traverseComponent(component, comp => {
|
traverseComponent(component, comp => {
|
||||||
if (comp.props) {
|
if (comp.props) {
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
if (has(comp.props, path)) {
|
if (objectHas(comp.props, path)) {
|
||||||
set(comp.props, path, '<<<filtered>>>');
|
objectSet(comp.props, path, '<<<filtered>>>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,27 +13,12 @@
|
||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
"pretty": true,
|
"pretty": true,
|
||||||
"lib": [
|
"lib": ["es2015", "es2016", "es2017", "dom", "WebWorker.ImportScripts"],
|
||||||
"es2015",
|
|
||||||
"es2016",
|
|
||||||
"es2017",
|
|
||||||
"dom",
|
|
||||||
"WebWorker.ImportScripts"
|
|
||||||
],
|
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
"types": [
|
"types": ["webpack", "webpack-env", "jest"]
|
||||||
"webpack",
|
|
||||||
"webpack-env",
|
|
||||||
"jest"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"exclude": [
|
"exclude": ["node_modules", ".tmp", "lib", "e2e/**"],
|
||||||
"node_modules",
|
|
||||||
".tmp",
|
|
||||||
"lib",
|
|
||||||
"e2e/**"
|
|
||||||
],
|
|
||||||
"include": [
|
"include": [
|
||||||
"cli/index.ts",
|
"cli/index.ts",
|
||||||
"./custom.d.ts",
|
"./custom.d.ts",
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
import * as webpack from 'webpack';
|
import * as webpack from 'webpack';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { getBabelLoader, webpackIgnore } from './config/webpack-utils';
|
import { webpackIgnore } from './config/webpack-utils';
|
||||||
|
|
||||||
const nodeExternals = require('webpack-node-externals')({
|
const nodeExternals = require('webpack-node-externals')({
|
||||||
// bundle in modules that need transpiling + non-js (e.g. css)
|
// bundle in modules that need transpiling + non-js (e.g. css)
|
||||||
|
@ -32,10 +32,14 @@ const BANNER = `ReDoc - OpenAPI/Swagger-generated API Reference Documentation
|
||||||
Version: ${VERSION}
|
Version: ${VERSION}
|
||||||
Repo: https://github.com/Redocly/redoc`;
|
Repo: https://github.com/Redocly/redoc`;
|
||||||
|
|
||||||
export default (env: { standalone?: boolean, browser?: boolean } = {}) => ({
|
export default (env: { standalone?: boolean; browser?: boolean } = {}) => ({
|
||||||
entry: env.standalone ? ['./src/polyfills.ts', './src/standalone.tsx'] : './src/index.ts',
|
entry: env.standalone ? ['./src/polyfills.ts', './src/standalone.tsx'] : './src/index.ts',
|
||||||
output: {
|
output: {
|
||||||
filename: env.standalone ? 'redoc.standalone.js' : env.browser ? 'redoc.browser.lib.js' : 'redoc.lib.js',
|
filename: env.standalone
|
||||||
|
? 'redoc.standalone.js'
|
||||||
|
: env.browser
|
||||||
|
? 'redoc.browser.lib.js'
|
||||||
|
: 'redoc.lib.js',
|
||||||
path: path.join(__dirname, '/bundles'),
|
path: path.join(__dirname, '/bundles'),
|
||||||
library: 'Redoc',
|
library: 'Redoc',
|
||||||
libraryTarget: 'umd',
|
libraryTarget: 'umd',
|
||||||
|
@ -46,11 +50,12 @@ export default (env: { standalone?: boolean, browser?: boolean } = {}) => ({
|
||||||
extensions: ['.ts', '.tsx', '.js', '.mjs', '.json'],
|
extensions: ['.ts', '.tsx', '.js', '.mjs', '.json'],
|
||||||
fallback: {
|
fallback: {
|
||||||
path: require.resolve('path-browserify'),
|
path: require.resolve('path-browserify'),
|
||||||
|
buffer: require.resolve('buffer'),
|
||||||
http: false,
|
http: false,
|
||||||
fs: path.resolve(__dirname, 'src/empty.js'),
|
fs: path.resolve(__dirname, 'src/empty.js'),
|
||||||
os: path.resolve(__dirname, 'src/empty.js'),
|
os: path.resolve(__dirname, 'src/empty.js'),
|
||||||
tty: path.resolve(__dirname, 'src/empty.js'),
|
tty: path.resolve(__dirname, 'src/empty.js'),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
performance: false,
|
performance: false,
|
||||||
externalsPresets: env.standalone || env.browser ? {} : { node: true },
|
externalsPresets: env.standalone || env.browser ? {} : { node: true },
|
||||||
|
@ -74,32 +79,27 @@ export default (env: { standalone?: boolean, browser?: boolean } = {}) => ({
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.(tsx?|[cm]?js)$/,
|
test: /\.(tsx?|[cm]?js)$/,
|
||||||
use: [getBabelLoader({useBuiltIns: !!env.standalone})],
|
loader: 'esbuild-loader',
|
||||||
exclude: {
|
options: {
|
||||||
and: [/node_modules/],
|
loader: 'tsx',
|
||||||
not: {
|
target: 'es2015',
|
||||||
or: [
|
tsconfigRaw: require('./tsconfig.json'),
|
||||||
/swagger2openapi/,
|
|
||||||
/reftools/,
|
|
||||||
/openapi-sampler/,
|
|
||||||
/mobx/,
|
|
||||||
/oas-resolver/,
|
|
||||||
/oas-kit-common/,
|
|
||||||
/oas-schema-walker/,
|
|
||||||
/\@redocly\/openapi-core/,
|
|
||||||
/colorette/,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
exclude: [/node_modules/],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: {
|
use: [
|
||||||
loader: 'css-loader',
|
'style-loader',
|
||||||
options: {
|
'css-loader',
|
||||||
sourceMap: false,
|
{
|
||||||
|
loader: 'esbuild-loader',
|
||||||
|
options: {
|
||||||
|
loader: 'css',
|
||||||
|
minify: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -113,6 +113,9 @@ export default (env: { standalone?: boolean, browser?: boolean } = {}) => ({
|
||||||
}),
|
}),
|
||||||
new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
|
new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
|
||||||
new webpack.BannerPlugin(BANNER),
|
new webpack.BannerPlugin(BANNER),
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
Buffer: ['buffer', 'Buffer'],
|
||||||
|
}),
|
||||||
webpackIgnore(/js-yaml\/dumper\.js$/),
|
webpackIgnore(/js-yaml\/dumper\.js$/),
|
||||||
env.standalone ? webpackIgnore(/^\.\/SearchWorker\.worker$/) : undefined,
|
env.standalone ? webpackIgnore(/^\.\/SearchWorker\.worker$/) : undefined,
|
||||||
].filter(Boolean),
|
].filter(Boolean),
|
||||||
|
|
Loading…
Reference in New Issue
Block a user