mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 06:04:56 +03:00
Merge remote-tracking branch 'source/master' into develop
# Conflicts: # .gitignore # package.json # src/components/ApiInfo/ApiInfo.tsx # src/components/SearchBox/styled.elements.tsx # src/components/SideMenu/SideMenu.tsx # src/services/RedocNormalizedOptions.ts # src/theme.ts
This commit is contained in:
commit
5af9c204e3
|
@ -9,4 +9,4 @@
|
||||||
!webpack.config.ts
|
!webpack.config.ts
|
||||||
|
|
||||||
!package.json
|
!package.json
|
||||||
!yarn.lock
|
!package-lock.json
|
||||||
|
|
49
.eslintrc.js
Normal file
49
.eslintrc.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
},
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
extends: ['plugin:react/recommended', 'plugin:@typescript-eslint/recommended'],
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
sourceType: 'module',
|
||||||
|
createDefaultProgram: true,
|
||||||
|
ecmaFeatures: {
|
||||||
|
jsx: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint', 'import'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
'@typescript-eslint/no-use-before-define': 'off',
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/no-inferrable-types': 'off',
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||||
|
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
|
||||||
|
'import/no-extraneous-dependencies': 'error',
|
||||||
|
'import/no-internal-modules': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allow: [
|
||||||
|
'prismjs/**',
|
||||||
|
'perfect-scrollbar/**',
|
||||||
|
'react-dom/*',
|
||||||
|
'core-js/**',
|
||||||
|
'memoize-one/**',
|
||||||
|
'unfetch/**',
|
||||||
|
'raf/polyfill',
|
||||||
|
'**/fixtures/**', // for tests
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
38
.github/CONTRIBUTING.md
vendored
38
.github/CONTRIBUTING.md
vendored
|
@ -15,56 +15,56 @@ Hi! We're really excited that you are interested in contributing to ReDoc. Befor
|
||||||
Before submitting a pull request, please make sure the following is done:
|
Before submitting a pull request, please make sure the following is done:
|
||||||
|
|
||||||
1. Fork the repository and create your branch from master.
|
1. Fork the repository and create your branch from master.
|
||||||
2. Run `yarn` in the repository root.
|
2. Run `npm install` in the repository root.
|
||||||
3. If you’ve fixed a bug or added code that should be tested, add tests!
|
3. If you’ve fixed a bug or added code that should be tested, add tests!
|
||||||
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development.
|
4. Ensure the test suite passes (`npm test`). Tip: `npm test -- --watch TestName` is helpful in development.
|
||||||
5. Format your code with prettier (`yarn prettier`).
|
5. Format your code with prettier (`npm run prettier`).
|
||||||
|
|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
You will need [Node.js](http://nodejs.org) at `v8.0.0+` and [Yarn](https://yarnpkg.com/en/) at `v1.2.0+`
|
You will need [Node.js](http://nodejs.org) at `v12.0.0+`.
|
||||||
|
|
||||||
After cloning the repo, run:
|
After cloning the repo, run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ yarn install # or npm
|
$ npm install # or npm
|
||||||
```
|
```
|
||||||
|
|
||||||
### Commonly used NPM scripts
|
### Commonly used NPM scripts
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
# dev-server, watch and auto reload playground
|
# dev-server, watch and auto reload playground
|
||||||
$ yarn start
|
$ npm start
|
||||||
|
|
||||||
# start playground app in production environement
|
# start playground app in production environment
|
||||||
$ yarn start:prod
|
$ npm run start:prod
|
||||||
|
|
||||||
# runt tslint
|
# runt tslint
|
||||||
$ yarn lint
|
$ npm run lint
|
||||||
|
|
||||||
# try autofix tslint issues
|
# try autofix tslint issues
|
||||||
$ yarn lint --fix
|
$ npm run lint -- --fix
|
||||||
|
|
||||||
# run unit tests
|
# run unit tests
|
||||||
$ yarn unit
|
$ npm run unit
|
||||||
|
|
||||||
# run e2e tests
|
# run e2e tests
|
||||||
$ yarn e2e
|
$ npm run e2e
|
||||||
|
|
||||||
# open cypress UI to debug e2e test
|
# open cypress UI to debug e2e test
|
||||||
$ yarn cy:open
|
$ npm run cy:open
|
||||||
|
|
||||||
# run the full test suite, include linting / unit / e2e
|
# run the full test suite, include linting / unit / e2e
|
||||||
$ yarn test
|
$ npm test
|
||||||
|
|
||||||
# prepare bundles
|
# prepare bundles
|
||||||
$ yarn bundle
|
$ npm run bundle
|
||||||
|
|
||||||
# format the code using prettier
|
# format the code using prettier
|
||||||
$ yarn prettier
|
$ npm run prettier
|
||||||
|
|
||||||
# auto-generate changelog
|
# auto-generate changelog
|
||||||
$ yarn changelog
|
$ npm run changelog
|
||||||
```
|
```
|
||||||
|
|
||||||
There are some other scripts available in the `scripts` section of the `package.json` file.
|
There are some other scripts available in the `scripts` section of the `package.json` file.
|
||||||
|
@ -84,11 +84,11 @@ There are some other scripts available in the `scripts` section of the `package.
|
||||||
|
|
||||||
- **`src`**: contains the source code. The codebase is written in Typescript. CSS styles are managed with [Styled components](https://www.styled-components.com/). State is managed by [MobX](https://github.com/mobxjs/mobx)
|
- **`src`**: contains the source code. The codebase is written in Typescript. CSS styles are managed with [Styled components](https://www.styled-components.com/). State is managed by [MobX](https://github.com/mobxjs/mobx)
|
||||||
|
|
||||||
- **`src/common-elements`**: containts common Styled elements or components used in multiple places
|
- **`src/common-elements`**: contains common Styled elements or components used in multiple places
|
||||||
- **`src/components`**: contains main visual components
|
- **`src/components`**: contains main visual components
|
||||||
- **`src/services`**: contains different services used by ReDoc including MobX stores
|
- **`src/services`**: contains different services used by ReDoc including MobX stores
|
||||||
- **`src/services/models`**: contains classes for OpenAPI entities (e.g. Response, Operations, etc)
|
- **`src/services/models`**: contains classes for OpenAPI entities (e.g. Response, Operations, etc)
|
||||||
- **`src/types`**: contains extra typescript typings including OpenAPI doc typings
|
- **`src/types`**: contains extra typescript typings including OpenAPI doc typings
|
||||||
- **`src/utils`**: utility functions
|
- **`src/utils`**: utility functions
|
||||||
- **`src/styled-components.ts`**: - reexprots styled-components with proper typescript annotations using theme
|
- **`src/styled-components.ts`**: - reexports styled-components with proper typescript annotations using theme
|
||||||
- **`src/theme.ts`**: - default theme (colors, fonts, etc) used by all the components
|
- **`src/theme.ts`**: - default theme (colors, fonts, etc) used by all the components
|
||||||
|
|
20
.github/workflows/unit-tests.yml
vendored
Normal file
20
.github/workflows/unit-tests.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
name: Unit Tests
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10.x
|
||||||
|
- name: npm install, build, and test
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run bundle
|
||||||
|
npm test
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -34,7 +34,7 @@ cli/index.js
|
||||||
/coverage
|
/coverage
|
||||||
.ghpages-tmp
|
.ghpages-tmp
|
||||||
stats.json
|
stats.json
|
||||||
/package-lock.json
|
yarn.lock
|
||||||
|
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
|
30
.travis.yml
30
.travis.yml
|
@ -1,19 +1,24 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- '8'
|
- '10'
|
||||||
cache: yarn
|
cache:
|
||||||
|
directories:
|
||||||
|
- "~/.cache"
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- GH_REF: github.com/Redocly/redoc.git
|
- GH_REF: github.com/Redocly/redoc.git
|
||||||
- GIT_AUTHOR_EMAIL: redoc-bot@users.noreply.github.com
|
- GIT_AUTHOR_EMAIL: redoc-bot@users.noreply.github.com
|
||||||
- GIT_AUTHOR_NAME: RedocBot
|
- GIT_AUTHOR_NAME: RedocBot
|
||||||
- secure: H2GClDJ7TEQaWgnk8d2fIVDpLwG3rTmN8TalUzrCqXGoG6ylCVmlwzKLgfPPWrVgSA7QTdfNV0ab7c2KyPoZBinHmeGMSuKNLVbhOXRc2VFxTBntBTuyJhfCgQEpUKvJesJUuv5RuBn//wC7VZcVVNc06TZDEe8+aDVYQuCRMXZJ4X3e6nSZ64QX2veyVcr+TjnRsZPkeBVcK9hngvuaxLb/hbJ85CvjiseZRt47PGIcrEpMn9n2GMw1m0fNnPoN+MBTSCnIklTmdjPG7t4GUSLmD6H0lNLdXuehYqmQAHgYrLec1aiFlV57QaDoDZrq2hSf4vDmCB/FVydGhD5JunI67pujoV2OnD1V80eUZhYNWOYsJ2Nfp4NxgXsPUcE6zWLYsLfktMPZADhOXInQRACt1cnx8zMYKLnch1RY/ZqjSg0nPtRjLzQ0lNsw5leixvBdBnMjxYHVyAWVwg8WiJMaLO9vog2Qnxg1NTacHO2CsOmm2rw6stpg7ndp/+nOleRlfUKggjt0Tn3FjwCIXeGup2P2EBa+WW2YMAaoMFofYviR5vRlKBgdKo9fsAruaO1r6nm2EdAjOlniyw92bEfU/qOey1nVp/oK2S82uT5In8KB7vl6rF3ak7WAsT9Q5vZUhsrG+eE4PVyIyWNBhs4A7pSwZGHDR/MYtp0E2ug=
|
- secure: H2GClDJ7TEQaWgnk8d2fIVDpLwG3rTmN8TalUzrCqXGoG6ylCVmlwzKLgfPPWrVgSA7QTdfNV0ab7c2KyPoZBinHmeGMSuKNLVbhOXRc2VFxTBntBTuyJhfCgQEpUKvJesJUuv5RuBn//wC7VZcVVNc06TZDEe8+aDVYQuCRMXZJ4X3e6nSZ64QX2veyVcr+TjnRsZPkeBVcK9hngvuaxLb/hbJ85CvjiseZRt47PGIcrEpMn9n2GMw1m0fNnPoN+MBTSCnIklTmdjPG7t4GUSLmD6H0lNLdXuehYqmQAHgYrLec1aiFlV57QaDoDZrq2hSf4vDmCB/FVydGhD5JunI67pujoV2OnD1V80eUZhYNWOYsJ2Nfp4NxgXsPUcE6zWLYsLfktMPZADhOXInQRACt1cnx8zMYKLnch1RY/ZqjSg0nPtRjLzQ0lNsw5leixvBdBnMjxYHVyAWVwg8WiJMaLO9vog2Qnxg1NTacHO2CsOmm2rw6stpg7ndp/+nOleRlfUKggjt0Tn3FjwCIXeGup2P2EBa+WW2YMAaoMFofYviR5vRlKBgdKo9fsAruaO1r6nm2EdAjOlniyw92bEfU/qOey1nVp/oK2S82uT5In8KB7vl6rF3ak7WAsT9Q5vZUhsrG+eE4PVyIyWNBhs4A7pSwZGHDR/MYtp0E2ug=
|
||||||
- secure: apiavCfCQngL9Een1m7MIXMf3bqO3rY4YY59TMBl/yFKi80CEsHPHhgVUkl6hC+aM5PeBt/vgjh37rHMX31j/pcSZ4Z8SO/4Bwr36iHfhSxSEuAQog8P07qWqH7wYYWGIVmF682stgl0fYF+GN92sx/6edFVzsWVECf2G7imtICKSTbhKGm3Dhn2JwGnhD7eyfgZ33omgiaswumdu0xABoXDfqSZR+16fC4Ap5rhv3fXO9ndvRNy1STn376nT+my6e86UrQL4aS/S+HNHgIe1BUs+5cOp6Jgw6t0ie7phY0EAiECsRxy9K4e3Dctv9m6+Wma4+vy65MS0zGyrqey6oyV4l827sCOjrD1qcqc9bX6FlMSouVoNfE4ZjINNAbgigTaiLSoDSPcf5I5smkkM2ezzFOMSZwZxNdaNL2LKb97vc8m/ZUkv0sKZyT7oqVL7aJweEivsSHj5l2KR8Z7XrVB1y2eI6GvyTSa/d+CL4dSRzjh8+IRN047YBrdTKD5IkdT0upfoBu14WPUfFmLKxX+iMCslXRWb6kwojhrWNYmZvL65KRAzJ6+eIPDG/W5QUOpYyYT77bLlBQjVo6NmVvl9v3HMECq9CHH0ivKFBGPiKMOx7cJkTax3FuyznOW2WCXB9kTb5Zk9toaiNlSp9L6ll/h2Eyxa6n6sWUgmmM=
|
- secure: apiavCfCQngL9Een1m7MIXMf3bqO3rY4YY59TMBl/yFKi80CEsHPHhgVUkl6hC+aM5PeBt/vgjh37rHMX31j/pcSZ4Z8SO/4Bwr36iHfhSxSEuAQog8P07qWqH7wYYWGIVmF682stgl0fYF+GN92sx/6edFVzsWVECf2G7imtICKSTbhKGm3Dhn2JwGnhD7eyfgZ33omgiaswumdu0xABoXDfqSZR+16fC4Ap5rhv3fXO9ndvRNy1STn376nT+my6e86UrQL4aS/S+HNHgIe1BUs+5cOp6Jgw6t0ie7phY0EAiECsRxy9K4e3Dctv9m6+Wma4+vy65MS0zGyrqey6oyV4l827sCOjrD1qcqc9bX6FlMSouVoNfE4ZjINNAbgigTaiLSoDSPcf5I5smkkM2ezzFOMSZwZxNdaNL2LKb97vc8m/ZUkv0sKZyT7oqVL7aJweEivsSHj5l2KR8Z7XrVB1y2eI6GvyTSa/d+CL4dSRzjh8+IRN047YBrdTKD5IkdT0upfoBu14WPUfFmLKxX+iMCslXRWb6kwojhrWNYmZvL65KRAzJ6+eIPDG/W5QUOpYyYT77bLlBQjVo6NmVvl9v3HMECq9CHH0ivKFBGPiKMOx7cJkTax3FuyznOW2WCXB9kTb5Zk9toaiNlSp9L6ll/h2Eyxa6n6sWUgmmM=
|
||||||
- secure: vVRg9BKGBwF2MbXQnEccFL+XW0/7RaBmge9k7jbGYScBwkP3XjnQ/Xaj0cvTz2CM2EqXsbpwfvr4Jo+enW/E3MGy5RiEzv5hUe/jIFRR0gfAFbZxSTvg5xiFhTDffqQk0fncO4jXu+wPO5lZ2CMRWzyXz3i1MZhjMcAgoDr1+TRss/EGXLNHxr2RM88tpUW0fV2prIRoyGqhCgnYZtrm7hmr41Ej+itg1MqZLml/Rjkt3KsNgI+z0O5Qn3QSAO8GtPZqeftQxAjevOmxZGcssxY8EJvqbjAujr4y51WncXpEmCRPSY2J9R5+fkgZurqwnJapbQpjwKYemok3ps7EHg2gWkAlmPdQO4LKpbffGkM/o5b+8+HdIuQZugsSWQD9hUSftTAFLcfA1isi7V2lHE1m8bX/vk9zIyDdcPSwIaFe9y+w3PexwFmTjPLq+nia/UY2kARFZMEIFAJby6gkA70DcAJ50QOM86InJu5DSzGbIssgTGAXCn0TPPyGveaurVLw8x61j3yh8LDF46gUHey3rqv6WjpCM9h/vg7X/gq5ve/5Q2KHscUKfs/sA53Mt7qPeqRZY1QCaaRjzqJO/ZraHqWWeKmPKaWhPGR0kYEnkvB+K9GZ+HNSWCltjCO4SJ1xeEl7CRqQxAwdiMATF5SKqyiC+bn5oc35mFgbRF8=
|
- secure: vVRg9BKGBwF2MbXQnEccFL+XW0/7RaBmge9k7jbGYScBwkP3XjnQ/Xaj0cvTz2CM2EqXsbpwfvr4Jo+enW/E3MGy5RiEzv5hUe/jIFRR0gfAFbZxSTvg5xiFhTDffqQk0fncO4jXu+wPO5lZ2CMRWzyXz3i1MZhjMcAgoDr1+TRss/EGXLNHxr2RM88tpUW0fV2prIRoyGqhCgnYZtrm7hmr41Ej+itg1MqZLml/Rjkt3KsNgI+z0O5Qn3QSAO8GtPZqeftQxAjevOmxZGcssxY8EJvqbjAujr4y51WncXpEmCRPSY2J9R5+fkgZurqwnJapbQpjwKYemok3ps7EHg2gWkAlmPdQO4LKpbffGkM/o5b+8+HdIuQZugsSWQD9hUSftTAFLcfA1isi7V2lHE1m8bX/vk9zIyDdcPSwIaFe9y+w3PexwFmTjPLq+nia/UY2kARFZMEIFAJby6gkA70DcAJ50QOM86InJu5DSzGbIssgTGAXCn0TPPyGveaurVLw8x61j3yh8LDF46gUHey3rqv6WjpCM9h/vg7X/gq5ve/5Q2KHscUKfs/sA53Mt7qPeqRZY1QCaaRjzqJO/ZraHqWWeKmPKaWhPGR0kYEnkvB+K9GZ+HNSWCltjCO4SJ1xeEl7CRqQxAwdiMATF5SKqyiC+bn5oc35mFgbRF8=
|
||||||
- secure: ela1tn4wkJQZ8O4iv+4pIZi5cebxeCStVF1tEUe6qa6WWgJYVXmS2tEv3QQ36NUBFrP58Y6yl10XguPnvj/2BCqcZI4FUBHh3BfiBoUtXxDCVKI5LtlniNiOFGUwfzEeYka8T51zFlcUXSCCaxHkRZbmBsIzeJ39UwTi5fy0qwLv9GgL0czhwm8I8sZ8gyWdGmqpXNFEsb9JP4ZA3mw2qpWkGpGAqQPD9XSCkU3LmX1/ltwsBMAgGYKLLo7vU8d5KV2c8L1Gnxfl6BvfmqUD/dsas/1rnk08rU2nez5ekuQa2tJRkDLOv8bqvrGRLjHSUa3yPuisC6SsDGSU7/3DcozZyYsz7WQ6WI8tYabyjqqeJTF1N8a5T3IbZaZNV1J4JHOO9Cb/y7gIg4edANg6tbe7MzZpdEPRBnw6OkdTdirpNsWQ/jnfpY1hn6mraQZz/q8yaz3W21NjbBJhVnvfh5gWLKQ3YAAziCBhmmrThFhUu0czz+G920MuFo477TBcxvlrE7CaNJ0Q6yYkDehEPOv3jvEs1QVHPwuRrlaLTbBhrlTICKZ58gdX30O8N4i0Xgp/v6qrC03bplnMQc8E/uC61wcVLJixnlZVp8FODpUvPjsxVFkpuNSOIAaiqcERmoiPXx05Epzmr78hjU5rYCx/1MmVoeB4gs9YO+4guD4=
|
- secure: ela1tn4wkJQZ8O4iv+4pIZi5cebxeCStVF1tEUe6qa6WWgJYVXmS2tEv3QQ36NUBFrP58Y6yl10XguPnvj/2BCqcZI4FUBHh3BfiBoUtXxDCVKI5LtlniNiOFGUwfzEeYka8T51zFlcUXSCCaxHkRZbmBsIzeJ39UwTi5fy0qwLv9GgL0czhwm8I8sZ8gyWdGmqpXNFEsb9JP4ZA3mw2qpWkGpGAqQPD9XSCkU3LmX1/ltwsBMAgGYKLLo7vU8d5KV2c8L1Gnxfl6BvfmqUD/dsas/1rnk08rU2nez5ekuQa2tJRkDLOv8bqvrGRLjHSUa3yPuisC6SsDGSU7/3DcozZyYsz7WQ6WI8tYabyjqqeJTF1N8a5T3IbZaZNV1J4JHOO9Cb/y7gIg4edANg6tbe7MzZpdEPRBnw6OkdTdirpNsWQ/jnfpY1hn6mraQZz/q8yaz3W21NjbBJhVnvfh5gWLKQ3YAAziCBhmmrThFhUu0czz+G920MuFo477TBcxvlrE7CaNJ0Q6yYkDehEPOv3jvEs1QVHPwuRrlaLTbBhrlTICKZ58gdX30O8N4i0Xgp/v6qrC03bplnMQc8E/uC61wcVLJixnlZVp8FODpUvPjsxVFkpuNSOIAaiqcERmoiPXx05Epzmr78hjU5rYCx/1MmVoeB4gs9YO+4guD4=
|
||||||
- secure: SEqTg6WoGPPpcWzJ03ZfcSBb3nZ2Mdhug0ec2PszuzYO3libCb9usiqi+jils9z6qyXsL6ecz8HYazDGOUepnubhIpI5otLgfn9XiapjMT06Bj//AjbKpH7eu3TJSpJMzoRHZrKIE1y9ZKIBqKwl9Xs7ko+1oa+MLhrLuxXkoi0JqRB5UzkQtJRDoxVNjysnLQn+hsfnm+yuqPHZd2+Loy++q//WHuf9bwJrlkXn2ICYQIX5oQGlxNO6ui+OZklb0YknvyO5GdQeoKaHYru3MMKKCIS6I7AG9wLmPs5Ou3T0Ia0Xx4/7xazs0rH4NCVpIceSYc3v6evR37pp8MsFTC3BzjL1V3slTnmitC1KSNM8ndGRUg1nsCBkJysnR3HpX6SHuCH+UzOuMxEjwiPdSRnzJPEbTHa1HqMfTkTJMbm4zhp7W4/ozX4TtjUB0ql6NoQE2n0Z3aYgR2C78TmzaPQun8EgredWnCID1FedyexaNcw4HyZ2rXlcvG3rBzSwLHH5PePT9skyqy6KtIaL0MlAP556ilgUeyCZfCNdTmzCvPDZuqaeLRezWDdsKnRfTkxIW80QWlmZ6sW0hynJV5JN2Oghk9Tr+QzgV4ZF68FHwoU9YXCTyX4w5iTYq/GjvfTBqB3VSGPOz3PwU7r47tmaYzPj+I44zqktgxyuxDo=
|
- secure: Pc86j2/rgCPAEWcjzPbbVFkL2SwNt4CpeXP69zedMi9RomQblMJa9R0wbAT6H5VCnPky2cpmxkzjFWOc91N9crzceg6aOoWkPr6pTTnTv+EL70i6+XSrAFpkgRjszprxnU1Bz+GcYEjP/zR8479fx8ooSl7MwHOaP7XiQyaBQAbY1CPlmpT+b4Ut7Fm5QnD90/NgPjbKkngl0kVUfNFdFOSfJ3QWLyFCUUSQ4DlxccJOTIaOH/n8u9Nz4NTuuHE3XeRuEsuj2SJutJnFBUYwsvugrdPvKWiubkewJfylp6EwABzByENsg6XxW9SIq80lMc3Oi7ld9L2lAgpj+8/42olnbMzH0F0rw/p1ccPAdVwQVV6YFaqCzivK5A5aX5LmGKwJ6SR9k1PgcWP6sKKMIsIEObbyM88Tke3QkreEz+cLg/3jjko7Vpb0tbqh8BtbpWV+exL4rX3r2C5Mb1Es1W597hN5LSczWYFgw0ZETpfbVZg6Ri1iZks0wpsT/E+c0q2scUaBVrdTZseHxUPB7mPDlXL1l9/i4sOxPyBHZtJRAzeTT/fOXfj4vuD+ihspXzoRRLaQbizlb8FpyPA47XdmBDpXi3OBiaIFLwvybEn7qM7rqvWxdz6vvCZv0t/AN3t3Qvh2vHKCshHecaa8NoJQHWrdFMHeecYHyeoujZ8=
|
||||||
addons:
|
addons:
|
||||||
chrome: stable
|
chrome: stable
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- libgconf-2-4
|
||||||
before_script: npm run bundle
|
before_script: npm run bundle
|
||||||
script: npm test && ([ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run e2e-ci || npm
|
script: npm test && ([ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run e2e-ci || npm
|
||||||
run e2e)
|
run e2e)
|
||||||
|
@ -29,9 +34,6 @@ deploy:
|
||||||
tags: true
|
tags: true
|
||||||
- provider: script
|
- provider: script
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
script: cd cli && yarn install && yarn ci-publish || true
|
script: npm run deploy:demo
|
||||||
- provider: script
|
|
||||||
skip_cleanup: true
|
|
||||||
script: yarn deploy:demo
|
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
|
|
243
CHANGELOG.md
243
CHANGELOG.md
|
@ -1,3 +1,228 @@
|
||||||
|
# [2.0.0-rc.24](https://github.com/Redocly/redoc/compare/v2.0.0-rc.23...v2.0.0-rc.24) (2020-03-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add debounce for 300 ms when searching ([#1089](https://github.com/Redocly/redoc/issues/1089)) ([373f018](https://github.com/Redocly/redoc/commit/373f018d0c183f83d07a4dbad4a4e2c9ab159f69))
|
||||||
|
* do not load SearchWorker if disableSearch is `true` ([#1191](https://github.com/Redocly/redoc/issues/1191)) ([af415e8](https://github.com/Redocly/redoc/commit/af415e89e8c074a3f7c84f76f24020a7bd545483)), closes [#764](https://github.com/Redocly/redoc/issues/764)
|
||||||
|
* fix major search performance due to wrong marker element ([8c053cc](https://github.com/Redocly/redoc/commit/8c053cc474e88befc3338307317c0702d212d4c3)), closes [#1109](https://github.com/Redocly/redoc/issues/1109)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* new option expandSingleSchemaField ([7608800](https://github.com/Redocly/redoc/commit/7608800d0acaa2fa0099dc840e17cd5aa90b54ca))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.23](https://github.com/Redocly/redoc/compare/v2.0.0-rc.22...v2.0.0-rc.23) (2020-02-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix broken sticky sidebar in Chrome 80 ([1a2a7dd](https://github.com/Redocly/redoc/commit/1a2a7dd8331cedd6ced4c18accf0b417549b3ff3)), closes [#1167](https://github.com/Redocly/redoc/issues/1167)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.22](https://github.com/Redocly/redoc/compare/v2.0.0-rc.21...v2.0.0-rc.22) (2020-01-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* do not process oneOf if inherited from parent with discriminator ([5248415](https://github.com/Redocly/redoc/commit/52484157912d908daea8255d0b7d684b33258d7a))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add HTTP syntax highlighting ([#1157](https://github.com/Redocly/redoc/issues/1157)) ([27a4af7](https://github.com/Redocly/redoc/commit/27a4af707686d56280753473b4294ee4af096534))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.21](https://github.com/Redocly/redoc/compare/v2.0.0-rc.20...v2.0.0-rc.21) (2020-01-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* empty servers behaviour per OAS spec ([ed1db0c](https://github.com/Redocly/redoc/commit/ed1db0c9027087ae0ae923e390e3e1d638a647ae)), closes [#1151](https://github.com/Redocly/redoc/issues/1151)
|
||||||
|
* fix duplicated content in tags when using md headings ([a260c84](https://github.com/Redocly/redoc/commit/a260c8414c34a259a70a20ebcd20ecbb06c3d250)), closes [#1150](https://github.com/Redocly/redoc/issues/1150) [#1152](https://github.com/Redocly/redoc/issues/1152)
|
||||||
|
* use mobile menu background color value from theme ([#1144](https://github.com/Redocly/redoc/issues/1144)) ([41a9b3c](https://github.com/Redocly/redoc/commit/41a9b3c18228d236d182d3c15c9abc35ae72a0d5))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.20](https://github.com/Redocly/redoc/compare/v2.0.0-rc.19...v2.0.0-rc.20) (2019-12-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix missing parameters ([942d782](https://github.com/Redocly/redoc/commit/942d782b5a8d08767a7538741b75587cf1e67f44)), closes [#1142](https://github.com/Redocly/redoc/issues/1142)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.19](https://github.com/Redocly/redoc/compare/v2.0.0-rc.18...v2.0.0-rc.19) (2019-12-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* change the title of "Security Scheme Type" to match "HTTP Authorization Scheme" ([#1126](https://github.com/Redocly/redoc/issues/1126)) ([289c8e6](https://github.com/Redocly/redoc/commit/289c8e6ae1ff00371f86d3f2646607c64bc30050))
|
||||||
|
* do not URI-encode parameter values for better readability ([6aeb0bf](https://github.com/Redocly/redoc/commit/6aeb0bf68df3f03f2ca1317f8b5787545bd363f1)), closes [#1138](https://github.com/Redocly/redoc/issues/1138)
|
||||||
|
* fix sortByRequired (stabilise sort) ([#1136](https://github.com/Redocly/redoc/issues/1136)) ([d92434d](https://github.com/Redocly/redoc/commit/d92434d11b08e8b0f6be5453ec69aa1d0e0df79f)), closes [#1104](https://github.com/Redocly/redoc/issues/1104) [#1121](https://github.com/Redocly/redoc/issues/1121) [#1061](https://github.com/Redocly/redoc/issues/1061)
|
||||||
|
* h2 padding on mobile ([7ed1a7e](https://github.com/Redocly/redoc/commit/7ed1a7ef0e7978a0dfb40afcc72c3362466f9624)), closes [#1118](https://github.com/Redocly/redoc/issues/1118)
|
||||||
|
* python comment stripped in headings ([4a25aae](https://github.com/Redocly/redoc/commit/4a25aaef69fad814836392ea7e41eb32c182a261)), closes [#1116](https://github.com/Redocly/redoc/issues/1116)
|
||||||
|
* remove hardcoded fontFamily for oneOf labels ([094ce91](https://github.com/Redocly/redoc/commit/094ce914e3f9cfe567b39db4ea88208014d8b686)), closes [#1120](https://github.com/Redocly/redoc/issues/1120)
|
||||||
|
* search-box use theme ([1bf490c](https://github.com/Redocly/redoc/commit/1bf490c05b343d262f8819bf1ddc433e070be1b9))
|
||||||
|
* support discriminator mapping 1-n ([6e390f9](https://github.com/Redocly/redoc/commit/6e390f9c7909da0b5d1d6fc571ab4ad92e715d6e)), closes [#1111](https://github.com/Redocly/redoc/issues/1111)
|
||||||
|
* wrap json examples in code tag ([#1064](https://github.com/Redocly/redoc/issues/1064)) ([dc5430e](https://github.com/Redocly/redoc/commit/dc5430e53def780a81612d269cc3aea3f8785eea))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* display `multipleOf` constrains ([#1065](https://github.com/Redocly/redoc/issues/1065)) ([3e90133](https://github.com/Redocly/redoc/commit/3e901336643b988ae45ae86c485005b8865e6e04))
|
||||||
|
* enable menuToggle by default ([5d81abe](https://github.com/Redocly/redoc/commit/5d81abeb28c1e4f2826e41424c10163834c37e45))
|
||||||
|
* new option hideSchemaTitles ([11cc4c4](https://github.com/Redocly/redoc/commit/11cc4c4c3e04a7e5bf3a9ebba20d10fa882a49e5))
|
||||||
|
* new option payloadSampleIdx ([eaaa99d](https://github.com/Redocly/redoc/commit/eaaa99d68e2392273e8d9c0173db3b546e035d5f))
|
||||||
|
* **cli:** Fallback on the spec's title before falling back on… ([#1073](https://github.com/Redocly/redoc/issues/1073)) ([e01eea4](https://github.com/Redocly/redoc/commit/e01eea445c93d74b66533c860d76bb3aff4d6df2))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.18](https://github.com/Redocly/redoc/compare/v2.0.0-rc.17...v2.0.0-rc.18) (2019-10-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add oneOf buttons vertical space when wrapped to new line ([cd9fd61](https://github.com/Redocly/redoc/commit/cd9fd61))
|
||||||
|
* improve mime-type dropdown font ([ce885f8](https://github.com/Redocly/redoc/commit/ce885f8))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.17](https://github.com/Redocly/redoc/compare/v2.0.0-rc.16...v2.0.0-rc.17) (2019-10-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* active menu item scroll into view ([0a01e9a](https://github.com/Redocly/redoc/commit/0a01e9a))
|
||||||
|
* changed several components style font-family to monospace ([#1063](https://github.com/Redocly/redoc/issues/1063)) ([0c20e64](https://github.com/Redocly/redoc/commit/0c20e64)), closes [#909](https://github.com/Redocly/redoc/issues/909)
|
||||||
|
* no quotes for default values in header fields. ([#1059](https://github.com/Redocly/redoc/issues/1059)) ([b5af71d](https://github.com/Redocly/redoc/commit/b5af71d))
|
||||||
|
* types over-pluralization ([#1057](https://github.com/Redocly/redoc/issues/1057)) ([4494f80](https://github.com/Redocly/redoc/commit/4494f80)), closes [#1053](https://github.com/Redocly/redoc/issues/1053)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* added support for file paths as --options cli argument ([#1049](https://github.com/Redocly/redoc/issues/1049)) ([4adb927](https://github.com/Redocly/redoc/commit/4adb927))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.16](https://github.com/Redocly/redoc/compare/v2.0.0-rc.15...v2.0.0-rc.16) (2019-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix scrollYOffset when SSR ([d09c1c1](https://github.com/Redocly/redoc/commit/d09c1c1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.15](https://github.com/Redocly/redoc/compare/v2.0.0-rc.14...v2.0.0-rc.15) (2019-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* auth section appears twice ([5aa7784](https://github.com/Redocly/redoc/commit/5aa7784)), closes [#818](https://github.com/Redocly/redoc/issues/818)
|
||||||
|
* clicking on group title breaks first tag ([4649683](https://github.com/Redocly/redoc/commit/4649683)), closes [#1034](https://github.com/Redocly/redoc/issues/1034)
|
||||||
|
* do not crash on empty scopes ([e787d9e](https://github.com/Redocly/redoc/commit/e787d9e)), closes [#1044](https://github.com/Redocly/redoc/issues/1044)
|
||||||
|
* false-positive recursive detection with allOf at the same level ([faa74d6](https://github.com/Redocly/redoc/commit/faa74d6))
|
||||||
|
* fix scrollYOffset when SSR ([21258a5](https://github.com/Redocly/redoc/commit/21258a5))
|
||||||
|
* left menu item before group is not highlighted ([67e2a8f](https://github.com/Redocly/redoc/commit/67e2a8f)), closes [#1033](https://github.com/Redocly/redoc/issues/1033)
|
||||||
|
* remove excessive whitespace between md sections on small screens ([e318fb3](https://github.com/Redocly/redoc/commit/e318fb3)), closes [#874](https://github.com/Redocly/redoc/issues/874)
|
||||||
|
* use url-template dependency ([#1008](https://github.com/Redocly/redoc/issues/1008)) ([32a464a](https://github.com/Redocly/redoc/commit/32a464a)), closes [#1007](https://github.com/Redocly/redoc/issues/1007)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **cli:** added support for JSON string value for --options CLI argument ([#1047](https://github.com/Redocly/redoc/issues/1047)) ([2a28130](https://github.com/Redocly/redoc/commit/2a28130)), closes [#797](https://github.com/Redocly/redoc/issues/797)
|
||||||
|
* **cli:** add `disableGoogleFont` parameter to cli ([#1045](https://github.com/Redocly/redoc/issues/1045)) ([aceb343](https://github.com/Redocly/redoc/commit/aceb343))
|
||||||
|
* new option expandDefaultServerVariables ([#1014](https://github.com/Redocly/redoc/issues/1014)) ([0360dce](https://github.com/Redocly/redoc/commit/0360dce))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.14](https://github.com/Redocly/redoc/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2019-08-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix escaping JSON string values ([58cb20d](https://github.com/Redocly/redoc/commit/58cb20d)), closes [#999](https://github.com/Redocly/redoc/issues/999)
|
||||||
|
* revert expanding default server variables ([7849f7f](https://github.com/Redocly/redoc/commit/7849f7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.13](https://github.com/Redocly/redoc/compare/v2.0.0-rc.12...v2.0.0-rc.13) (2019-08-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* enum list doesn't wrap ([bfbb0c1](https://github.com/Redocly/redoc/commit/bfbb0c1)), closes [#993](https://github.com/Redocly/redoc/issues/993)
|
||||||
|
* incorrect serialization of some parameter samples ([aba45db](https://github.com/Redocly/redoc/commit/aba45db)), closes [#992](https://github.com/Redocly/redoc/issues/992)
|
||||||
|
* support json serialization for parameter examples ([1367380](https://github.com/Redocly/redoc/commit/1367380)), closes [#934](https://github.com/Redocly/redoc/issues/934)
|
||||||
|
* unify accordion icons for responses section ([2afc2e4](https://github.com/Redocly/redoc/commit/2afc2e4)), closes [#975](https://github.com/Redocly/redoc/issues/975)
|
||||||
|
* update to core.js 3 ([9e3375d](https://github.com/Redocly/redoc/commit/9e3375d)), closes [#997](https://github.com/Redocly/redoc/issues/997)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.12](https://github.com/Redocly/redoc/compare/v2.0.0-rc.11...v2.0.0-rc.12) (2019-07-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* rename ObjectDescription to SchemaDefinition as discussed ([4496622](https://github.com/Redocly/redoc/commit/4496622))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.11](https://github.com/Redocly/redoc/compare/v2.0.0-rc.10...v2.0.0-rc.11) (2019-07-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* do not add extra slashes to pattern ([70d1ee9](https://github.com/Redocly/redoc/commit/70d1ee9)), closes [#983](https://github.com/Redocly/redoc/issues/983)
|
||||||
|
* dropdown fixes related to object description ([0504ad4](https://github.com/Redocly/redoc/commit/0504ad4))
|
||||||
|
* incorrect serialization of parameter sample with hyphen ([f7dd658](https://github.com/Redocly/redoc/commit/f7dd658))
|
||||||
|
* redoc-cli: Add missing content type header on compressed responses of `/` path
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* menu items from tags + md extension for Schema Definition ([#681](https://github.com/Redocly/redoc/pull/681))
|
||||||
|
* new option `menuToggle` - fold active MenuItem if clicked ([#963](https://github.com/Redocly/redoc/issues/963))
|
||||||
|
* Add option for skipping quotes in enums `enumSkipQuotes` ([#968](https://github.com/Redocly/redoc/issues/968)) ([afc7e36](https://github.com/Redocly/redoc/commit/afc7e36))
|
||||||
|
* add `sampleCollapseLevel` option ([#937](https://github.com/Redocly/redoc/issues/937)) ([d3f1c16](https://github.com/Redocly/redoc/commit/d3f1c16))
|
||||||
|
|
||||||
|
# [2.0.0-rc.10](https://github.com/Redocly/redoc/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2019-07-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* broken headings with single quote ([51d3b9b](https://github.com/Redocly/redoc/commit/51d3b9b)), closes [#955](https://github.com/Redocly/redoc/issues/955)
|
||||||
|
* fix fields table overflow if deeply nested with long title ([12b7057](https://github.com/Redocly/redoc/commit/12b7057))
|
||||||
|
* hide empty example when it is not defined ([4bd499f](https://github.com/Redocly/redoc/commit/4bd499f))
|
||||||
|
* markdown in examples descriptions + minor ui tweaks ([f52d9e8](https://github.com/Redocly/redoc/commit/f52d9e8))
|
||||||
|
* organize response examples in dropdown and display description ([995e557](https://github.com/Redocly/redoc/commit/995e557))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.9](https://github.com/Redocly/redoc/compare/v2.0.0-rc.8-1...v2.0.0-rc.9) (2019-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix regression double slashes added to full URL display ([f29a4fe](https://github.com/Redocly/redoc/commit/f29a4fe))
|
||||||
|
* IE11, add missing Object.assign polyfill ([888f04e](https://github.com/Redocly/redoc/commit/888f04e))
|
||||||
|
* serialize parameter example values according to the spec ([#917](https://github.com/Redocly/redoc/issues/917)) ([3939286](https://github.com/Redocly/redoc/commit/3939286))
|
||||||
|
* styled-component style error in tabs ([#946](https://github.com/Redocly/redoc/issues/946)) ([c488bbf](https://github.com/Redocly/redoc/commit/c488bbf))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add x-additionalPropertiesName ([#622](https://github.com/Redocly/redoc/issues/622)) ([#944](https://github.com/Redocly/redoc/issues/944)) ([0eb1e66](https://github.com/Redocly/redoc/commit/0eb1e66))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [2.0.0-rc.8-1](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.8...v2.0.0-rc.8-1) (2019-05-13)
|
# [2.0.0-rc.8-1](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.8...v2.0.0-rc.8-1) (2019-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +256,7 @@
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* broken schema talbes with long enums ([3a74b74](https://github.com/Rebilly/ReDoc/commit/3a74b74))
|
* broken schema tables with long enums ([3a74b74](https://github.com/Rebilly/ReDoc/commit/3a74b74))
|
||||||
* deep linking sometimes not working when sent over messengers ([2491d97](https://github.com/Rebilly/ReDoc/commit/2491d97))
|
* deep linking sometimes not working when sent over messengers ([2491d97](https://github.com/Rebilly/ReDoc/commit/2491d97))
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,7 +274,7 @@
|
||||||
* IE11 add missing fetch and URL polyfills ([d2ce1bd](https://github.com/Rebilly/ReDoc/commit/d2ce1bd)), closes [#875](https://github.com/Rebilly/ReDoc/issues/875)
|
* IE11 add missing fetch and URL polyfills ([d2ce1bd](https://github.com/Rebilly/ReDoc/commit/d2ce1bd)), closes [#875](https://github.com/Rebilly/ReDoc/issues/875)
|
||||||
* ignore empty x-tagGroups array ([#869](https://github.com/Rebilly/ReDoc/issues/869)) ([4366a0d](https://github.com/Rebilly/ReDoc/commit/4366a0d))
|
* ignore empty x-tagGroups array ([#869](https://github.com/Rebilly/ReDoc/issues/869)) ([4366a0d](https://github.com/Rebilly/ReDoc/commit/4366a0d))
|
||||||
* incorrect detected schema title for deeply inherited schemas ([7d7b4e3](https://github.com/Rebilly/ReDoc/commit/7d7b4e3))
|
* incorrect detected schema title for deeply inherited schemas ([7d7b4e3](https://github.com/Rebilly/ReDoc/commit/7d7b4e3))
|
||||||
* pluralize arrray of types ([fdcac30](https://github.com/Rebilly/ReDoc/commit/fdcac30))
|
* pluralize array of types ([fdcac30](https://github.com/Rebilly/ReDoc/commit/fdcac30))
|
||||||
* remove huge space after Authentication section ([548fae3](https://github.com/Rebilly/ReDoc/commit/548fae3)), closes [#872](https://github.com/Rebilly/ReDoc/issues/872)
|
* remove huge space after Authentication section ([548fae3](https://github.com/Rebilly/ReDoc/commit/548fae3)), closes [#872](https://github.com/Rebilly/ReDoc/issues/872)
|
||||||
* remove query string from server URL ([#895](https://github.com/Rebilly/ReDoc/issues/895)) ([64453ff](https://github.com/Rebilly/ReDoc/commit/64453ff))
|
* remove query string from server URL ([#895](https://github.com/Rebilly/ReDoc/issues/895)) ([64453ff](https://github.com/Rebilly/ReDoc/commit/64453ff))
|
||||||
* remove tabs top margin ([5c187f3](https://github.com/Rebilly/ReDoc/commit/5c187f3))
|
* remove tabs top margin ([5c187f3](https://github.com/Rebilly/ReDoc/commit/5c187f3))
|
||||||
|
@ -133,7 +358,7 @@
|
||||||
* improve scrolling performance in Chrome with non-wrapped json examples ([a69c402](https://github.com/Rebilly/ReDoc/commit/a69c402))
|
* improve scrolling performance in Chrome with non-wrapped json examples ([a69c402](https://github.com/Rebilly/ReDoc/commit/a69c402))
|
||||||
* nested oneOf button spacing ([3673720](https://github.com/Rebilly/ReDoc/commit/3673720)), closes [#719](https://github.com/Rebilly/ReDoc/issues/719)
|
* nested oneOf button spacing ([3673720](https://github.com/Rebilly/ReDoc/commit/3673720)), closes [#719](https://github.com/Rebilly/ReDoc/issues/719)
|
||||||
* onLoaded callback not run on spec error ([e77df0c](https://github.com/Rebilly/ReDoc/commit/e77df0c)), closes [#690](https://github.com/Rebilly/ReDoc/issues/690)
|
* onLoaded callback not run on spec error ([e77df0c](https://github.com/Rebilly/ReDoc/commit/e77df0c)), closes [#690](https://github.com/Rebilly/ReDoc/issues/690)
|
||||||
* theme improvments by [@stasiukanya](https://github.com/stasiukanya) ([e2d0cd5](https://github.com/Rebilly/ReDoc/commit/e2d0cd5))
|
* theme improvements by [@stasiukanya](https://github.com/stasiukanya) ([e2d0cd5](https://github.com/Rebilly/ReDoc/commit/e2d0cd5))
|
||||||
* **cli:** old peer dependency issue with styled-components ([#699](https://github.com/Rebilly/ReDoc/issues/699)) ([9e2853c](https://github.com/Rebilly/ReDoc/commit/9e2853c))
|
* **cli:** old peer dependency issue with styled-components ([#699](https://github.com/Rebilly/ReDoc/issues/699)) ([9e2853c](https://github.com/Rebilly/ReDoc/commit/9e2853c))
|
||||||
|
|
||||||
|
|
||||||
|
@ -205,7 +430,7 @@
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* addd indent to array schema internals ([865f3ce](https://github.com/Rebilly/ReDoc/commit/865f3ce))
|
* add indent to array schema internals ([865f3ce](https://github.com/Rebilly/ReDoc/commit/865f3ce))
|
||||||
* fix oneOf/anyOf titles ([39b930d](https://github.com/Rebilly/ReDoc/commit/39b930d)), closes [#618](https://github.com/Rebilly/ReDoc/issues/618) [#621](https://github.com/Rebilly/ReDoc/issues/621)
|
* fix oneOf/anyOf titles ([39b930d](https://github.com/Rebilly/ReDoc/commit/39b930d)), closes [#618](https://github.com/Rebilly/ReDoc/issues/618) [#621](https://github.com/Rebilly/ReDoc/issues/621)
|
||||||
|
|
||||||
|
|
||||||
|
@ -251,7 +476,7 @@
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* add some spacing between operation description and parameters ([597688e](https://github.com/Rebilly/ReDoc/commit/597688e))
|
* add some spacing between operation description and parameters ([597688e](https://github.com/Rebilly/ReDoc/commit/597688e))
|
||||||
* description is not rendered if doesn't containt markdown headings ([90ed717](https://github.com/Rebilly/ReDoc/commit/90ed717)), closes [#591](https://github.com/Rebilly/ReDoc/issues/591)
|
* description is not rendered if doesn't contain markdown headings ([90ed717](https://github.com/Rebilly/ReDoc/commit/90ed717)), closes [#591](https://github.com/Rebilly/ReDoc/issues/591)
|
||||||
* download button downloads index.html instead of spec with CLI ([334f904](https://github.com/Rebilly/ReDoc/commit/334f904)), closes [#594](https://github.com/Rebilly/ReDoc/issues/594)
|
* download button downloads index.html instead of spec with CLI ([334f904](https://github.com/Rebilly/ReDoc/commit/334f904)), closes [#594](https://github.com/Rebilly/ReDoc/issues/594)
|
||||||
* fix Authentication section is not rendered ([2ecc8bc](https://github.com/Rebilly/ReDoc/commit/2ecc8bc)), closes [#590](https://github.com/Rebilly/ReDoc/issues/590)
|
* fix Authentication section is not rendered ([2ecc8bc](https://github.com/Rebilly/ReDoc/commit/2ecc8bc)), closes [#590](https://github.com/Rebilly/ReDoc/issues/590)
|
||||||
* fix linebreaks in multiparagraph field descriptions ([8fb9cd6](https://github.com/Rebilly/ReDoc/commit/8fb9cd6))
|
* fix linebreaks in multiparagraph field descriptions ([8fb9cd6](https://github.com/Rebilly/ReDoc/commit/8fb9cd6))
|
||||||
|
@ -562,7 +787,7 @@
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
* Path parameters are not correctly overriden ([c406dc5](https://github.com/Rebilly/ReDoc/commit/c406dc5)), closes [#400](https://github.com/Rebilly/ReDoc/issues/400)
|
* Path parameters are not correctly overridden ([c406dc5](https://github.com/Rebilly/ReDoc/commit/c406dc5)), closes [#400](https://github.com/Rebilly/ReDoc/issues/400)
|
||||||
* Use parentNode instead of parentElement to fix IE11 crash ([e8adb60](https://github.com/Rebilly/ReDoc/commit/e8adb60)), closes [#406](https://github.com/Rebilly/ReDoc/issues/406)
|
* Use parentNode instead of parentElement to fix IE11 crash ([e8adb60](https://github.com/Rebilly/ReDoc/commit/e8adb60)), closes [#406](https://github.com/Rebilly/ReDoc/issues/406)
|
||||||
|
|
||||||
|
|
||||||
|
@ -724,7 +949,7 @@
|
||||||
* do not ignore path level parameters ([14f8408](https://github.com/Rebilly/Redoc/commit/14f8408))
|
* do not ignore path level parameters ([14f8408](https://github.com/Rebilly/Redoc/commit/14f8408))
|
||||||
* improve rendering of types ([17da7b7](https://github.com/Rebilly/Redoc/commit/17da7b7))
|
* improve rendering of types ([17da7b7](https://github.com/Rebilly/Redoc/commit/17da7b7))
|
||||||
* move title propagation to the correct place ([0b0bc99](https://github.com/Rebilly/Redoc/commit/0b0bc99))
|
* move title propagation to the correct place ([0b0bc99](https://github.com/Rebilly/Redoc/commit/0b0bc99))
|
||||||
* owerwrite text-align to left ([bfee3ed](https://github.com/Rebilly/Redoc/commit/bfee3ed))
|
* overwrite text-align to left ([bfee3ed](https://github.com/Rebilly/Redoc/commit/bfee3ed))
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
@ -786,7 +1011,7 @@ Complete rewrite also means that this rewrite may introduce issues, but they sho
|
||||||
|
|
||||||
### Deprecations
|
### Deprecations
|
||||||
|
|
||||||
- Fonts are not loaded by ReDoc so you should load them. Default fonts can be loaded as bellow:
|
- Fonts are not loaded by ReDoc so you should load them. Default fonts can be loaded as below:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||||
|
@ -1155,7 +1380,7 @@ closes [#321](https://github.com/Rebilly/ReDoc/issues/321)
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
* Update webpack to the latest beta ([#143](https://github.com/Rebilly/ReDoc/issues/143))
|
* Update webpack to the latest beta ([#143](https://github.com/Rebilly/ReDoc/issues/143))
|
||||||
* Fix read-only fields appear in request samples ([#142](https://github.com/Rebilly/ReDoc/issues/142))
|
* Fix read-only fields appear in request samples ([#142](https://github.com/Rebilly/ReDoc/issues/142))
|
||||||
* A few more minor UI improvemnts
|
* A few more minor UI improvements
|
||||||
|
|
||||||
### Features/Improvements
|
### Features/Improvements
|
||||||
* Major performance optimization with new option `lazy-rendering`
|
* Major performance optimization with new option `lazy-rendering`
|
||||||
|
|
60
README.md
60
README.md
|
@ -64,11 +64,11 @@ Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(d
|
||||||
| 1.17.x | 2.0 |
|
| 1.17.x | 2.0 |
|
||||||
|
|
||||||
## Some Real-life usages
|
## Some Real-life usages
|
||||||
- [Rebilly](https://rebilly.github.io/RebillyAPI)
|
- [Rebilly](https://rebilly-api.redoc.ly/)
|
||||||
- [Docker Engine](https://docs.docker.com/engine/api/v1.25/)
|
- [Docker Engine](https://docs.docker.com/engine/api/v1.25/)
|
||||||
- [Zuora](https://www.zuora.com/developer/api-reference/)
|
- [Zuora](https://www.zuora.com/developer/api-reference/)
|
||||||
- [Shopify Draft Orders](https://help.shopify.com/api/draft-orders)
|
|
||||||
- [Discourse](http://docs.discourse.org)
|
- [Discourse](http://docs.discourse.org)
|
||||||
|
- [Commbox](https://www.commbox.io/api/)
|
||||||
- [APIs.guru](https://apis.guru/api-doc/)
|
- [APIs.guru](https://apis.guru/api-doc/)
|
||||||
- [FastAPI](https://github.com/tiangolo/fastapi)
|
- [FastAPI](https://github.com/tiangolo/fastapi)
|
||||||
|
|
||||||
|
@ -107,14 +107,14 @@ That's all folks!
|
||||||
**IMPORTANT NOTE:** if you work with untrusted user spec, use `untrusted-spec` [option](#redoc-options-object) to prevent XSS security risks.
|
**IMPORTANT NOTE:** if you work with untrusted user spec, use `untrusted-spec` [option](#redoc-options-object) to prevent XSS security risks.
|
||||||
|
|
||||||
### 1. Install ReDoc (skip this step for CDN)
|
### 1. Install ReDoc (skip this step for CDN)
|
||||||
Install using [yarn](https://yarnpkg.com):
|
Install using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
|
||||||
|
|
||||||
|
npm i redoc
|
||||||
|
|
||||||
|
or using [yarn](https://yarnpkg.com):
|
||||||
|
|
||||||
yarn add redoc
|
yarn add redoc
|
||||||
|
|
||||||
or using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
|
|
||||||
|
|
||||||
npm install redoc --save
|
|
||||||
|
|
||||||
### 2. Reference redoc script in HTML
|
### 2. Reference redoc script in HTML
|
||||||
For **CDN**:
|
For **CDN**:
|
||||||
```html
|
```html
|
||||||
|
@ -138,7 +138,7 @@ For npm:
|
||||||
|
|
||||||
Install peer dependencies required by ReDoc if you don't have them installed already:
|
Install peer dependencies required by ReDoc if you don't have them installed already:
|
||||||
|
|
||||||
npm i react react-dom mobx@^4.2.0 styled-components
|
npm i react react-dom mobx@^4.2.0 styled-components core-js
|
||||||
|
|
||||||
Import `RedocStandalone` component from 'redoc' module:
|
Import `RedocStandalone` component from 'redoc' module:
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@ ReDoc makes use of the following [vendor extensions](https://swagger.io/specific
|
||||||
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
|
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
|
||||||
* [`x-code-samples`](docs/redoc-vendor-extensions.md#x-code-samples) - specify operation code samples
|
* [`x-code-samples`](docs/redoc-vendor-extensions.md#x-code-samples) - specify operation code samples
|
||||||
* [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests
|
* [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests
|
||||||
* [`x-nullable`](docs/redoc-vendor-extensions.md#nullable) - mark schema param as a nullable
|
* [`x-nullable`](docs/redoc-vendor-extensions.md#x-nullable) - mark schema param as a nullable
|
||||||
* [`x-displayName`](docs/redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
|
* [`x-displayName`](docs/redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
|
||||||
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
|
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
|
||||||
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
||||||
|
@ -223,27 +223,33 @@ ReDoc makes use of the following [vendor extensions](https://swagger.io/specific
|
||||||
### `<redoc>` options object
|
### `<redoc>` options object
|
||||||
You can use all of the following options with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`.
|
You can use all of the following options with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`.
|
||||||
|
|
||||||
* `untrustedSpec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!**
|
* `disableSearch` - disable search indexing and search box.
|
||||||
|
* `expandDefaultServerVariables` - enable expanding default server variables, default `false`.
|
||||||
|
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
||||||
|
* `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
|
||||||
|
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
||||||
|
* `hideLoading` - do not show loading animation. Useful for small docs.
|
||||||
|
* `hideSingleRequestSampleTab` - do not show the request sample tab for requests with only one sample.
|
||||||
|
* `expandSingleSchemaField` - automatically expand single field in a schema
|
||||||
|
* `jsonSampleExpandLevel` - set the default expand level for JSON payload samples (responses and request body). Special value 'all' expands all levels. The default value is `2`.
|
||||||
|
* `lazyRendering` - _Not implemented yet_ ~~if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\redocly.github.io/redoc) for the example.~~
|
||||||
|
* `menuToggle` - if true clicking second time on expanded menu item will collapse it, default `false`.
|
||||||
|
* `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs).
|
||||||
|
* `noAutoAuth` - do not inject Authentication section automatically.
|
||||||
|
* `onlyRequiredInSamples` - shows only required fields in request samples.
|
||||||
|
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one.
|
||||||
|
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
|
||||||
* `scrollYOffset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc;
|
* `scrollYOffset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc;
|
||||||
`scrollYOffset` can be specified in various ways:
|
`scrollYOffset` can be specified in various ways:
|
||||||
* **number**: A fixed number of pixels to be used as offset;
|
* **number**: A fixed number of pixels to be used as offset.
|
||||||
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset;
|
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset.
|
||||||
* **function**: A getter function. Must return a number representing the offset (in pixels);
|
* **function**: A getter function. Must return a number representing the offset (in pixels).
|
||||||
|
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display.
|
||||||
|
* `sortPropsAlphabetically` - sort properties alphabetically.
|
||||||
* `suppressWarnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console).
|
* `suppressWarnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console).
|
||||||
* `lazyRendering` - _Not implemented yet_ ~~if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\redocly.github.io/redoc) for the example.~~
|
* `payloadSampleIdx` - if set, payload sample will be inserted at this index or last. Indexes start from 0.
|
||||||
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
* `theme` - ReDoc theme. Not documented yet. For details check source code: [theme.ts](https://github.com/Redocly/redoc/blob/master/src/theme.ts).
|
||||||
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
* `untrustedSpec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!**
|
||||||
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
|
|
||||||
* `sortPropsAlphabetically` - sort properties alphabetically
|
|
||||||
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display
|
|
||||||
* `noAutoAuth` - do not inject Authentication section automatically
|
|
||||||
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one
|
|
||||||
* `hideLoading` - do not show loading animation. Useful for small docs
|
|
||||||
* `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs)
|
|
||||||
* `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
|
|
||||||
* `disableSearch` - disable search indexing and search box
|
|
||||||
* `onlyRequiredInSamples` - shows only required fields in request samples.
|
|
||||||
* `theme` - ReDoc theme. Not documented yet. For details check source code: [theme.ts](https://github.com/Redocly/redoc/blob/master/src/theme.ts)
|
|
||||||
|
|
||||||
## Advanced usage of standalone version
|
## Advanced usage of standalone version
|
||||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
||||||
|
|
|
@ -17,7 +17,7 @@ const localDistDir = './benchmark/revisions/local/bundles';
|
||||||
sh.rm('-rf', localDistDir);
|
sh.rm('-rf', localDistDir);
|
||||||
console.log(`Building local dist: ${localDistDir}`);
|
console.log(`Building local dist: ${localDistDir}`);
|
||||||
sh.mkdir('-p', localDistDir);
|
sh.mkdir('-p', localDistDir);
|
||||||
exec(`yarn bundle:lib --output-path ${localDistDir}`);
|
exec(`npm run bundle:lib --output-path ${localDistDir}`);
|
||||||
|
|
||||||
const revisions = [];
|
const revisions = [];
|
||||||
for (const arg of args) {
|
for (const arg of args) {
|
||||||
|
@ -32,7 +32,7 @@ const configDir = './benchmark/revisions/config.js';
|
||||||
console.log(`Writing config "${configDir}"`);
|
console.log(`Writing config "${configDir}"`);
|
||||||
fs.writeFileSync(configDir, configFile);
|
fs.writeFileSync(configDir, configFile);
|
||||||
|
|
||||||
console.log('Starging benchmark server');
|
console.log('Starting benchmark server');
|
||||||
const proc = spawn('npm', ['run', 'start:benchmark']);
|
const proc = spawn('npm', ['run', 'start:benchmark']);
|
||||||
|
|
||||||
proc.stdout.on('data', data => {
|
proc.stdout.on('data', data => {
|
||||||
|
@ -119,7 +119,7 @@ function buildRevisionDist(revision) {
|
||||||
|
|
||||||
const pwd = sh.pwd();
|
const pwd = sh.pwd();
|
||||||
sh.cd(buildDir);
|
sh.cd(buildDir);
|
||||||
exec('yarn remove cypress puppeteer && yarn && yarn bundle:lib');
|
exec('npm uninstall cypress puppeteer && npm install && npm run bundle:lib');
|
||||||
sh.cd(pwd);
|
sh.cd(pwd);
|
||||||
return distDir;
|
return distDir;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
# To run:
|
# To run:
|
||||||
# To display the command line options:
|
# To display the command line options:
|
||||||
# $ docker run --rm -it redoc-cli --help
|
# $ docker run --rm -it redoc-cli --help
|
||||||
# .. will display the comand line help
|
# .. will display the command line help
|
||||||
#
|
#
|
||||||
# To turn `swagger.yml` file in the current directory, to html documentation 'redoc-static.html'
|
# To turn `swagger.yml` file in the current directory, to html documentation 'redoc-static.html'
|
||||||
# $ docker run --rm -it -v $PWD:/data redoc-cli bundle swagger.yml
|
# $ docker run --rm -it -v $PWD:/data redoc-cli bundle swagger.yml
|
||||||
|
|
96
cli/index.ts
96
cli/index.ts
|
@ -14,7 +14,14 @@ import * as zlib from 'zlib';
|
||||||
import { createStore, loadAndBundleSpec, Redoc } from 'redoc';
|
import { createStore, loadAndBundleSpec, Redoc } from 'redoc';
|
||||||
|
|
||||||
import { watch } from 'chokidar';
|
import { watch } from 'chokidar';
|
||||||
import { createReadStream, existsSync, readFileSync, ReadStream, writeFileSync } from 'fs';
|
import {
|
||||||
|
createReadStream,
|
||||||
|
existsSync,
|
||||||
|
lstatSync,
|
||||||
|
readFileSync,
|
||||||
|
ReadStream,
|
||||||
|
writeFileSync,
|
||||||
|
} from 'fs';
|
||||||
import * as mkdirp from 'mkdirp';
|
import * as mkdirp from 'mkdirp';
|
||||||
|
|
||||||
import * as YargsParser from 'yargs';
|
import * as YargsParser from 'yargs';
|
||||||
|
@ -25,6 +32,7 @@ interface Options {
|
||||||
cdn?: boolean;
|
cdn?: boolean;
|
||||||
output?: string;
|
output?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
disableGoogleFont?: boolean;
|
||||||
port?: number;
|
port?: number;
|
||||||
templateFileName?: string;
|
templateFileName?: string;
|
||||||
templateOptions?: any;
|
templateOptions?: any;
|
||||||
|
@ -68,9 +76,11 @@ YargsParser.command(
|
||||||
watch: argv.watch as boolean,
|
watch: argv.watch as boolean,
|
||||||
templateFileName: argv.template as string,
|
templateFileName: argv.template as string,
|
||||||
templateOptions: argv.templateOptions || {},
|
templateOptions: argv.templateOptions || {},
|
||||||
redocOptions: argv.options || {},
|
redocOptions: getObjectOrJSON(argv.options),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(config);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await serve(argv.port as number, argv.spec as string, config);
|
await serve(argv.port as number, argv.spec as string, config);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -96,7 +106,12 @@ YargsParser.command(
|
||||||
yargs.options('title', {
|
yargs.options('title', {
|
||||||
describe: 'Page Title',
|
describe: 'Page Title',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
default: 'ReDoc documentation',
|
});
|
||||||
|
|
||||||
|
yargs.options('disableGoogleFont', {
|
||||||
|
describe: 'Disable Google Font',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
yargs.option('cdn', {
|
yargs.option('cdn', {
|
||||||
|
@ -108,15 +123,16 @@ YargsParser.command(
|
||||||
yargs.demandOption('spec');
|
yargs.demandOption('spec');
|
||||||
return yargs;
|
return yargs;
|
||||||
},
|
},
|
||||||
async argv => {
|
async (argv: any) => {
|
||||||
const config: Options = {
|
const config = {
|
||||||
ssr: true,
|
ssr: true,
|
||||||
output: argv.o as string,
|
output: argv.o as string,
|
||||||
cdn: argv.cdn as boolean,
|
cdn: argv.cdn as boolean,
|
||||||
title: argv.title as string,
|
title: argv.title as string,
|
||||||
|
disableGoogleFont: argv.disableGoogleFont as boolean,
|
||||||
templateFileName: argv.template as string,
|
templateFileName: argv.template as string,
|
||||||
templateOptions: argv.templateOptions || {},
|
templateOptions: argv.templateOptions || {},
|
||||||
redocOptions: argv.options || {},
|
redocOptions: getObjectOrJSON(argv.options),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -156,7 +172,9 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (request.url === '/') {
|
} else if (request.url === '/') {
|
||||||
respondWithGzip(pageHTML, request, response);
|
respondWithGzip(pageHTML, request, response, {
|
||||||
|
'Content-Type': 'text/html',
|
||||||
|
});
|
||||||
} else if (request.url === '/spec.json') {
|
} else if (request.url === '/spec.json') {
|
||||||
const specStr = JSON.stringify(spec, null, 2);
|
const specStr = JSON.stringify(spec, null, 2);
|
||||||
respondWithGzip(specStr, request, response, {
|
respondWithGzip(specStr, request, response, {
|
||||||
|
@ -178,21 +196,34 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
||||||
if (options.watch && existsSync(pathToSpec)) {
|
if (options.watch && existsSync(pathToSpec)) {
|
||||||
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
||||||
const watchOptions = {
|
const watchOptions = {
|
||||||
ignored: /(^|[\/\\])\../,
|
ignored: [/(^|[\/\\])\../, /___jb_[a-z]+___$/],
|
||||||
|
ignoreInitial: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const watcher = watch(pathToSpecDirectory, watchOptions);
|
const watcher = watch(pathToSpecDirectory, watchOptions);
|
||||||
const log = console.log.bind(console);
|
const log = console.log.bind(console);
|
||||||
|
|
||||||
|
const handlePath = async _path => {
|
||||||
|
try {
|
||||||
|
spec = await loadAndBundleSpec(pathToSpec);
|
||||||
|
pageHTML = await getPageHTML(spec, pathToSpec, options);
|
||||||
|
log('Updated successfully');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error while updating: ', e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
watcher
|
watcher
|
||||||
.on('change', async path => {
|
.on('change', async path => {
|
||||||
log(`${path} changed, updating docs`);
|
log(`${path} changed, updating docs`);
|
||||||
try {
|
handlePath(path);
|
||||||
spec = await loadAndBundleSpec(pathToSpec);
|
})
|
||||||
pageHTML = await getPageHTML(spec, pathToSpec, options);
|
.on('add', async path => {
|
||||||
log('Updated successfully');
|
log(`File ${path} added, updating docs`);
|
||||||
} catch (e) {
|
handlePath(path);
|
||||||
console.error('Error while updating: ', e.message);
|
})
|
||||||
}
|
.on('addDir', path => {
|
||||||
|
log(`↗ Directory ${path} added. Files in here will trigger reload.`);
|
||||||
})
|
})
|
||||||
.on('error', error => console.error(`Watcher error: ${error}`))
|
.on('error', error => console.error(`Watcher error: ${error}`))
|
||||||
.on('ready', () => log(`👀 Watching ${pathToSpecDirectory} for changes...`));
|
.on('ready', () => log(`👀 Watching ${pathToSpecDirectory} for changes...`));
|
||||||
|
@ -216,7 +247,15 @@ async function bundle(pathToSpec, options: Options = {}) {
|
||||||
async function getPageHTML(
|
async function getPageHTML(
|
||||||
spec: any,
|
spec: any,
|
||||||
pathToSpec: string,
|
pathToSpec: string,
|
||||||
{ ssr, cdn, title, templateFileName, templateOptions, redocOptions = {} }: Options,
|
{
|
||||||
|
ssr,
|
||||||
|
cdn,
|
||||||
|
title,
|
||||||
|
disableGoogleFont,
|
||||||
|
templateFileName,
|
||||||
|
templateOptions,
|
||||||
|
redocOptions = {},
|
||||||
|
}: Options,
|
||||||
) {
|
) {
|
||||||
let html;
|
let html;
|
||||||
let css;
|
let css;
|
||||||
|
@ -258,7 +297,8 @@ async function getPageHTML(
|
||||||
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
|
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
|
||||||
: `<script>${redocStandaloneSrc}</script>`) + css
|
: `<script>${redocStandaloneSrc}</script>`) + css
|
||||||
: '<script src="redoc.standalone.js"></script>',
|
: '<script src="redoc.standalone.js"></script>',
|
||||||
title,
|
title: title || spec.info.title || 'ReDoc documentation',
|
||||||
|
disableGoogleFont,
|
||||||
templateOptions,
|
templateOptions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -321,3 +361,25 @@ function handleError(error: Error) {
|
||||||
console.error(error.stack);
|
console.error(error.stack);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getObjectOrJSON(options) {
|
||||||
|
switch (typeof options) {
|
||||||
|
case 'object':
|
||||||
|
return options;
|
||||||
|
case 'string':
|
||||||
|
try {
|
||||||
|
if (existsSync(options) && lstatSync(options).isFile()) {
|
||||||
|
return JSON.parse(readFileSync(options, 'utf-8'));
|
||||||
|
} else {
|
||||||
|
return JSON.parse(options);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
`Encountered error:\n\n${options}\n\nis neither a file with a valid JSON object neither a stringified JSON object.`,
|
||||||
|
);
|
||||||
|
handleError(e);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
2246
cli/package-lock.json
generated
Normal file
2246
cli/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,36 +1,35 @@
|
||||||
{
|
{
|
||||||
"name": "redoc-cli",
|
"name": "redoc-cli",
|
||||||
"version": "0.8.4",
|
"version": "0.9.7",
|
||||||
"description": "ReDoc's Command Line Interface",
|
"description": "ReDoc's Command Line Interface",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
"repository": "https://github.com/Redocly/redoc",
|
"repository": "https://github.com/Redocly/redoc",
|
||||||
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^2.0.4",
|
"chokidar": "^3.0.2",
|
||||||
"handlebars": "^4.0.11",
|
"handlebars": "^4.1.2",
|
||||||
"isarray": "^2.0.4",
|
"isarray": "^2.0.5",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mobx": "^4.2.0",
|
"mobx": "^4.2.0",
|
||||||
"node-libs-browser": "^2.2.0",
|
"node-libs-browser": "^2.2.1",
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.6",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.8.6",
|
||||||
"redoc": "^2.0.0-rc.8-1",
|
"redoc": "2.0.0-rc.24",
|
||||||
"styled-components": "^4.1.3",
|
"styled-components": "^4.3.2",
|
||||||
"tslib": "^1.9.3",
|
"tslib": "^1.10.0",
|
||||||
"yargs": "^12.0.5"
|
"yargs": "^13.3.0"
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"ci-publish": "ci-publish"
|
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chokidar": "^1.7.5",
|
"@types/chokidar": "^2.1.3",
|
||||||
"@types/handlebars": "^4.0.39",
|
"@types/handlebars": "^4.0.39",
|
||||||
"@types/mkdirp": "^0.5.2",
|
"@types/mkdirp": "^0.5.2"
|
||||||
"ci-publish": "^1.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{{{redocHead}}}
|
{{{redocHead}}}
|
||||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
{{#unless disableGoogleFont}}<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">{{/unless}}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
4756
cli/yarn.lock
4756
cli/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -11,8 +11,8 @@ RUN apk update && apk add --no-cache git
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
COPY package.json yarn.lock /build/
|
COPY package.json package-lock.json /build/
|
||||||
RUN yarn install --frozen-lockfile --ignore-optional --ignore-scripts
|
RUN npm ci --no-optional --ignore-scripts
|
||||||
|
|
||||||
# copy only required for the build files
|
# copy only required for the build files
|
||||||
COPY src /build/src
|
COPY src /build/src
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# DockerHub cd into Dockerfile location before buil
|
# DockerHub cd into Dockerfile location before build
|
||||||
# So we have to undo this.
|
# So we have to undo this.
|
||||||
cd ../..
|
cd ../..
|
||||||
docker build -f config/docker/Dockerfile -t $IMAGE_NAME .
|
docker build -f config/docker/Dockerfile -t $IMAGE_NAME .
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Contacts",
|
"name": "Contacts",
|
||||||
"description": "Contacts are Customer's address book.\nAll contact information used in Invoices, Subscriptions, Transacions, etc is enlisted here. Hovewer, changing a Contact won't change corresponding contact information in related resources\n"
|
"description": "Contacts are Customer's address book.\nAll contact information used in Invoices, Subscriptions, Transacions, etc is enlisted here. However, changing a Contact won't change corresponding contact information in related resources\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Coupons",
|
"name": "Coupons",
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Files",
|
"name": "Files",
|
||||||
"description": "A File is an entity that can store a phyiscal file and some metadata. It also provides an easy access to\nits size, mime-type, user-defined tags and description thus allowing easy sorting and searching among stored\nfiles.\nThere are several methods of file uploading available: multipart/form-data encoded form, RAW POST (by sending\nfile contents as POST body), fetching from URL (by providing the file URL via 'url' param)\nAttachment is an entity that is used to link a File to one or multiple objects like Customer, Dispute, Payment,\nTransaction, Subscription, Plan, Product, Invoice, Note. That allows to quickly find and use files related to\nthose specific entities.\n"
|
"description": "A File is an entity that can store a physical file and some metadata. It also provides an easy access to\nits size, mime-type, user-defined tags and description thus allowing easy sorting and searching among stored\nfiles.\nThere are several methods of file uploading available: multipart/form-data encoded form, RAW POST (by sending\nfile contents as POST body), fetching from URL (by providing the file URL via 'url' param)\nAttachment is an entity that is used to link a File to one or multiple objects like Customer, Dispute, Payment,\nTransaction, Subscription, Plan, Product, Invoice, Note. That allows to quickly find and use files related to\nthose specific entities.\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Gateway Accounts",
|
"name": "Gateway Accounts",
|
||||||
|
@ -15623,7 +15623,7 @@
|
||||||
"description": "Reset user password\n",
|
"description": "Reset user password\n",
|
||||||
"responses": {
|
"responses": {
|
||||||
"201": {
|
"201": {
|
||||||
"description": "Password was reseted successfully",
|
"description": "Password was reset successfully",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Rate-Limit-Limit": {
|
"Rate-Limit-Limit": {
|
||||||
"description": "The number of allowed requests in the current period",
|
"description": "The number of allowed requests in the current period",
|
||||||
|
@ -23851,17 +23851,17 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"totpRequired": {
|
"totpRequired": {
|
||||||
"description": "The user setting of two-factor authentification",
|
"description": "The user setting of two-factor authentication",
|
||||||
"readOnly": true,
|
"readOnly": true,
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"totpSecret": {
|
"totpSecret": {
|
||||||
"description": "The user TOTP key for authentification app (if TOTP enabled)",
|
"description": "The user TOTP key for authentication app (if TOTP enabled)",
|
||||||
"readOnly": true,
|
"readOnly": true,
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"totpUrl": {
|
"totpUrl": {
|
||||||
"description": "The user link to QR-code for TOTP authentification app (if TOTP enabled)",
|
"description": "The user link to QR-code for TOTP authentication app (if TOTP enabled)",
|
||||||
"readOnly": true,
|
"readOnly": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "url"
|
"format": "url"
|
||||||
|
@ -24250,7 +24250,7 @@
|
||||||
},
|
},
|
||||||
"bodyHtml": {
|
"bodyHtml": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Leave empty to recieve \"text/plain\" email.\nThe template palceholders are allowed.\n"
|
"description": "Leave empty to receive \"text/plain\" email.\nThe template palceholders are allowed.\n"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -26495,15 +26495,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"totpRequired": {
|
"totpRequired": {
|
||||||
"description": "The user setting of two-factor authentification",
|
"description": "The user setting of two-factor authentication",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"totpSecret": {
|
"totpSecret": {
|
||||||
"description": "The user TOTP key for authentification app (if TOTP enabled)",
|
"description": "The user TOTP key for authentication app (if TOTP enabled)",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"totpUrl": {
|
"totpUrl": {
|
||||||
"description": "The user link to QR-code for TOTP authentification app (if TOTP enabled)",
|
"description": "The user link to QR-code for TOTP authentication app (if TOTP enabled)",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "url"
|
"format": "url"
|
||||||
},
|
},
|
||||||
|
@ -26984,7 +26984,7 @@
|
||||||
"collectionExpand": {
|
"collectionExpand": {
|
||||||
"name": "expand",
|
"name": "expand",
|
||||||
"in": "query",
|
"in": "query",
|
||||||
"description": "Expand response to get full related object intead of ID. See the expand guide for more info.",
|
"description": "Expand response to get full related object instead of ID. See the expand guide for more info.",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ const demos = [
|
||||||
value: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml',
|
value: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml',
|
||||||
label: 'Google Calendar',
|
label: 'Google Calendar',
|
||||||
},
|
},
|
||||||
{ value: 'https://api.apis.guru/v2/specs/slack.com/1.0.6/swagger.yaml', label: 'Slack' },
|
{ value: 'https://api.apis.guru/v2/specs/slack.com/1.2.0/swagger.yaml', label: 'Slack' },
|
||||||
{ value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/swagger.yaml', label: 'Zoom.us' },
|
{ value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/swagger.yaml', label: 'Zoom.us' },
|
||||||
{
|
{
|
||||||
value: 'https://api.apis.guru/v2/specs/graphhopper.com/1.0/swagger.yaml',
|
value: 'https://api.apis.guru/v2/specs/graphhopper.com/1.0/swagger.yaml',
|
||||||
|
|
|
@ -38,7 +38,7 @@ info:
|
||||||
OAuth2 - an open protocol to allow secure authorization in a simple
|
OAuth2 - an open protocol to allow secure authorization in a simple
|
||||||
and standard method from web, mobile and desktop applications.
|
and standard method from web, mobile and desktop applications.
|
||||||
|
|
||||||
<security-definitions />
|
<SecurityDefinitions />
|
||||||
|
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
title: Swagger Petstore
|
title: Swagger Petstore
|
||||||
|
@ -63,6 +63,14 @@ tags:
|
||||||
description: Access to Petstore orders
|
description: Access to Petstore orders
|
||||||
- name: user
|
- name: user
|
||||||
description: Operations about user
|
description: Operations about user
|
||||||
|
- name: pet_model
|
||||||
|
x-displayName: The Pet Model
|
||||||
|
description: |
|
||||||
|
<SchemaDefinition schemaRef="#/components/schemas/Pet" />
|
||||||
|
- name: store_model
|
||||||
|
x-displayName: The Order Model
|
||||||
|
description: |
|
||||||
|
<SchemaDefinition schemaRef="#/components/schemas/Order" exampleRef="#/components/examples/Order" showReadOnly={true} showWriteOnly={true} />
|
||||||
x-tagGroups:
|
x-tagGroups:
|
||||||
- name: General
|
- name: General
|
||||||
tags:
|
tags:
|
||||||
|
@ -71,9 +79,21 @@ x-tagGroups:
|
||||||
- name: User Management
|
- name: User Management
|
||||||
tags:
|
tags:
|
||||||
- user
|
- user
|
||||||
|
- name: Models
|
||||||
|
tags:
|
||||||
|
- pet_model
|
||||||
|
- store_model
|
||||||
paths:
|
paths:
|
||||||
/pet:
|
/pet:
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: Accept-Language
|
||||||
|
in: header
|
||||||
|
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
|
||||||
|
example: en-US
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
default: en-AU
|
||||||
- name: cookieParam
|
- name: cookieParam
|
||||||
in: cookie
|
in: cookie
|
||||||
description: Some cookie
|
description: Some cookie
|
||||||
|
@ -286,7 +306,7 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
summary: Finds Pets by status
|
summary: Finds Pets by status
|
||||||
description: Multiple status values can be provided with comma seperated strings
|
description: Multiple status values can be provided with comma separated strings
|
||||||
operationId: findPetsByStatus
|
operationId: findPetsByStatus
|
||||||
parameters:
|
parameters:
|
||||||
- name: status
|
- name: status
|
||||||
|
@ -331,7 +351,7 @@ paths:
|
||||||
- pet
|
- pet
|
||||||
summary: Finds Pets by tags
|
summary: Finds Pets by tags
|
||||||
description: >-
|
description: >-
|
||||||
Muliple tags can be provided with comma seperated strings. Use tag1,
|
Multiple tags can be provided with comma separated strings. Use tag1,
|
||||||
tag2, tag3 for testing.
|
tag2, tag3 for testing.
|
||||||
operationId: findPetsByTags
|
operationId: findPetsByTags
|
||||||
deprecated: true
|
deprecated: true
|
||||||
|
@ -611,7 +631,7 @@ paths:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
X-Expires-After:
|
X-Expires-After:
|
||||||
description: date in UTC when toekn expires
|
description: date in UTC when token expires
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
@ -666,6 +686,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
description: The measured skill for hunting
|
description: The measured skill for hunting
|
||||||
default: lazy
|
default: lazy
|
||||||
|
example: adventurous
|
||||||
enum:
|
enum:
|
||||||
- clueless
|
- clueless
|
||||||
- lazy
|
- lazy
|
||||||
|
@ -717,6 +738,7 @@ components:
|
||||||
type: number
|
type: number
|
||||||
description: Average amount of honey produced per day in ounces
|
description: Average amount of honey produced per day in ounces
|
||||||
example: 3.14
|
example: 3.14
|
||||||
|
multipleOf: .01
|
||||||
required:
|
required:
|
||||||
- honeyPerDay
|
- honeyPerDay
|
||||||
Id:
|
Id:
|
||||||
|
@ -754,6 +776,11 @@ components:
|
||||||
description: Indicates whenever order was completed or not
|
description: Indicates whenever order was completed or not
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
readOnly: true
|
||||||
|
requestId:
|
||||||
|
description: Unique Request Id
|
||||||
|
type: string
|
||||||
|
writeOnly: true
|
||||||
xml:
|
xml:
|
||||||
name: Order
|
name: Order
|
||||||
Pet:
|
Pet:
|
||||||
|
@ -866,14 +893,13 @@ components:
|
||||||
as well as digits
|
as well as digits
|
||||||
format: password
|
format: password
|
||||||
minLength: 8
|
minLength: 8
|
||||||
pattern: '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'
|
pattern: '/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/'
|
||||||
example: drowssaP123
|
example: drowssaP123
|
||||||
phone:
|
phone:
|
||||||
description: User phone number in international format
|
description: User phone number in international format
|
||||||
type: string
|
type: string
|
||||||
pattern: '^\+(?:[0-9]-?){6,14}[0-9]$'
|
pattern: '/^\+(?:[0-9]-?){6,14}[0-9]$/'
|
||||||
example: +1-202-555-0192
|
example: +1-202-555-0192
|
||||||
nullable: true
|
|
||||||
userStatus:
|
userStatus:
|
||||||
description: User status
|
description: User status
|
||||||
type: integer
|
type: integer
|
||||||
|
@ -926,3 +952,10 @@ components:
|
||||||
type: apiKey
|
type: apiKey
|
||||||
name: api_key
|
name: api_key
|
||||||
in: header
|
in: header
|
||||||
|
examples:
|
||||||
|
Order:
|
||||||
|
value:
|
||||||
|
quantity: 1,
|
||||||
|
shipDate: 2018-10-19T16:46:45Z,
|
||||||
|
status: placed,
|
||||||
|
complete: false
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
|
// tslint:disable-next-line
|
||||||
import { AppContainer } from 'react-hot-loader';
|
import { AppContainer } from 'react-hot-loader';
|
||||||
// import DevTools from 'mobx-react-devtools';
|
// import DevTools from 'mobx-react-devtools';
|
||||||
|
|
||||||
|
|
|
@ -631,7 +631,7 @@ paths:
|
||||||
X-Expires-After:
|
X-Expires-After:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
description: date in UTC when toekn expires
|
description: date in UTC when token expires
|
||||||
'400':
|
'400':
|
||||||
description: Invalid username/password supplied
|
description: Invalid username/password supplied
|
||||||
/user/logout:
|
/user/logout:
|
||||||
|
|
|
@ -38,7 +38,6 @@ const babelLoader = mode => ({
|
||||||
['@babel/plugin-syntax-decorators', { legacy: true }],
|
['@babel/plugin-syntax-decorators', { legacy: true }],
|
||||||
'@babel/plugin-syntax-dynamic-import',
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
'@babel/plugin-syntax-jsx',
|
'@babel/plugin-syntax-jsx',
|
||||||
mode !== 'production' ? 'react-hot-loader/babel' : undefined,
|
|
||||||
[
|
[
|
||||||
'babel-plugin-styled-components',
|
'babel-plugin-styled-components',
|
||||||
{
|
{
|
||||||
|
@ -50,6 +49,13 @@ const babelLoader = mode => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const babelHotLoader = {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
plugins: ['react-hot-loader/babel'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
|
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
|
||||||
entry: [
|
entry: [
|
||||||
root('../src/polyfills.ts'),
|
root('../src/polyfills.ts'),
|
||||||
|
@ -105,7 +111,11 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
use: [tsLoader(env), babelLoader(mode)],
|
use: compact([
|
||||||
|
mode !== 'production' ? babelHotLoader : undefined,
|
||||||
|
tsLoader(env),
|
||||||
|
babelLoader(mode),
|
||||||
|
]),
|
||||||
exclude: [/node_modules/],
|
exclude: [/node_modules/],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,7 +6,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')
|
.find('li')
|
||||||
.should('have.length', 6 + (2 + 8 + 4) + (1 + 8));
|
.should('have.length', 6 + (2 + 8 + 1 + 4 + 2) + (1 + 8));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync active menu items while scroll', () => {
|
it('should sync active menu items while scroll', () => {
|
||||||
|
|
|
@ -42,7 +42,7 @@ describe('Servers', () => {
|
||||||
// TODO add cy-data attributes
|
// TODO add cy-data attributes
|
||||||
cy.get('[data-section-id="operation/addPet"]').should(
|
cy.get('[data-section-id="operation/addPet"]').should(
|
||||||
'contain',
|
'contain',
|
||||||
'http://localhost:' + win.location.port + '/e2e/pet',
|
'http://localhost:' + win.location.port + '/pet',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -57,7 +57,7 @@ describe('Servers', () => {
|
||||||
// TODO add cy-data attributes
|
// TODO add cy-data attributes
|
||||||
cy.get('[data-section-id="operation/addPet"]').should(
|
cy.get('[data-section-id="operation/addPet"]').should(
|
||||||
'contain',
|
'contain',
|
||||||
'http://localhost:' + win.location.port + '/e2e/pet',
|
'http://localhost:' + win.location.port + '/pet',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,6 +27,8 @@ describe('Search', () => {
|
||||||
it('should support arrow navigation', () => {
|
it('should support arrow navigation', () => {
|
||||||
getSearchInput().type('int', { force: true });
|
getSearchInput().type('int', { force: true });
|
||||||
|
|
||||||
|
cy.wait(500);
|
||||||
|
|
||||||
getSearchInput().type('{downarrow}', { force: true });
|
getSearchInput().type('{downarrow}', { force: true });
|
||||||
getResult(0).should('have.class', 'active');
|
getResult(0).should('have.class', 'active');
|
||||||
|
|
||||||
|
|
17990
package-lock.json
generated
Normal file
17990
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
159
package.json
159
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "otx-redoc",
|
"name": "redoc",
|
||||||
"version": "2.0.0-rc.2",
|
"version": "2.0.0-rc.24",
|
||||||
"description": "ReDoc",
|
"description": "ReDoc",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
"start": "webpack-dev-server --mode=development --env.playground --hot --config demo/webpack.config.ts",
|
"start": "webpack-dev-server --mode=development --env.playground --hot --config demo/webpack.config.ts",
|
||||||
"start:prod": "webpack-dev-server --env.playground --mode=production --config demo/webpack.config.ts",
|
"start:prod": "webpack-dev-server --env.playground --mode=production --config demo/webpack.config.ts",
|
||||||
"start:benchmark": "webpack-dev-server --mode=production --env.bench --config demo/webpack.config.ts",
|
"start:benchmark": "webpack-dev-server --mode=production --env.bench --config demo/webpack.config.ts",
|
||||||
"test": "npm run lint && npm run unit && npm run bundlesize && npm run license-check",
|
"test": "npm run lint && npm run unit && npm run license-check",
|
||||||
"unit": "jest --coverage",
|
"unit": "jest --coverage",
|
||||||
"e2e": "cypress run",
|
"e2e": "cypress run",
|
||||||
"e2e-ci": "cypress run --record",
|
"e2e-ci": "cypress run --record",
|
||||||
|
@ -40,9 +40,9 @@
|
||||||
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone",
|
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone",
|
||||||
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
|
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
|
||||||
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json",
|
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json",
|
||||||
"prettier": "prettier --write \"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": "tslint --project tsconfig.json",
|
"lint": "eslint 'src/**/*.{js,ts,tsx}'",
|
||||||
"benchmark": "node ./benchmark/benchmark.js",
|
"benchmark": "node ./benchmark/benchmark.js",
|
||||||
"start:demo": "webpack-dev-server --hot --config demo/webpack.config.ts --mode=development",
|
"start:demo": "webpack-dev-server --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",
|
||||||
|
@ -52,80 +52,83 @@
|
||||||
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ."
|
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.3.4",
|
"@babel/core": "7.8.7",
|
||||||
"@babel/plugin-syntax-decorators": "7.2.0",
|
"@babel/plugin-syntax-decorators": "7.8.3",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||||
"@babel/plugin-syntax-jsx": "7.2.0",
|
"@babel/plugin-syntax-jsx": "7.8.3",
|
||||||
"@babel/plugin-syntax-typescript": "7.3.3",
|
"@babel/plugin-syntax-typescript": "7.8.3",
|
||||||
"@cypress/webpack-preprocessor": "4.0.3",
|
"@cypress/webpack-preprocessor": "4.1.3",
|
||||||
"@hot-loader/react-dom": "^16.8.4",
|
"@hot-loader/react-dom": "^16.12.0",
|
||||||
"@types/chai": "4.1.7",
|
"@types/chai": "4.2.10",
|
||||||
"@types/dompurify": "^0.0.32",
|
"@types/dompurify": "^2.0.1",
|
||||||
"@types/enzyme": "^3.9.0",
|
"@types/enzyme": "^3.10.5",
|
||||||
"@types/enzyme-to-json": "^1.5.3",
|
"@types/enzyme-to-json": "^1.5.3",
|
||||||
"@types/jest": "^24.0.11",
|
"@types/jest": "^25.1.4",
|
||||||
"@types/json-pointer": "^1.0.30",
|
"@types/json-pointer": "^1.0.30",
|
||||||
"@types/lodash": "^4.14.122",
|
"@types/lodash": "^4.14.149",
|
||||||
"@types/lunr": "^2.3.2",
|
"@types/lunr": "^2.3.2",
|
||||||
"@types/mark.js": "^8.11.3",
|
"@types/mark.js": "^8.11.5",
|
||||||
"@types/marked": "^0.6.3",
|
"@types/marked": "^0.7.3",
|
||||||
"@types/prismjs": "^1.9.1",
|
"@types/prismjs": "^1.16.0",
|
||||||
"@types/prop-types": "^15.7.0",
|
"@types/prop-types": "^15.7.3",
|
||||||
"@types/react": "^16.8.7",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-dom": "^16.8.2",
|
"@types/react-dom": "^16.9.5",
|
||||||
"@types/react-hot-loader": "^4.1.0",
|
|
||||||
"@types/react-tabs": "^2.3.1",
|
"@types/react-tabs": "^2.3.1",
|
||||||
"@types/styled-components": "^4.1.12",
|
"@types/styled-components": "^4.4.1",
|
||||||
"@types/tapable": "1.0.4",
|
"@types/tapable": "1.0.5",
|
||||||
"@types/webpack": "^4.4.25",
|
"@types/webpack": "^4.41.7",
|
||||||
"@types/webpack-env": "^1.13.9",
|
"@types/webpack-env": "^1.15.1",
|
||||||
"@types/yargs": "^12.0.9",
|
"@types/yargs": "^13.0.3",
|
||||||
"babel-loader": "8.0.5",
|
"@typescript-eslint/eslint-plugin": "^2.24.0",
|
||||||
"babel-plugin-styled-components": "^1.10.0",
|
"@typescript-eslint/parser": "^2.24.0",
|
||||||
|
"babel-loader": "8.0.6",
|
||||||
|
"babel-plugin-styled-components": "^1.10.7",
|
||||||
"beautify-benchmark": "^0.2.4",
|
"beautify-benchmark": "^0.2.4",
|
||||||
"bundlesize": "^0.17.1",
|
"bundlesize": "^0.18.0",
|
||||||
"conventional-changelog-cli": "^2.0.12",
|
"conventional-changelog-cli": "^2.0.31",
|
||||||
"copy-webpack-plugin": "^5.0.0",
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
"core-js": "^2.6.5",
|
"core-js": "^3.5.0",
|
||||||
"coveralls": "^3.0.3",
|
"coveralls": "^3.0.9",
|
||||||
"css-loader": "^2.1.1",
|
"css-loader": "^3.4.2",
|
||||||
"cypress": "~3.1.5",
|
"cypress": "~3.7.0",
|
||||||
"deploy-to-gh-pages": "^1.3.6",
|
"deploy-to-gh-pages": "^1.3.7",
|
||||||
"enzyme": "^3.9.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-adapter-react-16": "^1.10.0",
|
"enzyme-adapter-react-16": "^1.15.2",
|
||||||
"enzyme-to-json": "^3.3.5",
|
"enzyme-to-json": "^3.4.4",
|
||||||
"fork-ts-checker-webpack-plugin": "1.0.0",
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-plugin-import": "^2.20.1",
|
||||||
|
"eslint-plugin-react": "^7.19.0",
|
||||||
|
"fork-ts-checker-webpack-plugin": "3.1.1",
|
||||||
"html-webpack-plugin": "^3.1.0",
|
"html-webpack-plugin": "^3.1.0",
|
||||||
"jest": "^24.3.1",
|
"jest": "^24.9.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.15",
|
||||||
"mobx": "^4.3.1",
|
"mobx": "^4.3.1",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.19.1",
|
||||||
"prettier-eslint": "^8.8.2",
|
|
||||||
"raf": "^3.4.1",
|
"raf": "^3.4.1",
|
||||||
"react": "^16.8.4",
|
"react": "^16.13.0",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.13.0",
|
||||||
"rimraf": "^2.6.3",
|
"react-hot-loader": "^4.12.19",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"shelljs": "^0.8.3",
|
"shelljs": "^0.8.3",
|
||||||
"source-map-loader": "^0.2.4",
|
"source-map-loader": "^0.2.4",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^1.1.3",
|
||||||
"styled-components": "^4.1.3",
|
"styled-components": "^4.4.1",
|
||||||
"ts-jest": "24.0.0",
|
"ts-jest": "24.2.0",
|
||||||
"ts-loader": "5.3.3",
|
"ts-loader": "6.2.1",
|
||||||
"ts-node": "^8.0.3",
|
"ts-node": "^8.6.2",
|
||||||
"tslint": "^5.13.1",
|
"typescript": "^3.8.3",
|
||||||
"tslint-react": "^3.4.0",
|
|
||||||
"typescript": "^3.3.3333",
|
|
||||||
"unfetch": "^4.1.0",
|
"unfetch": "^4.1.0",
|
||||||
"url-polyfill": "^1.1.5",
|
"url-polyfill": "^1.1.8",
|
||||||
"webpack": "^4.29.6",
|
"webpack": "^4.42.0",
|
||||||
"webpack-cli": "^3.2.3",
|
"webpack-cli": "^3.3.11",
|
||||||
"webpack-dev-server": "^3.2.1",
|
"webpack-dev-server": "^3.10.3",
|
||||||
"webpack-node-externals": "^1.6.0",
|
"webpack-node-externals": "^1.6.0",
|
||||||
"workerize-loader": "^1.0.4",
|
"workerize-loader": "^1.1.0",
|
||||||
"yaml-js": "^0.2.3"
|
"yaml-js": "^0.2.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"core-js": "^3.1.4",
|
||||||
"mobx": "^4.2.0 || ^5.0.0",
|
"mobx": "^4.2.0 || ^5.0.0",
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.4",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.8.4",
|
||||||
|
@ -134,27 +137,27 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"decko": "^1.2.0",
|
"decko": "^1.2.0",
|
||||||
"dompurify": "^1.0.10",
|
"dompurify": "^2.0.8",
|
||||||
"eventemitter3": "^3.0.0",
|
"eventemitter3": "^4.0.0",
|
||||||
"json-pointer": "^0.6.0",
|
"json-pointer": "^0.6.0",
|
||||||
"json-schema-ref-parser": "^6.1.0",
|
"json-schema-ref-parser": "^6.1.0",
|
||||||
"lunr": "2.3.6",
|
"lunr": "2.3.8",
|
||||||
"mark.js": "^8.11.1",
|
"mark.js": "^8.11.1",
|
||||||
"marked": "^0.6.1",
|
"marked": "^0.7.0",
|
||||||
"memoize-one": "^5.0.0",
|
"memoize-one": "~5.1.1",
|
||||||
"mobx-react": "^5.4.3",
|
"mobx-react": "6.1.5",
|
||||||
"openapi-sampler": "1.0.0-beta.14",
|
"openapi-sampler": "1.0.0-beta.15",
|
||||||
"perfect-scrollbar": "^1.4.0",
|
"perfect-scrollbar": "^1.4.0",
|
||||||
"polished": "^3.0.3",
|
"polished": "^3.4.4",
|
||||||
"prismjs": "^1.15.0",
|
"prismjs": "^1.19.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react-dropdown": "^1.6.4",
|
"react-dropdown": "^1.7.0",
|
||||||
"react-hot-loader": "^4.8.0",
|
"react-tabs": "^3.1.0",
|
||||||
"react-tabs": "^3.0.0",
|
"slugify": "^1.4.0",
|
||||||
"slugify": "^1.3.4",
|
|
||||||
"stickyfill": "^1.1.1",
|
"stickyfill": "^1.1.1",
|
||||||
"swagger2openapi": "^5.2.3",
|
"swagger2openapi": "^5.3.4",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.11.1",
|
||||||
|
"url-template": "^2.0.8"
|
||||||
},
|
},
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,11 +5,7 @@ import { ClipboardService } from '../services/ClipboardService';
|
||||||
|
|
||||||
export interface CopyButtonWrapperProps {
|
export interface CopyButtonWrapperProps {
|
||||||
data: any;
|
data: any;
|
||||||
children: (
|
children: (props: { renderCopyButton: () => React.ReactNode }) => React.ReactNode;
|
||||||
props: {
|
|
||||||
renderCopyButton: (() => React.ReactNode);
|
|
||||||
},
|
|
||||||
) => React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CopyButtonWrapper extends React.PureComponent<
|
export class CopyButtonWrapper extends React.PureComponent<
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const StyledDropdown = styled(Dropdown)`
|
||||||
display: block;
|
display: block;
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.35em;
|
right: 0.3em;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
margin-top: -0.125em;
|
margin-top: -0.125em;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
@ -97,7 +97,7 @@ export const StyledDropdown = styled(Dropdown)`
|
||||||
export const SimpleDropdown = styled(StyledDropdown)`
|
export const SimpleDropdown = styled(StyledDropdown)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-size: 0.929em;
|
font-size: 0.969em;
|
||||||
|
|
||||||
.Dropdown-control {
|
.Dropdown-control {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
|
|
@ -63,7 +63,7 @@ export const PropertyNameCell = styled(PropertyCell)`
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
font-family: ${props => props.theme.typography.headings.fontFamily};
|
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||||
|
|
||||||
&.deprecated {
|
&.deprecated {
|
||||||
${deprecatedCss};
|
${deprecatedCss};
|
||||||
|
|
|
@ -32,6 +32,7 @@ export const TypeName = styled(FieldLabel)`
|
||||||
|
|
||||||
export const TypeTitle = styled(FieldLabel)`
|
export const TypeTitle = styled(FieldLabel)`
|
||||||
color: ${props => props.theme.schema.typeTitleColor};
|
color: ${props => props.theme.schema.typeTitleColor};
|
||||||
|
word-break: break-word;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypeFormat = TypeName;
|
export const TypeFormat = TypeName;
|
||||||
|
@ -60,14 +61,6 @@ export const PatternLabel = styled(FieldLabel)`
|
||||||
&::after {
|
&::after {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: ' /';
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '/ ';
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ExampleValue = styled(FieldLabel)`
|
export const ExampleValue = styled(FieldLabel)`
|
||||||
|
@ -76,11 +69,9 @@ export const ExampleValue = styled(FieldLabel)`
|
||||||
background-color: ${transparentize(0.95, theme.colors.text.primary)};
|
background-color: ${transparentize(0.95, theme.colors.text.primary)};
|
||||||
color: ${transparentize(0.1, theme.colors.text.primary)};
|
color: ${transparentize(0.1, theme.colors.text.primary)};
|
||||||
|
|
||||||
margin: ${theme.spacing.unit}px;
|
|
||||||
padding: 0 ${theme.spacing.unit}px;
|
padding: 0 ${theme.spacing.unit}px;
|
||||||
border: 1px solid ${transparentize(0.9, theme.colors.text.primary)};
|
border: 1px solid ${transparentize(0.9, theme.colors.text.primary)};
|
||||||
font-family: ${theme.typography.code.fontFamily};
|
font-family: ${theme.typography.code.fontFamily};
|
||||||
color: ${theme.typography.code.color};
|
|
||||||
}`};
|
}`};
|
||||||
& + & {
|
& + & {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
@ -99,6 +90,7 @@ 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;
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import { SECTION_ATTR } from '../services/MenuStore';
|
import { SECTION_ATTR } from '../services/MenuStore';
|
||||||
import styled, { media } from '../styled-components';
|
import styled, { media } from '../styled-components';
|
||||||
|
|
||||||
export const MiddlePanel = styled.div`
|
export const MiddlePanel = styled.div<{ compact?: boolean }>`
|
||||||
width: calc(100% - ${props => props.theme.rightPanel.width});
|
width: calc(100% - ${props => props.theme.rightPanel.width});
|
||||||
padding: 0 ${props => props.theme.spacing.sectionHorizontal}px;
|
padding: 0 ${props => props.theme.spacing.sectionHorizontal}px;
|
||||||
|
|
||||||
${media.lessThan('medium', true)`
|
${({ compact, theme }) =>
|
||||||
|
media.lessThan('medium', true)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: ${props =>
|
padding: ${`${compact ? 0 : theme.spacing.sectionVertical}px ${
|
||||||
`${props.theme.spacing.sectionVertical}px ${props.theme.spacing.sectionHorizontal}px`};
|
theme.spacing.sectionHorizontal
|
||||||
|
}px`};
|
||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -11,13 +11,14 @@ export const OneOfLabel = styled.span`
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
color: ${props => props.theme.colors.primary.main};
|
color: ${props => props.theme.colors.primary.main};
|
||||||
font-family: Montserrat;
|
font-family: ${props => props.theme.typography.headings.fontFamily};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const OneOfButton = styled.li<{ active: boolean }>`
|
export const OneOfButton = styled.li<{ active: boolean }>`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid ${props => props.theme.colors.primary.main};
|
border: 1px solid ${props => props.theme.colors.primary.main};
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Constants } from '../../../src/services/Constants';
|
|
||||||
import { DarkRightPanel, StyledLink } from '../../../src/common-elements';
|
|
||||||
import { ContentPanel } from '../RightPanelContent/ContentPanel';
|
|
||||||
|
|
||||||
import { MiddlePanel, Row, Section } from '../../common-elements/';
|
|
||||||
|
|
||||||
import { AppStore } from '../../services/AppStore';
|
import { AppStore } from '../../services/AppStore';
|
||||||
|
|
||||||
|
import { MiddlePanel, Row, Section } from '../../common-elements/';
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
||||||
import { ApiHeader, DownloadButton, InfoSpan, InfoSpanBox, InfoSpanBoxWrap, } from './styled.elements';
|
import {
|
||||||
|
ApiHeader,
|
||||||
|
DownloadButton,
|
||||||
|
InfoSpan,
|
||||||
|
InfoSpanBox,
|
||||||
|
InfoSpanBoxWrap,
|
||||||
|
} from './styled.elements';
|
||||||
|
|
||||||
export interface ApiInfoProps {
|
export interface ApiInfoProps {
|
||||||
store: AppStore;
|
store: AppStore;
|
||||||
|
@ -25,8 +28,8 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {store} = this.props;
|
const { store } = this.props;
|
||||||
const {info, externalDocs} = store.spec;
|
const { info, externalDocs } = store.spec;
|
||||||
const hideDownloadButton = store.options.hideDownloadButton;
|
const hideDownloadButton = store.options.hideDownloadButton;
|
||||||
|
|
||||||
const downloadFilename = info.downloadFileName;
|
const downloadFilename = info.downloadFileName;
|
||||||
|
@ -35,43 +38,37 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
const license =
|
const license =
|
||||||
(info.license && (
|
(info.license && (
|
||||||
<InfoSpan>
|
<InfoSpan>
|
||||||
License: <StyledLink href={info.license.url}>{info.license.name}</StyledLink>
|
License: <a href={info.license.url}>{info.license.name}</a>
|
||||||
</InfoSpan>
|
</InfoSpan>
|
||||||
)) ||
|
)) ||
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const website =
|
const website =
|
||||||
(info.contact &&
|
(info.contact && info.contact.url && (
|
||||||
info.contact.url && (
|
<InfoSpan>
|
||||||
<InfoSpan>
|
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
||||||
URL: <StyledLink href={info.contact.url}>{info.contact.url}</StyledLink>
|
</InfoSpan>
|
||||||
</InfoSpan>
|
)) ||
|
||||||
)) ||
|
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const email =
|
const email =
|
||||||
(info.contact &&
|
(info.contact && info.contact.email && (
|
||||||
info.contact.email && (
|
<InfoSpan>
|
||||||
<InfoSpan>
|
{info.contact.name || 'E-mail'}:{' '}
|
||||||
{info.contact.name || 'E-mail'}:{' '}
|
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
||||||
<StyledLink href={'mailto:' + info.contact.email}>{info.contact.email}</StyledLink>
|
</InfoSpan>
|
||||||
</InfoSpan>
|
)) ||
|
||||||
)) ||
|
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const terms =
|
const terms =
|
||||||
(info.termsOfService && (
|
(info.termsOfService && (
|
||||||
<InfoSpan>
|
<InfoSpan>
|
||||||
<StyledLink href={info.termsOfService}>Terms of Service</StyledLink>
|
<a href={info.termsOfService}>Terms of Service</a>
|
||||||
</InfoSpan>
|
</InfoSpan>
|
||||||
)) ||
|
)) ||
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const version =
|
const version = (info.version && <span>({info.version})</span>) || null;
|
||||||
(info.version && (
|
|
||||||
<span>({info.version})</span>
|
|
||||||
)) ||
|
|
||||||
null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
|
@ -101,13 +98,11 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
</InfoSpanBox>
|
</InfoSpanBox>
|
||||||
</InfoSpanBoxWrap>
|
</InfoSpanBoxWrap>
|
||||||
)) ||
|
)) ||
|
||||||
null}
|
null}
|
||||||
</StyledMarkdownBlock>
|
</StyledMarkdownBlock>
|
||||||
<Markdown source={store.spec.info.description}/>
|
<Markdown source={store.spec.info.description} data-role="redoc-description" />
|
||||||
{externalDocs && <ExternalDocumentation externalDocs={externalDocs}/>}
|
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
|
||||||
</MiddlePanel>
|
</MiddlePanel>
|
||||||
{store.options.showOtherInfoPanel &&
|
|
||||||
<DarkRightPanel><ContentPanel content={info[Constants.OTX_EXTENSION_KEY]}/></DarkRightPanel>}
|
|
||||||
</Row>
|
</Row>
|
||||||
</Section>
|
</Section>
|
||||||
);
|
);
|
||||||
|
|
|
@ -60,7 +60,7 @@ export class ContentItem extends React.Component<ContentItemProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const middlePanelWrap = component => <MiddlePanel>{component}</MiddlePanel>;
|
const middlePanelWrap = component => <MiddlePanel compact={true}>{component}</MiddlePanel>;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class SectionItem extends React.Component<ContentItemProps> {
|
export class SectionItem extends React.Component<ContentItemProps> {
|
||||||
|
@ -71,7 +71,7 @@ export class SectionItem extends React.Component<ContentItemProps> {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row>
|
<Row>
|
||||||
<MiddlePanel>
|
<MiddlePanel compact={false}>
|
||||||
<Header>
|
<Header>
|
||||||
<ShareLink to={this.props.item.id} />
|
<ShareLink to={this.props.item.id} />
|
||||||
{name}
|
{name}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Markdown } from '../Markdown/Markdown';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
|
import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
|
||||||
|
|
||||||
import { getBasePath } from '../../utils';
|
import { expandDefaultServerVariables, getBasePath } from '../../utils';
|
||||||
import {
|
import {
|
||||||
EndpointInfo,
|
EndpointInfo,
|
||||||
HttpVerb,
|
HttpVerb,
|
||||||
|
@ -60,21 +60,26 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
|
||||||
/>
|
/>
|
||||||
</EndpointInfo>
|
</EndpointInfo>
|
||||||
<ServersOverlay expanded={expanded}>
|
<ServersOverlay expanded={expanded}>
|
||||||
{operation.servers.map(server => (
|
{operation.servers.map(server => {
|
||||||
<ServerItem key={server.url}>
|
const normalizedUrl = options.expandDefaultServerVariables
|
||||||
<Markdown source={server.description || ''} compact={true} />
|
? expandDefaultServerVariables(server.url, server.variables)
|
||||||
<SelectOnClick>
|
: server.url;
|
||||||
<ServerUrl>
|
return (
|
||||||
<span>
|
<ServerItem key={normalizedUrl}>
|
||||||
{hideHostname || options.hideHostname
|
<Markdown source={server.description || ''} compact={true} />
|
||||||
? getBasePath(server.url)
|
<SelectOnClick>
|
||||||
: server.url}
|
<ServerUrl>
|
||||||
</span>
|
<span>
|
||||||
{operation.path}
|
{hideHostname || options.hideHostname
|
||||||
</ServerUrl>
|
? getBasePath(normalizedUrl)
|
||||||
</SelectOnClick>
|
: normalizedUrl}
|
||||||
</ServerItem>
|
</span>
|
||||||
))}
|
{operation.path}
|
||||||
|
</ServerUrl>
|
||||||
|
</SelectOnClick>
|
||||||
|
</ServerItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</ServersOverlay>
|
</ServersOverlay>
|
||||||
</OperationEndpointWrap>
|
</OperationEndpointWrap>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as React from 'react';
|
||||||
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
||||||
|
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
|
||||||
export interface EnumValuesProps {
|
export interface EnumValuesProps {
|
||||||
values: string[];
|
values: string[];
|
||||||
|
@ -9,8 +10,10 @@ export interface EnumValuesProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
render() {
|
render() {
|
||||||
const { values, type } = this.props;
|
const { values, type } = this.props;
|
||||||
|
const { enumSkipQuotes } = this.context;
|
||||||
if (!values.length) {
|
if (!values.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -20,12 +23,15 @@ export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
||||||
<FieldLabel>
|
<FieldLabel>
|
||||||
{type === 'array' ? l('enumArray') : ''}{' '}
|
{type === 'array' ? l('enumArray') : ''}{' '}
|
||||||
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||||
</FieldLabel>
|
</FieldLabel>{' '}
|
||||||
{values.map((value, idx) => (
|
{values.map((value, idx) => {
|
||||||
<React.Fragment key={idx}>
|
const exampleValue = enumSkipQuotes ? value : JSON.stringify(value);
|
||||||
<ExampleValue>{JSON.stringify(value)}</ExampleValue>{' '}
|
return (
|
||||||
</React.Fragment>
|
<React.Fragment key={idx}>
|
||||||
))}
|
<ExampleValue>{exampleValue}</ExampleValue>{' '}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ export interface FieldProps extends SchemaOptions {
|
||||||
showExamples?: boolean;
|
showExamples?: boolean;
|
||||||
|
|
||||||
field: FieldModel;
|
field: FieldModel;
|
||||||
|
expandByDefault?: boolean;
|
||||||
|
|
||||||
renderDiscriminatorSwitch?: (opts: FieldProps) => JSX.Element;
|
renderDiscriminatorSwitch?: (opts: FieldProps) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
@ -30,13 +31,19 @@ export interface FieldProps extends SchemaOptions {
|
||||||
@observer
|
@observer
|
||||||
export class Field extends React.Component<FieldProps> {
|
export class Field extends React.Component<FieldProps> {
|
||||||
toggle = () => {
|
toggle = () => {
|
||||||
this.props.field.toggle();
|
if (this.props.field.expanded === undefined && this.props.expandByDefault) {
|
||||||
|
this.props.field.expanded = false;
|
||||||
|
} else {
|
||||||
|
this.props.field.toggle();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
render() {
|
render() {
|
||||||
const { className, field, isLast } = this.props;
|
const { className, field, isLast, expandByDefault } = this.props;
|
||||||
const { name, expanded, deprecated, required, kind } = field;
|
const { name, deprecated, required, kind } = field;
|
||||||
const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular;
|
const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular;
|
||||||
|
|
||||||
|
const expanded = field.expanded === undefined ? expandByDefault : field.expanded;
|
||||||
|
|
||||||
const paramName = withSubSchema ? (
|
const paramName = withSubSchema ? (
|
||||||
<ClickablePropertyNameCell
|
<ClickablePropertyNameCell
|
||||||
onClick={this.toggle}
|
onClick={this.toggle}
|
||||||
|
@ -65,21 +72,20 @@ export class Field extends React.Component<FieldProps> {
|
||||||
<FieldDetails {...this.props} />
|
<FieldDetails {...this.props} />
|
||||||
</PropertyDetailsCell>
|
</PropertyDetailsCell>
|
||||||
</tr>
|
</tr>
|
||||||
{field.expanded &&
|
{expanded && withSubSchema && (
|
||||||
withSubSchema && (
|
<tr key={field.name + 'inner'}>
|
||||||
<tr key={field.name + 'inner'}>
|
<PropertyCellWithInner colSpan={2}>
|
||||||
<PropertyCellWithInner colSpan={2}>
|
<InnerPropertiesWrap>
|
||||||
<InnerPropertiesWrap>
|
<Schema
|
||||||
<Schema
|
schema={field.schema}
|
||||||
schema={field.schema}
|
skipReadOnly={this.props.skipReadOnly}
|
||||||
skipReadOnly={this.props.skipReadOnly}
|
skipWriteOnly={this.props.skipWriteOnly}
|
||||||
skipWriteOnly={this.props.skipWriteOnly}
|
showTitle={this.props.showTitle}
|
||||||
showTitle={this.props.showTitle}
|
/>
|
||||||
/>
|
</InnerPropertiesWrap>
|
||||||
</InnerPropertiesWrap>
|
</PropertyCellWithInner>
|
||||||
</PropertyCellWithInner>
|
</tr>
|
||||||
</tr>
|
)}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
||||||
export interface FieldDetailProps {
|
export interface FieldDetailProps {
|
||||||
value?: any;
|
value?: any;
|
||||||
label: string;
|
label: string;
|
||||||
|
raw?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
||||||
|
@ -11,12 +12,12 @@ export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
||||||
if (this.props.value === undefined) {
|
if (this.props.value === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const value = this.props.raw ? this.props.value : JSON.stringify(this.props.value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel> {this.props.label} </FieldLabel>{' '}
|
<FieldLabel> {this.props.label} </FieldLabel> <ExampleValue>{value}</ExampleValue>
|
||||||
<ExampleValue>
|
|
||||||
{JSON.stringify(this.props.value)}
|
|
||||||
</ExampleValue>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
TypePrefix,
|
TypePrefix,
|
||||||
TypeTitle,
|
TypeTitle,
|
||||||
} from '../../common-elements/fields';
|
} from '../../common-elements/fields';
|
||||||
|
import { serializeParameterValue } from '../../utils/openapi';
|
||||||
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';
|
||||||
|
@ -20,13 +21,31 @@ import { FieldDetail } from './FieldDetail';
|
||||||
import { Badge } from '../../common-elements/';
|
import { Badge } from '../../common-elements/';
|
||||||
|
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
|
||||||
export class FieldDetails extends React.PureComponent<FieldProps> {
|
export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
render() {
|
render() {
|
||||||
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
||||||
|
const { enumSkipQuotes, hideSchemaTitles } = this.context;
|
||||||
|
|
||||||
const { schema, description, example, deprecated } = field;
|
const { schema, description, example, deprecated } = field;
|
||||||
|
|
||||||
|
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
|
||||||
|
|
||||||
|
let exampleField: JSX.Element | null = null;
|
||||||
|
|
||||||
|
if (showExamples && example !== undefined) {
|
||||||
|
const label = l('example') + ':';
|
||||||
|
if (field.in && (field.style || field.serializationMime)) {
|
||||||
|
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
|
||||||
|
const serializedValue = decodeURIComponent(serializeParameterValue(field, example));
|
||||||
|
exampleField = <FieldDetail label={label} value={serializedValue} raw={true} />;
|
||||||
|
} else {
|
||||||
|
exampleField = <FieldDetail label={label} value={example} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -40,10 +59,10 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||||
>{' '}
|
>{' '}
|
||||||
</TypeFormat>
|
</TypeFormat>
|
||||||
)}
|
)}
|
||||||
{schema.title && <TypeTitle> ({schema.title}) </TypeTitle>}
|
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||||
<ConstraintsView constraints={schema.constraints} />
|
<ConstraintsView constraints={schema.constraints} />
|
||||||
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>}
|
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>}
|
||||||
{schema.pattern && <PatternLabel>{schema.pattern}</PatternLabel>}
|
{schema.pattern && <PatternLabel> {schema.pattern} </PatternLabel>}
|
||||||
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
||||||
</div>
|
</div>
|
||||||
{deprecated && (
|
{deprecated && (
|
||||||
|
@ -51,9 +70,9 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||||
<Badge type="warning"> {l('deprecated')} </Badge>
|
<Badge type="warning"> {l('deprecated')} </Badge>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<FieldDetail label={l('default') + ':'} value={schema.default} />
|
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
||||||
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
||||||
{showExamples && <FieldDetail label={l('example') + ':'} value={example} />}
|
{exampleField}
|
||||||
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
||||||
<div>
|
<div>
|
||||||
<Markdown compact={true} source={description} />
|
<Markdown compact={true} source={description} />
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { SampleControls } from '../../common-elements';
|
||||||
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
|
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
|
||||||
import { PrismDiv } from '../../common-elements/PrismDiv';
|
import { PrismDiv } from '../../common-elements/PrismDiv';
|
||||||
import { jsonToHTML } from '../../utils/jsonToHtml';
|
import { jsonToHTML } from '../../utils/jsonToHtml';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { jsonStyles } from './style';
|
import { jsonStyles } from './style';
|
||||||
|
|
||||||
export interface JsonProps {
|
export interface JsonProps {
|
||||||
|
@ -32,12 +33,18 @@ class Json extends React.PureComponent<JsonProps> {
|
||||||
<span onClick={this.expandAll}> Expand all </span>
|
<span onClick={this.expandAll}> Expand all </span>
|
||||||
<span onClick={this.collapseAll}> Collapse all </span>
|
<span onClick={this.collapseAll}> Collapse all </span>
|
||||||
</SampleControls>
|
</SampleControls>
|
||||||
<PrismDiv
|
<OptionsContext.Consumer>
|
||||||
className={this.props.className}
|
{options => (
|
||||||
// tslint:disable-next-line
|
<PrismDiv
|
||||||
ref={node => (this.node = node!)}
|
className={this.props.className}
|
||||||
dangerouslySetInnerHTML={{ __html: jsonToHTML(this.props.data) }}
|
// tslint:disable-next-line
|
||||||
/>
|
ref={node => (this.node = node!)}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OptionsContext.Consumer>
|
||||||
</JsonViewerWrap>
|
</JsonViewerWrap>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
|
||||||
renderWithOptionsAndStore(options: RedocNormalizedOptions, store?: AppStore) {
|
renderWithOptionsAndStore(options: RedocNormalizedOptions, store?: AppStore) {
|
||||||
const { source, htmlWrap = i => i } = this.props;
|
const { source, htmlWrap = i => i } = this.props;
|
||||||
if (!store) {
|
if (!store) {
|
||||||
throw new Error('When using componentes in markdown, store prop must be provided');
|
throw new Error('When using components in markdown, store prop must be provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderer = new MarkdownRenderer(options);
|
const renderer = new MarkdownRenderer(options);
|
||||||
|
|
|
@ -17,11 +17,12 @@ export type MarkdownProps = BaseMarkdownProps &
|
||||||
StylingMarkdownProps & {
|
StylingMarkdownProps & {
|
||||||
source: string;
|
source: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
'data-role'?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Markdown extends React.Component<MarkdownProps> {
|
export class Markdown extends React.Component<MarkdownProps> {
|
||||||
render() {
|
render() {
|
||||||
const { source, inline, compact, className } = this.props;
|
const { source, inline, compact, className, 'data-role': dataRole } = this.props;
|
||||||
const renderer = new MarkdownRenderer();
|
const renderer = new MarkdownRenderer();
|
||||||
return (
|
return (
|
||||||
<SanitizedMarkdownHTML
|
<SanitizedMarkdownHTML
|
||||||
|
@ -29,6 +30,7 @@ export class Markdown extends React.Component<MarkdownProps> {
|
||||||
inline={inline}
|
inline={inline}
|
||||||
compact={compact}
|
compact={compact}
|
||||||
className={className}
|
className={className}
|
||||||
|
data-role={dataRole}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span');
|
||||||
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
|
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
|
||||||
|
|
||||||
export function SanitizedMarkdownHTML(
|
export function SanitizedMarkdownHTML(
|
||||||
props: StylingMarkdownProps & { html: string; className?: string },
|
props: StylingMarkdownProps & { html: string; className?: string; 'data-role'?: string },
|
||||||
) {
|
) {
|
||||||
const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock;
|
const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock;
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ export function SanitizedMarkdownHTML(
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: sanitize(options.untrustedSpec, props.html),
|
__html: sanitize(options.untrustedSpec, props.html),
|
||||||
}}
|
}}
|
||||||
|
data-role={props['data-role']}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -19,11 +19,13 @@ export const linksCss = css`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const StyledMarkdownBlock = styled(PrismDiv as StyledComponent<
|
export const StyledMarkdownBlock = styled(
|
||||||
'div',
|
PrismDiv as StyledComponent<
|
||||||
ResolvedThemeInterface,
|
'div',
|
||||||
{ compact?: boolean; inline?: boolean }
|
ResolvedThemeInterface,
|
||||||
>)`
|
{ compact?: boolean; inline?: boolean }
|
||||||
|
>,
|
||||||
|
)`
|
||||||
|
|
||||||
font-family: ${props => props.theme.typography.fontFamily};
|
font-family: ${props => props.theme.typography.fontFamily};
|
||||||
font-weight: ${props => props.theme.typography.fontWeightRegular};
|
font-weight: ${props => props.theme.typography.fontWeightRegular};
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { DropdownProps } from '../../common-elements/dropdown';
|
import { DropdownProps } from '../../common-elements/dropdown';
|
||||||
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
|
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
|
||||||
|
import { DropdownLabel, DropdownWrapper } from '../PayloadSamples/styled.elements';
|
||||||
|
|
||||||
export interface MediaTypeChildProps {
|
export interface MediaTypeChildProps {
|
||||||
schema: SchemaModel;
|
schema: SchemaModel;
|
||||||
|
@ -11,6 +12,8 @@ export interface MediaTypeChildProps {
|
||||||
|
|
||||||
export interface MediaTypesSwitchProps {
|
export interface MediaTypesSwitchProps {
|
||||||
content?: MediaContentModel;
|
content?: MediaContentModel;
|
||||||
|
withLabel?: boolean;
|
||||||
|
|
||||||
renderDropdown: (props: DropdownProps) => JSX.Element;
|
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||||
children: (activeMime: MediaTypeModel) => JSX.Element;
|
children: (activeMime: MediaTypeModel) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
@ -37,13 +40,25 @@ export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Wrapper = ({ children }) =>
|
||||||
|
this.props.withLabel ? (
|
||||||
|
<DropdownWrapper>
|
||||||
|
<DropdownLabel>Content type</DropdownLabel>
|
||||||
|
{children}
|
||||||
|
</DropdownWrapper>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.props.renderDropdown({
|
<Wrapper>
|
||||||
value: options[activeMimeIdx],
|
{this.props.renderDropdown({
|
||||||
options,
|
value: options[activeMimeIdx],
|
||||||
onChange: this.switchMedia,
|
options,
|
||||||
})}
|
onChange: this.switchMedia,
|
||||||
|
})}
|
||||||
|
</Wrapper>
|
||||||
{this.props.children(content.active)}
|
{this.props.children(content.active)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,7 +29,12 @@ export function ExternalExample({ example, mimeType }: ExampleProps) {
|
||||||
return (
|
return (
|
||||||
<StyledPre>
|
<StyledPre>
|
||||||
Error loading external example: <br />
|
Error loading external example: <br />
|
||||||
<a className={'token string'} href={example.externalValueUrl} target="_blank">
|
<a
|
||||||
|
className={'token string'}
|
||||||
|
href={example.externalValueUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
{example.externalValueUrl}
|
{example.externalValueUrl}
|
||||||
</a>
|
</a>
|
||||||
</StyledPre>
|
</StyledPre>
|
||||||
|
|
|
@ -1,17 +1,33 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { SmallTabs, Tab, TabList, TabPanel } from '../../common-elements';
|
import styled from '../../styled-components';
|
||||||
import { MediaTypeModel } from '../../services/models';
|
|
||||||
|
|
||||||
|
import { DropdownProps } from '../../common-elements';
|
||||||
|
import { MediaTypeModel } from '../../services/models';
|
||||||
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { Example } from './Example';
|
import { Example } from './Example';
|
||||||
import { NoSampleLabel } from './styled.elements';
|
import { DropdownLabel, DropdownWrapper, NoSampleLabel } from './styled.elements';
|
||||||
|
|
||||||
export interface PayloadSamplesProps {
|
export interface PayloadSamplesProps {
|
||||||
mediaType: MediaTypeModel;
|
mediaType: MediaTypeModel;
|
||||||
|
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
|
interface MediaTypeSamplesState {
|
||||||
|
activeIdx: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MediaTypeSamples extends React.Component<PayloadSamplesProps, MediaTypeSamplesState> {
|
||||||
|
state = {
|
||||||
|
activeIdx: 0,
|
||||||
|
};
|
||||||
|
switchMedia = ({ value }) => {
|
||||||
|
this.setState({
|
||||||
|
activeIdx: parseInt(value, 10),
|
||||||
|
});
|
||||||
|
};
|
||||||
render() {
|
render() {
|
||||||
|
const { activeIdx } = this.state;
|
||||||
const examples = this.props.mediaType.examples || {};
|
const examples = this.props.mediaType.examples || {};
|
||||||
const mimeType = this.props.mediaType.name;
|
const mimeType = this.props.mediaType.name;
|
||||||
|
|
||||||
|
@ -21,28 +37,46 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
|
||||||
if (examplesNames.length === 0) {
|
if (examplesNames.length === 0) {
|
||||||
return noSample;
|
return noSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (examplesNames.length > 1) {
|
if (examplesNames.length > 1) {
|
||||||
|
const options = examplesNames.map((name, idx) => {
|
||||||
|
return {
|
||||||
|
label: examples[name].summary || name,
|
||||||
|
value: idx.toString(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const example = examples[examplesNames[activeIdx]];
|
||||||
|
const description = example.description;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SmallTabs defaultIndex={0}>
|
<SamplesWrapper>
|
||||||
<TabList>
|
<DropdownWrapper>
|
||||||
{examplesNames.map(name => (
|
<DropdownLabel>Example</DropdownLabel>
|
||||||
<Tab key={name}> {examples[name].summary || name} </Tab>
|
{this.props.renderDropdown({
|
||||||
))}
|
value: options[activeIdx],
|
||||||
</TabList>
|
options,
|
||||||
{examplesNames.map(name => (
|
onChange: this.switchMedia,
|
||||||
<TabPanel key={name}>
|
})}
|
||||||
<Example example={examples[name]} mimeType={mimeType} />
|
</DropdownWrapper>
|
||||||
</TabPanel>
|
<div>
|
||||||
))}
|
{description && <Markdown source={description} />}
|
||||||
</SmallTabs>
|
<Example example={example} mimeType={mimeType} />
|
||||||
|
</div>
|
||||||
|
</SamplesWrapper>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const name = examplesNames[0];
|
const example = examples[examplesNames[0]];
|
||||||
return (
|
return (
|
||||||
<div>
|
<SamplesWrapper>
|
||||||
<Example example={examples[name]} mimeType={mimeType} />
|
{example.description && <Markdown source={example.description} />}
|
||||||
</div>
|
<Example example={example} mimeType={mimeType} />
|
||||||
|
</SamplesWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SamplesWrapper = styled.div`
|
||||||
|
margin-top: 15px;
|
||||||
|
`;
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import ReactDropdown from 'react-dropdown';
|
||||||
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { MediaTypeSamples } from './MediaTypeSamples';
|
import { MediaTypeSamples } from './MediaTypeSamples';
|
||||||
|
|
||||||
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
|
||||||
|
|
||||||
import { MediaContentModel } from '../../services/models';
|
import { MediaContentModel } from '../../services/models';
|
||||||
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||||
|
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
||||||
import { InvertedSimpleDropdown, MimeLabel } from './styled.elements';
|
import { InvertedSimpleDropdown, MimeLabel } from './styled.elements';
|
||||||
|
|
||||||
export interface PayloadSamplesProps {
|
export interface PayloadSamplesProps {
|
||||||
|
@ -21,8 +24,14 @@ export class PayloadSamples extends React.Component<PayloadSamplesProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown}>
|
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown} withLabel={true}>
|
||||||
{mediaType => <MediaTypeSamples key="samples" mediaType={mediaType} />}
|
{mediaType => (
|
||||||
|
<MediaTypeSamples
|
||||||
|
key="samples"
|
||||||
|
mediaType={mediaType}
|
||||||
|
renderDropdown={this.renderDropdown}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</MediaTypesSwitch>
|
</MediaTypesSwitch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,22 +13,19 @@ export function useExternalExample(example: ExampleModel, mimeType: string) {
|
||||||
|
|
||||||
prevRef.current = example;
|
prevRef.current = example;
|
||||||
|
|
||||||
useEffect(
|
useEffect(() => {
|
||||||
() => {
|
const load = async () => {
|
||||||
const load = async () => {
|
setIsLoading(true);
|
||||||
setIsLoading(true);
|
try {
|
||||||
try {
|
value.current = await example.getExternalValue(mimeType);
|
||||||
value.current = await example.getExternalValue(mimeType);
|
} catch (e) {
|
||||||
} catch (e) {
|
value.current = e;
|
||||||
value.current = e;
|
}
|
||||||
}
|
setIsLoading(false);
|
||||||
setIsLoading(false);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
load();
|
load();
|
||||||
},
|
}, [example, mimeType]);
|
||||||
[example, mimeType],
|
|
||||||
);
|
|
||||||
|
|
||||||
return value.current;
|
return value.current;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,49 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Dropdown from 'react-dropdown';
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
import ReactDropdown from 'react-dropdown';
|
||||||
|
|
||||||
|
import { transparentize } from 'polished';
|
||||||
import styled from '../../styled-components';
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
import { StyledDropdown } from '../../common-elements';
|
import { StyledDropdown } from '../../common-elements';
|
||||||
|
|
||||||
export const MimeLabel = styled.div`
|
export const MimeLabel = styled.div`
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.9);
|
padding: 12px;
|
||||||
|
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
display: block;
|
display: block;
|
||||||
color: rgba(255, 255, 255, 0.8);
|
`;
|
||||||
|
|
||||||
|
export const DropdownLabel = styled.span`
|
||||||
|
font-family: ${({ theme }) => theme.typography.headings.fontFamily};
|
||||||
|
font-size: 12px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: -11px;
|
||||||
|
left: 12px;
|
||||||
|
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
|
||||||
|
color: ${({ theme }) => transparentize(0.6, theme.rightPanel.textColor)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DropdownWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.rightPanel.textColor};
|
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
display: block;
|
display: block;
|
||||||
|
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||||
|
.Dropdown-control {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
.Dropdown-control,
|
.Dropdown-control,
|
||||||
.Dropdown-control:hover {
|
.Dropdown-control:hover {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0 1.2em 0 0;
|
padding: 0.9em 1.6em 0.9em 0.9em;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: ${({ theme }) => theme.rightPanel.textColor};
|
color: ${({ theme }) => theme.rightPanel.textColor};
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
@ -34,6 +54,7 @@ export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
||||||
}
|
}
|
||||||
.Dropdown-menu {
|
.Dropdown-menu {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { OperationModel, RedocNormalizedOptions } from '../../services';
|
import { isPayloadSample, OperationModel, RedocNormalizedOptions } from '../../services';
|
||||||
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
|
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
|
||||||
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
|
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
|
||||||
|
|
||||||
|
@ -19,15 +19,10 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { operation } = this.props;
|
const { operation } = this.props;
|
||||||
const requestBodyContent = operation.requestBody && operation.requestBody.content;
|
|
||||||
const hasBodySample = requestBodyContent && requestBodyContent.hasSample;
|
|
||||||
const samples = operation.codeSamples;
|
const samples = operation.codeSamples;
|
||||||
|
|
||||||
const hasSamples = hasBodySample || samples.length > 0;
|
const hasSamples = samples.length > 0;
|
||||||
const hideTabList =
|
const hideTabList = samples.length === 1 ? this.context.hideSingleRequestSampleTab : false;
|
||||||
samples.length + (hasBodySample ? 1 : 0) === 1
|
|
||||||
? this.context.hideSingleRequestSampleTab
|
|
||||||
: false;
|
|
||||||
return (
|
return (
|
||||||
(hasSamples && (
|
(hasSamples && (
|
||||||
<div>
|
<div>
|
||||||
|
@ -35,23 +30,21 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
|
||||||
|
|
||||||
<Tabs defaultIndex={0}>
|
<Tabs defaultIndex={0}>
|
||||||
<TabList hidden={hideTabList}>
|
<TabList hidden={hideTabList}>
|
||||||
{hasBodySample && <Tab key="payload"> Payload </Tab>}
|
|
||||||
{samples.map(sample => (
|
{samples.map(sample => (
|
||||||
<Tab key={sample.lang + '_' + (sample.label || '')}>
|
<Tab key={sample.lang + '_' + (sample.label || '')}>
|
||||||
{sample.label !== undefined ? sample.label : sample.lang}
|
{sample.label !== undefined ? sample.label : sample.lang}
|
||||||
</Tab>
|
</Tab>
|
||||||
))}
|
))}
|
||||||
</TabList>
|
</TabList>
|
||||||
{hasBodySample && (
|
|
||||||
<TabPanel key="payload">
|
|
||||||
<div>
|
|
||||||
<PayloadSamples content={requestBodyContent!} />
|
|
||||||
</div>
|
|
||||||
</TabPanel>
|
|
||||||
)}
|
|
||||||
{samples.map(sample => (
|
{samples.map(sample => (
|
||||||
<TabPanel key={sample.lang}>
|
<TabPanel key={sample.lang + '_' + (sample.label || '')}>
|
||||||
<SourceCodeWithCopy lang={sample.lang} source={sample.source} />
|
{isPayloadSample(sample) ? (
|
||||||
|
<div>
|
||||||
|
<PayloadSamples content={sample.requestBodyContent} />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<SourceCodeWithCopy lang={sample.lang} source={sample.source} />
|
||||||
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
|
@ -28,12 +28,11 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
|
||||||
code={code}
|
code={code}
|
||||||
opened={expanded}
|
opened={expanded}
|
||||||
/>
|
/>
|
||||||
{expanded &&
|
{expanded && !empty && (
|
||||||
!empty && (
|
<ResponseDetailsWrap>
|
||||||
<ResponseDetailsWrap>
|
<ResponseDetails response={this.props.response} />
|
||||||
<ResponseDetails response={this.props.response} />
|
</ResponseDetailsWrap>
|
||||||
</ResponseDetailsWrap>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class ResponseTitle extends React.PureComponent<ResponseTitleProps> {
|
||||||
<ShelfIcon
|
<ShelfIcon
|
||||||
size={'1.5em'}
|
size={'1.5em'}
|
||||||
color={type}
|
color={type}
|
||||||
direction={opened ? 'up' : 'down'}
|
direction={opened ? 'down' : 'right'}
|
||||||
float={'left'}
|
float={'left'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { DiscriminatorDropdown } from './DiscriminatorDropdown';
|
||||||
import { SchemaProps } from './Schema';
|
import { SchemaProps } from './Schema';
|
||||||
|
|
||||||
import { mapWithLast } from '../../utils';
|
import { mapWithLast } from '../../utils';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
|
||||||
export interface ObjectSchemaProps extends SchemaProps {
|
export interface ObjectSchemaProps extends SchemaProps {
|
||||||
discriminator?: {
|
discriminator?: {
|
||||||
|
@ -19,6 +20,8 @@ export interface ObjectSchemaProps extends SchemaProps {
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
|
|
||||||
get parentSchema() {
|
get parentSchema() {
|
||||||
return this.props.discriminator!.parentSchema;
|
return this.props.discriminator!.parentSchema;
|
||||||
}
|
}
|
||||||
|
@ -34,13 +37,15 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
||||||
|
|
||||||
const filteredFields = needFilter
|
const filteredFields = needFilter
|
||||||
? fields.filter(item => {
|
? fields.filter(item => {
|
||||||
return (
|
return !(
|
||||||
(this.props.skipReadOnly && !item.schema.readOnly) ||
|
(this.props.skipReadOnly && item.schema.readOnly) ||
|
||||||
(this.props.skipWriteOnly && !item.schema.writeOnly)
|
(this.props.skipWriteOnly && item.schema.writeOnly)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: fields;
|
: fields;
|
||||||
|
|
||||||
|
const expandByDefault = this.context.expandSingleSchemaField && filteredFields.length === 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PropertiesTable>
|
<PropertiesTable>
|
||||||
{showTitle && <PropertiesTableCaption>{this.props.schema.title}</PropertiesTableCaption>}
|
{showTitle && <PropertiesTableCaption>{this.props.schema.title}</PropertiesTableCaption>}
|
||||||
|
@ -51,6 +56,7 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
||||||
key={field.name}
|
key={field.name}
|
||||||
isLast={isLast}
|
isLast={isLast}
|
||||||
field={field}
|
field={field}
|
||||||
|
expandByDefault={expandByDefault}
|
||||||
renderDiscriminatorSwitch={
|
renderDiscriminatorSwitch={
|
||||||
(discriminator &&
|
(discriminator &&
|
||||||
discriminator.fieldName === field.name &&
|
discriminator.fieldName === field.name &&
|
||||||
|
|
|
@ -44,9 +44,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
if (discriminatorProp !== undefined) {
|
if (discriminatorProp !== undefined) {
|
||||||
if (!oneOf || !oneOf.length) {
|
if (!oneOf || !oneOf.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Looks like you are using discriminator wrong: you don't have any definition inherited from the ${
|
`Looks like you are using discriminator wrong: you don't have any definition inherited from the ${schema.title}`,
|
||||||
schema.title
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -66,9 +64,9 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'object':
|
case 'object':
|
||||||
return <ObjectSchema {...this.props as any} />;
|
return <ObjectSchema {...(this.props as any)} />;
|
||||||
case 'array':
|
case 'array':
|
||||||
return <ArraySchema {...this.props as any} />;
|
return <ArraySchema {...(this.props as any)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybe adjust FieldDetails to accept schema
|
// TODO: maybe adjust FieldDetails to accept schema
|
||||||
|
|
93
src/components/SchemaDefinition/SchemaDefinition.tsx
Normal file
93
src/components/SchemaDefinition/SchemaDefinition.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { DarkRightPanel, MiddlePanel, MimeLabel, Row, Section } from '../../common-elements';
|
||||||
|
import { MediaTypeModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
import { OpenAPIMediaType } from '../../types';
|
||||||
|
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||||
|
import { MediaTypeSamples } from '../PayloadSamples/MediaTypeSamples';
|
||||||
|
import { InvertedSimpleDropdown } from '../PayloadSamples/styled.elements';
|
||||||
|
import { Schema } from '../Schema';
|
||||||
|
|
||||||
|
export interface ObjectDescriptionProps {
|
||||||
|
schemaRef: string;
|
||||||
|
exampleRef?: string;
|
||||||
|
showReadOnly?: boolean;
|
||||||
|
showWriteOnly?: boolean;
|
||||||
|
parser: OpenAPIParser;
|
||||||
|
options: RedocNormalizedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps> {
|
||||||
|
private static getMediaType(schemaRef: string, exampleRef?: string): OpenAPIMediaType {
|
||||||
|
if (!schemaRef) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const info: OpenAPIMediaType = {
|
||||||
|
schema: { $ref: schemaRef },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (exampleRef) {
|
||||||
|
info.examples = { example: { $ref: exampleRef } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _mediaModel: MediaTypeModel;
|
||||||
|
|
||||||
|
private get mediaModel() {
|
||||||
|
const { parser, schemaRef, exampleRef, options } = this.props;
|
||||||
|
if (!this._mediaModel) {
|
||||||
|
this._mediaModel = new MediaTypeModel(
|
||||||
|
parser,
|
||||||
|
'json',
|
||||||
|
false,
|
||||||
|
SchemaDefinition.getMediaType(schemaRef, exampleRef),
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._mediaModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { showReadOnly = true, showWriteOnly = false } = this.props;
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<Row>
|
||||||
|
<MiddlePanel>
|
||||||
|
<Schema
|
||||||
|
skipWriteOnly={!showWriteOnly}
|
||||||
|
skipReadOnly={!showReadOnly}
|
||||||
|
schema={this.mediaModel.schema}
|
||||||
|
/>
|
||||||
|
</MiddlePanel>
|
||||||
|
<DarkRightPanel>
|
||||||
|
<MediaSamplesWrap>
|
||||||
|
<MediaTypeSamples renderDropdown={this.renderDropdown} mediaType={this.mediaModel} />
|
||||||
|
</MediaSamplesWrap>
|
||||||
|
</DarkRightPanel>
|
||||||
|
</Row>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderDropdown = props => {
|
||||||
|
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const MediaSamplesWrap = styled.div`
|
||||||
|
background: ${({ theme }) => theme.codeSample.backgroundColor};
|
||||||
|
& > div,
|
||||||
|
& > pre {
|
||||||
|
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div > pre {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
`;
|
|
@ -7,6 +7,7 @@ import { MenuItem } from '../SideMenu/MenuItem';
|
||||||
import { MarkerService } from '../../services/MarkerService';
|
import { MarkerService } from '../../services/MarkerService';
|
||||||
import { SearchResult } from '../../services/SearchWorker.worker';
|
import { SearchResult } from '../../services/SearchWorker.worker';
|
||||||
|
|
||||||
|
import { bind, debounce } from 'decko';
|
||||||
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||||
import {
|
import {
|
||||||
ClearIcon,
|
ClearIcon,
|
||||||
|
@ -94,11 +95,18 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
setResults(results: SearchResult[], term: string) {
|
setResults(results: SearchResult[], term: string) {
|
||||||
this.setState({
|
this.setState({
|
||||||
results,
|
results,
|
||||||
term,
|
|
||||||
});
|
});
|
||||||
this.props.marker.mark(term);
|
this.props.marker.mark(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
@debounce(400)
|
||||||
|
searchCallback(searchTerm: string) {
|
||||||
|
this.props.search.search(searchTerm).then(res => {
|
||||||
|
this.setResults(res, searchTerm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
search = (event: React.ChangeEvent<HTMLInputElement>) => {
|
search = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const q = event.target.value;
|
const q = event.target.value;
|
||||||
if (q.length < 3) {
|
if (q.length < 3) {
|
||||||
|
@ -106,13 +114,12 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
this.setState(
|
||||||
term: q,
|
{
|
||||||
});
|
term: q,
|
||||||
|
},
|
||||||
this.props.search.search(event.target.value).then(res => {
|
() => this.searchCallback(this.state.term),
|
||||||
this.setResults(res, q);
|
);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { darken } from 'polished';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { darken, getLuminance, lighten } from 'polished';
|
||||||
import styled from '../../styled-components';
|
import styled from '../../styled-components';
|
||||||
import { MenuItemLabel } from '../SideMenu/styled.elements';
|
import { MenuItemLabel } from '../SideMenu/styled.elements';
|
||||||
|
|
||||||
|
@ -16,9 +17,15 @@ export const SearchInput = styled.input.attrs(() => ({
|
||||||
padding: 5px ${props => props.theme.spacing.unit * 2}px 5px
|
padding: 5px ${props => props.theme.spacing.unit * 2}px 5px
|
||||||
${props => props.theme.spacing.unit * 4}px;
|
${props => props.theme.spacing.unit * 4}px;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 1px solid ${({theme}) => darken(0.1, theme.menu.backgroundColor)};
|
border-bottom: 1px solid
|
||||||
font-family: ${({theme}) => theme.typography.fontFamily};
|
${({ theme }) =>
|
||||||
font-size: 1em;
|
(getLuminance(theme.menu.backgroundColor) > 0.5 ? darken : lighten)(
|
||||||
|
0.1,
|
||||||
|
theme.menu.backgroundColor,
|
||||||
|
)};
|
||||||
|
font-family: ${({ theme }) => theme.typography.fontFamily};
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 13px;
|
||||||
color: ${props => props.theme.menu.textColor};
|
color: ${props => props.theme.menu.textColor};
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
@ -26,23 +33,14 @@ export const SearchInput = styled.input.attrs(() => ({
|
||||||
|
|
||||||
export const SearchIcon = styled((props: { className?: string }) => (
|
export const SearchIcon = styled((props: { className?: string }) => (
|
||||||
<svg
|
<svg
|
||||||
version="1.1"
|
|
||||||
id="Layer_1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
x="0px"
|
|
||||||
y="0px"
|
|
||||||
className={props.className}
|
className={props.className}
|
||||||
viewBox="0 0 24 24"
|
version="1.1"
|
||||||
xmlSpace="preserve"
|
viewBox="0 0 1000 1000"
|
||||||
|
x="0px"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
y="0px"
|
||||||
>
|
>
|
||||||
<g>
|
<path d="M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z" />
|
||||||
<path
|
|
||||||
className="st0"
|
|
||||||
d="M22.7,21.5l-5.1-5c1.5-1.7,2.4-4,2.4-6.5c0-5.5-4.5-10-10-10S0,4.5,0,10s4.5,10,10,10c2.3,0,4.4-0.8,6.1-2.1
|
|
||||||
l5.2,5.1c0.2,0.2,0.4,0.3,0.7,0.3c0.3,0,0.5-0.1,0.7-0.3C23.1,22.5,23.1,21.9,22.7,21.5z M10,18c-4.4,0-8-3.6-8-8s3.6-8,8-8
|
|
||||||
s8,3.6,8,8S14.4,18,10,18z"
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
)).attrs({
|
)).attrs({
|
||||||
className: 'search-icon',
|
className: 'search-icon',
|
||||||
|
@ -59,11 +57,12 @@ export const SearchIcon = styled((props: { className?: string }) => (
|
||||||
|
|
||||||
export const SearchResultsBox = styled.div`
|
export const SearchResultsBox = styled.div`
|
||||||
padding: ${props => props.theme.spacing.unit}px 0;
|
padding: ${props => props.theme.spacing.unit}px 0;
|
||||||
background-color: #ededed;
|
background-color: ${({ theme }) => darken(0.05, theme.menu.backgroundColor)}};
|
||||||
|
color: ${props => props.theme.menu.textColor};
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
max-height: 250px;
|
max-height: 250px;
|
||||||
border-top: 1px solid #e1e1e1;
|
border-top: ${({ theme }) => darken(0.1, theme.menu.backgroundColor)}};
|
||||||
border-bottom: 1px solid #e1e1e1;
|
border-bottom: ${({ theme }) => darken(0.1, theme.menu.backgroundColor)}};
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
@ -72,17 +71,14 @@ export const SearchResultsBox = styled.div`
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
padding-bottom: 6px;
|
padding-bottom: 6px;
|
||||||
|
|
||||||
&:hover {
|
&:hover,
|
||||||
background-color: #e1e1e1;
|
&.active {
|
||||||
|
background-color: ${({ theme }) => darken(0.1, theme.menu.backgroundColor)};
|
||||||
}
|
}
|
||||||
|
|
||||||
> svg {
|
> svg {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
|
||||||
background-color: #e1e1e1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ export class OAuthFlow extends React.PureComponent<OAuthFlowProps> {
|
||||||
<strong> Scopes: </strong>
|
<strong> Scopes: </strong>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{Object.keys(flow!.scopes).map(scope => (
|
{Object.keys(flow!.scopes || {}).map(scope => (
|
||||||
<li key={scope}>
|
<li key={scope}>
|
||||||
<code>{scope}</code> - <Markdown inline={true} source={flow!.scopes[scope] || ''} />
|
<code>{scope}</code> - <Markdown inline={true} source={flow!.scopes[scope] || ''} />
|
||||||
</li>
|
</li>
|
||||||
|
@ -80,7 +80,7 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
<table className="security-details">
|
<table className="security-details">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th> Security scheme type: </th>
|
<th> Security Scheme Type </th>
|
||||||
<td> {AUTH_TYPES[scheme.type] || scheme.type} </td>
|
<td> {AUTH_TYPES[scheme.type] || scheme.type} </td>
|
||||||
</tr>
|
</tr>
|
||||||
{scheme.apiKey ? (
|
{scheme.apiKey ? (
|
||||||
|
@ -97,7 +97,7 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
|
scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
|
||||||
<tr key="bearer">
|
<tr key="bearer">
|
||||||
<th> Bearer format </th>
|
<th> Bearer format </th>
|
||||||
<td> "{scheme.http.bearerFormat}" </td>
|
<td> "{scheme.http.bearerFormat}" </td>
|
||||||
</tr>
|
</tr>
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -105,7 +105,11 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
<tr>
|
<tr>
|
||||||
<th> Connect URL </th>
|
<th> Connect URL </th>
|
||||||
<td>
|
<td>
|
||||||
<a target="_blank" href={scheme.openId.connectUrl}>
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href={scheme.openId.connectUrl}
|
||||||
|
>
|
||||||
{scheme.openId.connectUrl}
|
{scheme.openId.connectUrl}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// import { observe } from 'mobx';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ export interface MenuItemProps {
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class MenuItem extends React.Component<MenuItemProps> {
|
export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
ref: Element | null;
|
ref = React.createRef<HTMLLabelElement>();
|
||||||
|
|
||||||
activate = (evt: React.MouseEvent<HTMLElement>) => {
|
activate = (evt: React.MouseEvent<HTMLElement>) => {
|
||||||
this.props.onActivate!(this.props.item);
|
this.props.onActivate!(this.props.item);
|
||||||
|
@ -31,48 +32,36 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollIntoViewIfActive() {
|
scrollIntoViewIfActive() {
|
||||||
if (this.props.item.active && this.ref) {
|
if (this.props.item.active && this.ref.current) {
|
||||||
this.ref.scrollIntoViewIfNeeded();
|
this.ref.current.scrollIntoViewIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveRef = ref => {
|
|
||||||
this.ref = ref;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item, withoutChildren } = this.props;
|
const { item, withoutChildren } = this.props;
|
||||||
return (
|
return (
|
||||||
<MenuItemLi
|
<MenuItemLi onClick={this.activate} depth={item.depth} data-item-id={item.id}>
|
||||||
onClick={this.activate}
|
|
||||||
depth={item.depth}
|
|
||||||
ref={this.saveRef}
|
|
||||||
data-item-id={item.id}
|
|
||||||
>
|
|
||||||
{item.type === 'operation' ? (
|
{item.type === 'operation' ? (
|
||||||
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
||||||
) : (
|
) : (
|
||||||
<MenuItemLabel depth={item.depth} active={item.active} type={item.type}>
|
<MenuItemLabel depth={item.depth} active={item.active} type={item.type} ref={this.ref}>
|
||||||
<MenuItemTitle title={item.name}>
|
<MenuItemTitle title={item.name}>
|
||||||
{item.name}
|
{item.name}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</MenuItemTitle>
|
</MenuItemTitle>
|
||||||
{(item.depth > 0 &&
|
{(item.depth > 0 && item.items.length > 0 && (
|
||||||
item.items.length > 0 && (
|
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
|
||||||
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
|
)) ||
|
||||||
)) ||
|
|
||||||
null}
|
null}
|
||||||
</MenuItemLabel>
|
</MenuItemLabel>
|
||||||
)}
|
)}
|
||||||
{!withoutChildren &&
|
{!withoutChildren && item.items && item.items.length > 0 && (
|
||||||
item.items &&
|
<MenuItems
|
||||||
item.items.length > 0 && (
|
expanded={item.expanded}
|
||||||
<MenuItems
|
items={item.items}
|
||||||
expanded={item.expanded}
|
onActivate={this.props.onActivate}
|
||||||
items={item.items}
|
/>
|
||||||
onActivate={this.props.onActivate}
|
)}
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</MenuItemLi>
|
</MenuItemLi>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -83,11 +72,24 @@ export interface OperationMenuItemContentProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
|
export class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
|
||||||
|
ref = React.createRef<HTMLLabelElement>();
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (this.props.item.active && this.ref.current) {
|
||||||
|
this.ref.current.scrollIntoViewIfNeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item } = this.props;
|
const { item } = this.props;
|
||||||
return (
|
return (
|
||||||
<MenuItemLabel depth={item.depth} active={item.active} deprecated={item.deprecated}>
|
<MenuItemLabel
|
||||||
|
depth={item.depth}
|
||||||
|
active={item.active}
|
||||||
|
deprecated={item.deprecated}
|
||||||
|
ref={this.ref}
|
||||||
|
>
|
||||||
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
||||||
<MenuItemTitle width="calc(100% - 38px)">
|
<MenuItemTitle width="calc(100% - 38px)">
|
||||||
{item.name}
|
{item.name}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { StyledLink } from '../../../src/common-elements';
|
|
||||||
|
|
||||||
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
|
||||||
|
|
||||||
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { MenuItems } from './MenuItems';
|
import { MenuItems } from './MenuItems';
|
||||||
|
|
||||||
|
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||||
import { RedocAttribution } from './styled.elements';
|
import { RedocAttribution } from './styled.elements';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
|
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
private _updateScroll?: () => void;
|
private _updateScroll?: () => void;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -22,17 +23,21 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
||||||
wheelPropagation: false,
|
wheelPropagation: false,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItems items={store.items} onActivate={this.activate} root={true}/>
|
<MenuItems items={store.items} onActivate={this.activate} root={true} />
|
||||||
<RedocAttribution>
|
<RedocAttribution>
|
||||||
<StyledLink href="https://www.opentext.com/" target={'_blank'}>
|
<a target="_blank" rel="noopener noreferrer" href="https://www.opentext.com/">
|
||||||
© Copyright 2019 OpenText Corp
|
© Copyright 2019 OpenText Corp
|
||||||
</StyledLink>
|
</a>
|
||||||
</RedocAttribution>
|
</RedocAttribution>
|
||||||
</PerfectScrollbarWrap>
|
</PerfectScrollbarWrap>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
activate = (item: IMenuItem) => {
|
activate = (item: IMenuItem) => {
|
||||||
|
if (item && item.active && this.context.menuToggle) {
|
||||||
|
return item.expanded ? item.collapse() : item.expand();
|
||||||
|
}
|
||||||
|
|
||||||
this.props.menu.activateAndScroll(item, true);
|
this.props.menu.activateAndScroll(item, true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this._updateScroll) {
|
if (this._updateScroll) {
|
||||||
|
|
|
@ -104,7 +104,7 @@ export const menuItemDepth = {
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
text-transform: ${({ theme }) => theme.menu.level1Items.textTransform};
|
text-transform: ${({ theme }) => theme.menu.level1Items.textTransform};
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${props => props.theme.colors.primary.main};
|
color: ${props => props.theme.menu.activeTextColor};
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
2: css`
|
2: css`
|
||||||
|
@ -126,7 +126,7 @@ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
|
||||||
}),
|
}),
|
||||||
}))<MenuItemLabelType>`
|
}))<MenuItemLabelType>`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${props => (props.active ? props.theme.colors.primary.main : props.theme.menu.textColor)};
|
color: ${props => (props.active ? props.theme.menu.activeTextColor : props.theme.menu.textColor)};
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 12.5px ${props => props.theme.spacing.unit * 4}px;
|
padding: 12.5px ${props => props.theme.spacing.unit * 4}px;
|
||||||
${({ depth, type, theme }) =>
|
${({ depth, type, theme }) =>
|
||||||
|
|
|
@ -19,6 +19,10 @@ export interface StickySidebarProps {
|
||||||
menu: MenuStore;
|
menu: MenuStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StickySidebarState {
|
||||||
|
offsetTop?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const stickyfill = Stickyfill && Stickyfill();
|
const stickyfill = Stickyfill && Stickyfill();
|
||||||
|
|
||||||
const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
||||||
|
@ -29,7 +33,7 @@ const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
contain: strict;
|
/* contain: strict; TODO: breaks layout since Chrome 80*/
|
||||||
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
|
@ -40,7 +44,7 @@ const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #ffffff;
|
background: ${({ theme }) => theme.menu.backgroundColor};
|
||||||
display: ${props => (props.open ? 'flex' : 'none')};
|
display: ${props => (props.open ? 'flex' : 'none')};
|
||||||
`};
|
`};
|
||||||
|
|
||||||
|
@ -77,13 +81,26 @@ const FloatingButton = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class StickyResponsiveSidebar extends React.Component<StickySidebarProps> {
|
export class StickyResponsiveSidebar extends React.Component<
|
||||||
|
StickySidebarProps,
|
||||||
|
StickySidebarState
|
||||||
|
> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
|
context!: React.ContextType<typeof OptionsContext>;
|
||||||
|
state: StickySidebarState = { offsetTop: '0px' };
|
||||||
|
|
||||||
stickyElement: Element;
|
stickyElement: Element;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (stickyfill) {
|
if (stickyfill) {
|
||||||
stickyfill.add(this.stickyElement);
|
stickyfill.add(this.stickyElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rerender when hydrating from SSR
|
||||||
|
// see https://github.com/facebook/react/issues/8017#issuecomment-256351955
|
||||||
|
this.setState({
|
||||||
|
offsetTop: this.getScrollYOffset(this.context),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -92,7 +109,7 @@ export class StickyResponsiveSidebar extends React.Component<StickySidebarProps>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getScrollYOffset(options) {
|
getScrollYOffset(options: RedocNormalizedOptions) {
|
||||||
let top;
|
let top;
|
||||||
if (this.props.scrollYOffset !== undefined) {
|
if (this.props.scrollYOffset !== undefined) {
|
||||||
top = RedocNormalizedOptions.normalizeScrollYOffset(this.props.scrollYOffset)();
|
top = RedocNormalizedOptions.normalizeScrollYOffset(this.props.scrollYOffset)();
|
||||||
|
@ -105,43 +122,32 @@ export class StickyResponsiveSidebar extends React.Component<StickySidebarProps>
|
||||||
render() {
|
render() {
|
||||||
const open = this.props.menu.sideBarOpened;
|
const open = this.props.menu.sideBarOpened;
|
||||||
|
|
||||||
const style = options => {
|
const top = this.state.offsetTop;
|
||||||
const top = this.getScrollYOffset(options);
|
|
||||||
return {
|
|
||||||
top,
|
|
||||||
height: `calc(100vh - ${top})`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsContext.Consumer>
|
<>
|
||||||
{options => (
|
<StyledStickySidebar
|
||||||
<>
|
open={open}
|
||||||
<StyledStickySidebar
|
className={this.props.className}
|
||||||
open={open}
|
style={{
|
||||||
className={this.props.className}
|
top,
|
||||||
style={style(options)}
|
height: `calc(100vh - ${top})`,
|
||||||
// tslint:disable-next-line
|
}}
|
||||||
ref={el => {
|
// tslint:disable-next-line
|
||||||
this.stickyElement = el as any;
|
ref={el => {
|
||||||
}}
|
this.stickyElement = el as any;
|
||||||
>
|
}}
|
||||||
{this.props.children}
|
>
|
||||||
</StyledStickySidebar>
|
{this.props.children}
|
||||||
<FloatingButton onClick={this.toggleNavMenu}>
|
</StyledStickySidebar>
|
||||||
<AnimatedChevronButton open={open} />
|
<FloatingButton onClick={this.toggleNavMenu}>
|
||||||
</FloatingButton>
|
<AnimatedChevronButton open={open} />
|
||||||
</>
|
</FloatingButton>
|
||||||
)}
|
</>
|
||||||
</OptionsContext.Consumer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggleNavMenu = () => {
|
private toggleNavMenu = () => {
|
||||||
this.props.menu.toggleSidebar();
|
this.props.menu.toggleSidebar();
|
||||||
};
|
};
|
||||||
|
|
||||||
// private closeNavMenu = () => {
|
|
||||||
// this.setState({ open: false });
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import memoize from 'memoize-one';
|
import * as memoize from 'memoize-one/dist/memoize-one.cjs'; // fixme: https://github.com/alexreardon/memoize-one/issues/37
|
||||||
import { Component, createContext } from 'react';
|
import { Component, createContext } from 'react';
|
||||||
|
|
||||||
import { AppStore } from '../services/';
|
import { AppStore } from '../services/';
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable import/no-internal-modules */
|
||||||
/* tslint:disable:no-implicit-dependencies */
|
/* tslint:disable:no-implicit-dependencies */
|
||||||
|
|
||||||
import { shallow } from 'enzyme';
|
import { shallow } from 'enzyme';
|
||||||
|
|
|
@ -24,14 +24,14 @@ describe('Components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should collapse/uncollapse', () => {
|
test('should collapse/uncollapse', () => {
|
||||||
expect(component.html()).not.toContain('class="hoverable"'); // all are collapesed by default
|
expect(component.html()).not.toContain('class="hoverable"'); // all are collapsed by default
|
||||||
const expandAll = component.find('div > span[children=" Expand all "]');
|
const expandAll = component.find('div > span[children=" Expand all "]');
|
||||||
expandAll.simulate('click');
|
expandAll.simulate('click');
|
||||||
expect(component.html()).toContain('class="hoverable"'); // all are collapesed
|
expect(component.html()).toContain('class="hoverable"'); // all are collapsed
|
||||||
|
|
||||||
const collapseAll = component.find('div > span[children=" Collapse all "]');
|
const collapseAll = component.find('div > span[children=" Collapse all "]');
|
||||||
collapseAll.simulate('click');
|
collapseAll.simulate('click');
|
||||||
expect(component.html()).not.toContain('class="hoverable"'); // all are collapesed
|
expect(component.html()).not.toContain('class="hoverable"'); // all are collapsed
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should collapse/uncollapse', () => {
|
test('should collapse/uncollapse', () => {
|
||||||
|
|
|
@ -9,7 +9,8 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"example": undefined,
|
"example": undefined,
|
||||||
"expanded": false,
|
"expanded": undefined,
|
||||||
|
"explode": false,
|
||||||
"in": undefined,
|
"in": undefined,
|
||||||
"kind": "field",
|
"kind": "field",
|
||||||
"name": "packSize",
|
"name": "packSize",
|
||||||
|
@ -58,7 +59,8 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"description": "",
|
"description": "",
|
||||||
"example": undefined,
|
"example": undefined,
|
||||||
"expanded": false,
|
"expanded": undefined,
|
||||||
|
"explode": false,
|
||||||
"in": undefined,
|
"in": undefined,
|
||||||
"kind": "field",
|
"kind": "field",
|
||||||
"name": "type",
|
"name": "type",
|
||||||
|
|
|
@ -28,3 +28,5 @@ export * from './OptionsProvider';
|
||||||
export * from './SideMenu/';
|
export * from './SideMenu/';
|
||||||
export * from './StickySidebar/StickyResponsiveSidebar';
|
export * from './StickySidebar/StickyResponsiveSidebar';
|
||||||
export * from './SearchBox/SearchBox';
|
export * from './SearchBox/SearchBox';
|
||||||
|
export * from './SchemaDefinition/SchemaDefinition';
|
||||||
|
export * from './SourceCode/SourceCode';
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import 'core-js/es6/promise';
|
import 'core-js/es/promise';
|
||||||
import 'core-js/fn/array/find';
|
|
||||||
import 'core-js/fn/object/assign';
|
|
||||||
import 'core-js/fn/string/ends-with';
|
|
||||||
import 'core-js/fn/string/starts-with';
|
|
||||||
|
|
||||||
import 'core-js/es6/map';
|
import 'core-js/es/array/find';
|
||||||
import 'core-js/es6/symbol';
|
import 'core-js/es/object/assign';
|
||||||
|
import 'core-js/es/string/ends-with';
|
||||||
|
import 'core-js/es/string/starts-with';
|
||||||
|
|
||||||
|
import 'core-js/es/map';
|
||||||
|
import 'core-js/es/symbol';
|
||||||
|
|
||||||
import 'unfetch/polyfill/index';
|
import 'unfetch/polyfill/index';
|
||||||
import 'url-polyfill';
|
import 'url-polyfill';
|
||||||
|
|
|
@ -10,8 +10,15 @@ import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOption
|
||||||
import { ScrollService } from './ScrollService';
|
import { ScrollService } from './ScrollService';
|
||||||
import { SearchStore } from './SearchStore';
|
import { SearchStore } from './SearchStore';
|
||||||
|
|
||||||
|
import { SchemaDefinition } from '../components/SchemaDefinition/SchemaDefinition';
|
||||||
import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
|
import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
|
||||||
import { SECURITY_DEFINITIONS_COMPONENT_NAME } from '../utils/openapi';
|
import {
|
||||||
|
SCHEMA_DEFINITION_JSX_NAME,
|
||||||
|
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||||
|
SECURITY_DEFINITIONS_JSX_NAME,
|
||||||
|
} from '../utils/openapi';
|
||||||
|
|
||||||
|
import { IS_BROWSER } from '../utils';
|
||||||
|
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
menu: {
|
menu: {
|
||||||
|
@ -96,6 +103,9 @@ export class AppStore {
|
||||||
dispose() {
|
dispose() {
|
||||||
this.scroll.dispose();
|
this.scroll.dispose();
|
||||||
this.menu.dispose();
|
this.menu.dispose();
|
||||||
|
if (this.search) {
|
||||||
|
this.search.dispose();
|
||||||
|
}
|
||||||
if (this.disposer != null) {
|
if (this.disposer != null) {
|
||||||
this.disposer();
|
this.disposer();
|
||||||
}
|
}
|
||||||
|
@ -126,16 +136,16 @@ export class AppStore {
|
||||||
|
|
||||||
const elements: Element[] = [];
|
const elements: Element[] = [];
|
||||||
for (let i = start; i < end; i++) {
|
for (let i = start; i < end; i++) {
|
||||||
let elem = this.menu.getElementAt(i);
|
const elem = this.menu.getElementAt(i);
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (this.menu.flatItems[i].type === 'section') {
|
elements.push(elem);
|
||||||
elem = elem.parentElement!.parentElement;
|
}
|
||||||
}
|
|
||||||
if (elem) {
|
if (idx === -1 && IS_BROWSER) {
|
||||||
elements.push(elem);
|
const $description = document.querySelector('[data-role="redoc-description"]');
|
||||||
}
|
if ($description) elements.push($description);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.marker.addOnly(elements);
|
this.marker.addOnly(elements);
|
||||||
|
@ -151,5 +161,18 @@ const DEFAULT_OPTIONS: RedocRawOptions = {
|
||||||
securitySchemes: store.spec.securitySchemes,
|
securitySchemes: store.spec.securitySchemes,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
[SECURITY_DEFINITIONS_JSX_NAME]: {
|
||||||
|
component: SecurityDefs,
|
||||||
|
propsSelector: (store: AppStore) => ({
|
||||||
|
securitySchemes: store.spec.securitySchemes,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
[SCHEMA_DEFINITION_JSX_NAME]: {
|
||||||
|
component: SchemaDefinition,
|
||||||
|
propsSelector: (store: AppStore) => ({
|
||||||
|
parser: store.spec.parser,
|
||||||
|
options: store.options,
|
||||||
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,10 @@ export class ClipboardService {
|
||||||
if ((document as any).selection) {
|
if ((document as any).selection) {
|
||||||
(document as any).selection.empty();
|
(document as any).selection.empty();
|
||||||
} else if (window.getSelection) {
|
} else if (window.getSelection) {
|
||||||
window.getSelection().removeAllRanges();
|
const selection = window.getSelection();
|
||||||
|
if (selection) {
|
||||||
|
selection.removeAllRanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import * as marked from 'marked';
|
import * as marked from 'marked';
|
||||||
|
|
||||||
import { highlight, safeSlugify } from '../utils';
|
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
||||||
import { AppStore } from './AppStore';
|
import { AppStore } from './AppStore';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
|
@ -45,6 +45,14 @@ export class MarkdownRenderer {
|
||||||
return compRegexp.test(rawText);
|
return compRegexp.test(rawText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getTextBeforeHading(md: string, heading: string): string {
|
||||||
|
const headingLinePos = md.search(new RegExp(`^##?\\s+${heading}`, 'm'));
|
||||||
|
if (headingLinePos > -1) {
|
||||||
|
return md.substring(0, headingLinePos);
|
||||||
|
}
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
|
||||||
headings: MarkdownHeading[] = [];
|
headings: MarkdownHeading[] = [];
|
||||||
currentTopHeading: MarkdownHeading;
|
currentTopHeading: MarkdownHeading;
|
||||||
|
|
||||||
|
@ -65,6 +73,7 @@ export class MarkdownRenderer {
|
||||||
container: MarkdownHeading[] = this.headings,
|
container: MarkdownHeading[] = this.headings,
|
||||||
parentId?: string,
|
parentId?: string,
|
||||||
): MarkdownHeading {
|
): MarkdownHeading {
|
||||||
|
name = unescapeHTMLChars(name);
|
||||||
const item = {
|
const item = {
|
||||||
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
|
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
|
||||||
name,
|
name,
|
||||||
|
@ -88,7 +97,7 @@ export class MarkdownRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachHeadingsDescriptions(rawText: string) {
|
attachHeadingsDescriptions(rawText: string) {
|
||||||
const buildRegexp = heading => {
|
const buildRegexp = (heading: MarkdownHeading) => {
|
||||||
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -118,7 +127,12 @@ export class MarkdownRenderer {
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
headingRule = (text: string, level: number, raw: string, slugger: marked.Slugger) => {
|
headingRule = (
|
||||||
|
text: string,
|
||||||
|
level: 1 | 2 | 3 | 4 | 5 | 6,
|
||||||
|
raw: string,
|
||||||
|
slugger: marked.Slugger,
|
||||||
|
) => {
|
||||||
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) {
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class MenuBuilder {
|
||||||
|
|
||||||
const items: ContentItemModel[] = [];
|
const items: ContentItemModel[] = [];
|
||||||
const tagsMap = MenuBuilder.getTagsWithOperations(spec);
|
const tagsMap = MenuBuilder.getTagsWithOperations(spec);
|
||||||
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', options));
|
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', undefined, 1, options));
|
||||||
if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) {
|
if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) {
|
||||||
items.push(
|
items.push(
|
||||||
...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap, options),
|
...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap, options),
|
||||||
|
@ -59,14 +59,23 @@ export class MenuBuilder {
|
||||||
*/
|
*/
|
||||||
static addMarkdownItems(
|
static addMarkdownItems(
|
||||||
description: string,
|
description: string,
|
||||||
|
parent: GroupModel | undefined,
|
||||||
|
initialDepth: number,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
): ContentItemModel[] {
|
): ContentItemModel[] {
|
||||||
const renderer = new MarkdownRenderer(options);
|
const renderer = new MarkdownRenderer(options);
|
||||||
const headings = renderer.extractHeadings(description || '');
|
const headings = renderer.extractHeadings(description || '');
|
||||||
|
|
||||||
const mapHeadingsDeep = (parent, items, depth = 1) =>
|
if (headings.length && parent && parent.description) {
|
||||||
|
parent.description = MarkdownRenderer.getTextBeforeHading(
|
||||||
|
parent.description,
|
||||||
|
headings[0].name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapHeadingsDeep = (_parent, items, depth = 1) =>
|
||||||
items.map(heading => {
|
items.map(heading => {
|
||||||
const group = new GroupModel('section', heading, parent);
|
const group = new GroupModel('section', heading, _parent);
|
||||||
group.depth = depth;
|
group.depth = depth;
|
||||||
if (heading.items) {
|
if (heading.items) {
|
||||||
group.items = mapHeadingsDeep(group, heading.items, depth + 1);
|
group.items = mapHeadingsDeep(group, heading.items, depth + 1);
|
||||||
|
@ -82,11 +91,11 @@ export class MenuBuilder {
|
||||||
return group;
|
return group;
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapHeadingsDeep(undefined, headings);
|
return mapHeadingsDeep(parent, headings, initialDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of OperationsGroup items for the tag groups (x-tagGroups vendor extenstion)
|
* Returns array of OperationsGroup items for the tag groups (x-tagGroups vendor extension)
|
||||||
* @param tags value of `x-tagGroups` vendor extension
|
* @param tags value of `x-tagGroups` vendor extension
|
||||||
*/
|
*/
|
||||||
static getTagGroupsItems(
|
static getTagGroupsItems(
|
||||||
|
@ -144,15 +153,22 @@ export class MenuBuilder {
|
||||||
}
|
}
|
||||||
const item = new GroupModel('tag', tag, parent);
|
const item = new GroupModel('tag', tag, parent);
|
||||||
item.depth = GROUP_DEPTH + 1;
|
item.depth = GROUP_DEPTH + 1;
|
||||||
item.items = this.getOperationsItems(parser, item, tag, item.depth + 1, options);
|
|
||||||
|
|
||||||
// don't put empty tag into content, instead put its operations
|
// don't put empty tag into content, instead put its operations
|
||||||
if (tag.name === '') {
|
if (tag.name === '') {
|
||||||
const items = this.getOperationsItems(parser, undefined, tag, item.depth + 1, options);
|
const items = [
|
||||||
|
...MenuBuilder.addMarkdownItems(tag.description || '', item, item.depth + 1, options),
|
||||||
|
...this.getOperationsItems(parser, undefined, tag, item.depth + 1, options),
|
||||||
|
];
|
||||||
res.push(...items);
|
res.push(...items);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.items = [
|
||||||
|
...MenuBuilder.addMarkdownItems(tag.description || '', item, item.depth + 1, options),
|
||||||
|
...this.getOperationsItems(parser, item, tag, item.depth + 1, options),
|
||||||
|
];
|
||||||
|
|
||||||
res.push(item);
|
res.push(item);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -116,7 +116,7 @@ export class MenuStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isScrolledDown) {
|
if (isScrolledDown) {
|
||||||
const el = this.getElementAt(itemIdx + 1);
|
const el = this.getElementAtOrFirstChild(itemIdx + 1);
|
||||||
if (this.scroll.isElementBellow(el)) {
|
if (this.scroll.isElementBellow(el)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,18 @@ export class MenuStore {
|
||||||
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
|
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get section/operation DOM Node related to the item or if it is group item, returns first item of the group
|
||||||
|
* @param idx item absolute index
|
||||||
|
*/
|
||||||
|
getElementAtOrFirstChild(idx: number): Element | null {
|
||||||
|
let item = this.flatItems[idx];
|
||||||
|
if (item && item.type === 'group') {
|
||||||
|
item = item.items[0];
|
||||||
|
}
|
||||||
|
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* current active item
|
* current active item
|
||||||
*/
|
*/
|
||||||
|
@ -178,7 +190,7 @@ export class MenuStore {
|
||||||
* activate menu item
|
* activate menu item
|
||||||
* @param item item to activate
|
* @param item item to activate
|
||||||
* @param updateLocation [true] whether to update location
|
* @param updateLocation [true] whether to update location
|
||||||
* @param rewriteHistory [false] whether to rewrite browser history (do not create new enrty)
|
* @param rewriteHistory [false] whether to rewrite browser history (do not create new entry)
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
activate(
|
activate(
|
||||||
|
@ -189,6 +201,11 @@ export class MenuStore {
|
||||||
if ((this.activeItem && this.activeItem.id) === (item && item.id)) {
|
if ((this.activeItem && this.activeItem.id) === (item && item.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item && item.type === 'group') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.deactivate(this.activeItem);
|
this.deactivate(this.activeItem);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
this.history.replace('', rewriteHistory);
|
this.history.replace('', rewriteHistory);
|
||||||
|
|
|
@ -4,7 +4,11 @@ import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
|
||||||
|
|
||||||
import { appendToMdHeading, IS_BROWSER } from '../utils/';
|
import { appendToMdHeading, IS_BROWSER } from '../utils/';
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer } from '../utils/JsonPointer';
|
||||||
import { isNamedDefinition, SECURITY_DEFINITIONS_COMPONENT_NAME } from '../utils/openapi';
|
import {
|
||||||
|
isNamedDefinition,
|
||||||
|
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||||
|
SECURITY_DEFINITIONS_JSX_NAME,
|
||||||
|
} from '../utils/openapi';
|
||||||
import { buildComponentComment, MarkdownRenderer } from './MarkdownRenderer';
|
import { buildComponentComment, MarkdownRenderer } from './MarkdownRenderer';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
|
@ -40,6 +44,7 @@ class RefCounter {
|
||||||
export class OpenAPIParser {
|
export class OpenAPIParser {
|
||||||
specUrl?: string;
|
specUrl?: string;
|
||||||
spec: OpenAPISpec;
|
spec: OpenAPISpec;
|
||||||
|
mergeRefs: Set<string>;
|
||||||
|
|
||||||
private _refCounter: RefCounter = new RefCounter();
|
private _refCounter: RefCounter = new RefCounter();
|
||||||
|
|
||||||
|
@ -53,6 +58,8 @@ export class OpenAPIParser {
|
||||||
|
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
|
|
||||||
|
this.mergeRefs = new Set();
|
||||||
|
|
||||||
const href = IS_BROWSER ? window.location.href : '';
|
const href = IS_BROWSER ? window.location.href : '';
|
||||||
if (typeof specUrl === 'string') {
|
if (typeof specUrl === 'string') {
|
||||||
this.specUrl = urlResolve(href, specUrl);
|
this.specUrl = urlResolve(href, specUrl);
|
||||||
|
@ -74,7 +81,10 @@ export class OpenAPIParser {
|
||||||
) {
|
) {
|
||||||
// Automatically inject Authentication section with SecurityDefinitions component
|
// Automatically inject Authentication section with SecurityDefinitions component
|
||||||
const description = spec.info.description || '';
|
const description = spec.info.description || '';
|
||||||
if (!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_COMPONENT_NAME)) {
|
if (
|
||||||
|
!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_COMPONENT_NAME) &&
|
||||||
|
!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_JSX_NAME)
|
||||||
|
) {
|
||||||
const comment = buildComponentComment(SECURITY_DEFINITIONS_COMPONENT_NAME);
|
const comment = buildComponentComment(SECURITY_DEFINITIONS_COMPONENT_NAME);
|
||||||
spec.info.description = appendToMdHeading(description, 'Authentication', comment);
|
spec.info.description = appendToMdHeading(description, 'Authentication', comment);
|
||||||
}
|
}
|
||||||
|
@ -102,7 +112,7 @@ export class OpenAPIParser {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checks if the objectt is OpenAPI reference (containts $ref property)
|
* checks if the object is OpenAPI reference (contains $ref property)
|
||||||
*/
|
*/
|
||||||
isRef(obj: any): obj is OpenAPIRef {
|
isRef(obj: any): obj is OpenAPIRef {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
|
@ -112,7 +122,7 @@ export class OpenAPIParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* resets visited enpoints. should be run after
|
* resets visited endpoints. should be run after
|
||||||
*/
|
*/
|
||||||
resetVisited() {
|
resetVisited() {
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
@ -136,9 +146,9 @@ export class OpenAPIParser {
|
||||||
/**
|
/**
|
||||||
* Resolve given reference object or return as is if it is not a reference
|
* Resolve given reference object or return as is if it is not a reference
|
||||||
* @param obj object to dereference
|
* @param obj object to dereference
|
||||||
* @param forceCircular whether to dereference even if it is cirular ref
|
* @param forceCircular whether to dereference even if it is circular ref
|
||||||
*/
|
*/
|
||||||
deref<T extends object>(obj: OpenAPIRef | T, forceCircular: boolean = false): T {
|
deref<T extends object>(obj: OpenAPIRef | T, forceCircular = false): T {
|
||||||
if (this.isRef(obj)) {
|
if (this.isRef(obj)) {
|
||||||
const resolved = this.byRef<T>(obj.$ref)!;
|
const resolved = this.byRef<T>(obj.$ref)!;
|
||||||
const visited = this._refCounter.visited(obj.$ref);
|
const visited = this._refCounter.visited(obj.$ref);
|
||||||
|
@ -167,16 +177,21 @@ export class OpenAPIParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge allOf contsraints.
|
* Merge allOf constraints.
|
||||||
* @param schema schema with allOF
|
* @param schema schema with allOF
|
||||||
* @param $ref pointer of the schema
|
* @param $ref pointer of the schema
|
||||||
* @param forceCircular whether to dereference children even if it is a cirular ref
|
* @param forceCircular whether to dereference children even if it is a circular ref
|
||||||
*/
|
*/
|
||||||
mergeAllOf(
|
mergeAllOf(
|
||||||
schema: OpenAPISchema,
|
schema: OpenAPISchema,
|
||||||
$ref?: string,
|
$ref?: string,
|
||||||
forceCircular: boolean = false,
|
forceCircular: boolean = false,
|
||||||
|
used$Refs = new Set<string>(),
|
||||||
): MergedOpenAPISchema {
|
): MergedOpenAPISchema {
|
||||||
|
if ($ref) {
|
||||||
|
used$Refs.add($ref);
|
||||||
|
}
|
||||||
|
|
||||||
schema = this.hoistOneOfs(schema);
|
schema = this.hoistOneOfs(schema);
|
||||||
|
|
||||||
if (schema.allOf === undefined) {
|
if (schema.allOf === undefined) {
|
||||||
|
@ -198,16 +213,25 @@ export class OpenAPIParser {
|
||||||
receiver.items = { ...receiver.items };
|
receiver.items = { ...receiver.items };
|
||||||
}
|
}
|
||||||
|
|
||||||
const allOfSchemas = schema.allOf.map(subSchema => {
|
const allOfSchemas = schema.allOf
|
||||||
const resolved = this.deref(subSchema, forceCircular);
|
.map(subSchema => {
|
||||||
const subRef = subSchema.$ref || undefined;
|
if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
|
||||||
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular);
|
return undefined;
|
||||||
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
}
|
||||||
return {
|
|
||||||
$ref: subRef,
|
const resolved = this.deref(subSchema, forceCircular);
|
||||||
schema: subMerged,
|
const subRef = subSchema.$ref || undefined;
|
||||||
};
|
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
|
||||||
});
|
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
||||||
|
return {
|
||||||
|
$ref: subRef,
|
||||||
|
schema: subMerged,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(child => child !== undefined) as Array<{
|
||||||
|
$ref: string | undefined;
|
||||||
|
schema: MergedOpenAPISchema;
|
||||||
|
}>;
|
||||||
|
|
||||||
for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
|
for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
|
||||||
if (
|
if (
|
||||||
|
@ -251,13 +275,13 @@ export class OpenAPIParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge rest of constraints
|
// merge rest of constraints
|
||||||
// TODO: do more intelegent merge
|
// TODO: do more intelligent merge
|
||||||
receiver = { ...subSchema, ...receiver };
|
receiver = { ...subSchema, ...receiver };
|
||||||
|
|
||||||
if (subSchemaRef) {
|
if (subSchemaRef) {
|
||||||
receiver.parentRefs!.push(subSchemaRef);
|
receiver.parentRefs!.push(subSchemaRef);
|
||||||
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
||||||
// this is not so correct behaviour. comented out for now
|
// this is not so correct behaviour. commented out for now
|
||||||
// ref: https://github.com/Redocly/redoc/issues/601
|
// ref: https://github.com/Redocly/redoc/issues/601
|
||||||
// receiver.title = JsonPointer.baseName(subSchemaRef);
|
// receiver.title = JsonPointer.baseName(subSchemaRef);
|
||||||
}
|
}
|
||||||
|
@ -272,8 +296,8 @@ export class OpenAPIParser {
|
||||||
* returns map of definition pointer to definition name
|
* returns map of definition pointer to definition name
|
||||||
* @param $refs array of references to find derived from
|
* @param $refs array of references to find derived from
|
||||||
*/
|
*/
|
||||||
findDerived($refs: string[]): Dict<string> {
|
findDerived($refs: string[]): Dict<string[] | string> {
|
||||||
const res: Dict<string> = {};
|
const res: Dict<string[]> = {};
|
||||||
const schemas = (this.spec.components && this.spec.components.schemas) || {};
|
const schemas = (this.spec.components && this.spec.components.schemas) || {};
|
||||||
for (const defName in schemas) {
|
for (const defName in schemas) {
|
||||||
const def = this.deref(schemas[defName]);
|
const def = this.deref(schemas[defName]);
|
||||||
|
@ -281,7 +305,7 @@ export class OpenAPIParser {
|
||||||
def.allOf !== undefined &&
|
def.allOf !== undefined &&
|
||||||
def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
|
def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
|
||||||
) {
|
) {
|
||||||
res['#/components/schemas/' + defName] = def['x-discriminator-value'] || defName;
|
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -21,22 +21,30 @@ export interface RedocRawOptions {
|
||||||
disableSearch?: boolean | string;
|
disableSearch?: boolean | string;
|
||||||
onlyRequiredInSamples?: boolean | string;
|
onlyRequiredInSamples?: boolean | string;
|
||||||
showExtensions?: boolean | string | string[];
|
showExtensions?: boolean | string | string[];
|
||||||
showOtherInfoPanel?: boolean;
|
|
||||||
hideSingleRequestSampleTab?: boolean | string;
|
hideSingleRequestSampleTab?: boolean | string;
|
||||||
|
menuToggle?: boolean | string;
|
||||||
|
jsonSampleExpandLevel?: number | string | 'all';
|
||||||
|
hideSchemaTitles?: boolean | string;
|
||||||
|
payloadSampleIdx?: number;
|
||||||
|
expandSingleSchemaField?: boolean | string;
|
||||||
|
|
||||||
unstable_ignoreMimeParameters?: boolean;
|
unstable_ignoreMimeParameters?: boolean;
|
||||||
|
|
||||||
allowedMdComponents?: Dict<MDXComponentMeta>;
|
allowedMdComponents?: Dict<MDXComponentMeta>;
|
||||||
|
|
||||||
labels?: LabelsConfigRaw;
|
labels?: LabelsConfigRaw;
|
||||||
|
|
||||||
|
enumSkipQuotes?: boolean | string;
|
||||||
|
|
||||||
|
expandDefaultServerVariables?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function argValueToBoolean(val?: string | boolean): boolean {
|
function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
|
||||||
if (val === undefined) {
|
if (val === undefined) {
|
||||||
return false;
|
return defaultValue || false;
|
||||||
}
|
}
|
||||||
if (typeof val === 'string') {
|
if (typeof val === 'string') {
|
||||||
return true;
|
return val === 'false' ? false : true;
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
@ -111,6 +119,28 @@ export class RedocNormalizedOptions {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static normalizePayloadSampleIdx(value: RedocRawOptions['payloadSampleIdx']): number {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
return Math.max(0, value); // always greater or equal than 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return isFinite(value) ? parseInt(value, 10) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static normalizeJsonSampleExpandLevel(level?: number | string | 'all'): number {
|
||||||
|
if (level === 'all') {
|
||||||
|
return +Infinity;
|
||||||
|
}
|
||||||
|
if (!isNaN(Number(level))) {
|
||||||
|
return Math.ceil(Number(level));
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
theme: ResolvedThemeInterface;
|
theme: ResolvedThemeInterface;
|
||||||
scrollYOffset: () => number;
|
scrollYOffset: () => number;
|
||||||
hideHostname: boolean;
|
hideHostname: boolean;
|
||||||
|
@ -126,12 +156,19 @@ export class RedocNormalizedOptions {
|
||||||
onlyRequiredInSamples: boolean;
|
onlyRequiredInSamples: boolean;
|
||||||
showExtensions: boolean | string[];
|
showExtensions: boolean | string[];
|
||||||
hideSingleRequestSampleTab: boolean;
|
hideSingleRequestSampleTab: boolean;
|
||||||
showOtherInfoPanel: boolean;
|
menuToggle: boolean;
|
||||||
|
jsonSampleExpandLevel: number;
|
||||||
|
enumSkipQuotes: boolean;
|
||||||
|
hideSchemaTitles: boolean;
|
||||||
|
payloadSampleIdx: number;
|
||||||
|
expandSingleSchemaField: boolean;
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
unstable_ignoreMimeParameters: boolean;
|
unstable_ignoreMimeParameters: boolean;
|
||||||
allowedMdComponents: Dict<MDXComponentMeta>;
|
allowedMdComponents: Dict<MDXComponentMeta>;
|
||||||
|
|
||||||
|
expandDefaultServerVariables: boolean;
|
||||||
|
|
||||||
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
||||||
raw = { ...defaults, ...raw };
|
raw = { ...defaults, ...raw };
|
||||||
const hook = raw.theme && raw.theme.extensionsHook;
|
const hook = raw.theme && raw.theme.extensionsHook;
|
||||||
|
@ -157,11 +194,21 @@ export class RedocNormalizedOptions {
|
||||||
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
||||||
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
||||||
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
||||||
this.showOtherInfoPanel = argValueToBoolean(raw.showOtherInfoPanel);
|
|
||||||
this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab);
|
this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab);
|
||||||
|
this.menuToggle = argValueToBoolean(raw.menuToggle, true);
|
||||||
|
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
||||||
|
raw.jsonSampleExpandLevel,
|
||||||
|
);
|
||||||
|
this.enumSkipQuotes = argValueToBoolean(raw.enumSkipQuotes);
|
||||||
|
this.hideSchemaTitles = argValueToBoolean(raw.hideSchemaTitles);
|
||||||
|
this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx);
|
||||||
|
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
||||||
this.allowedMdComponents = raw.allowedMdComponents || {};
|
this.allowedMdComponents = raw.allowedMdComponents || {};
|
||||||
|
|
||||||
|
this.expandDefaultServerVariables = argValueToBoolean(raw.expandDefaultServerVariables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const EVENT = 'scroll';
|
||||||
export class ScrollService {
|
export class ScrollService {
|
||||||
private _scrollParent: Window | HTMLElement | undefined;
|
private _scrollParent: Window | HTMLElement | undefined;
|
||||||
private _emiter: EventEmitter;
|
private _emiter: EventEmitter;
|
||||||
private _prevOffsetY: number = 0;
|
private _prevOffsetY = 0;
|
||||||
constructor(private options: RedocNormalizedOptions) {
|
constructor(private options: RedocNormalizedOptions) {
|
||||||
this._scrollParent = IS_BROWSER ? window : undefined;
|
this._scrollParent = IS_BROWSER ? window : undefined;
|
||||||
this._emiter = new EventEmitter();
|
this._emiter = new EventEmitter();
|
||||||
|
|
|
@ -4,21 +4,23 @@ import { OperationModel } from './models';
|
||||||
|
|
||||||
import Worker from './SearchWorker.worker';
|
import Worker from './SearchWorker.worker';
|
||||||
|
|
||||||
let worker: new () => Worker;
|
function getWorker() {
|
||||||
|
let worker: new () => Worker;
|
||||||
if (IS_BROWSER) {
|
if (IS_BROWSER) {
|
||||||
try {
|
try {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
worker = require('workerize-loader?inline&fallback=false!./SearchWorker.worker');
|
worker = require('workerize-loader?inline&fallback=false!./SearchWorker.worker');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
worker = require('./SearchWorker.worker').default;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
worker = require('./SearchWorker.worker').default;
|
worker = require('./SearchWorker.worker').default;
|
||||||
}
|
}
|
||||||
} else {
|
return new worker();
|
||||||
worker = require('./SearchWorker.worker').default;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SearchStore<T> {
|
export class SearchStore<T> {
|
||||||
searchWorker = new worker();
|
searchWorker = getWorker();
|
||||||
|
|
||||||
indexItems(groups: Array<IMenuItem | OperationModel>) {
|
indexItems(groups: Array<IMenuItem | OperationModel>) {
|
||||||
const recurse = items => {
|
const recurse = items => {
|
||||||
|
@ -38,6 +40,10 @@ export class SearchStore<T> {
|
||||||
this.searchWorker.add(title, body, meta);
|
this.searchWorker.add(title, body, meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
(this.searchWorker as any).terminate();
|
||||||
|
}
|
||||||
|
|
||||||
search(q: string) {
|
search(q: string) {
|
||||||
return this.searchWorker.search<T>(q);
|
return this.searchWorker.search<T>(q);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as lunr from 'lunr';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
require('core-js/es6/promise'); // bundle into worker
|
require('core-js/es/promise'); // bundle into worker
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// nope
|
// nope
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { SecuritySchemesModel } from './models/SecuritySchemes';
|
||||||
import { OpenAPIParser } from './OpenAPIParser';
|
import { OpenAPIParser } from './OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
/**
|
/**
|
||||||
* Store that containts all the specification related information in the form of tree
|
* Store that contains all the specification related information in the form of tree
|
||||||
*/
|
*/
|
||||||
export class SpecStore {
|
export class SpecStore {
|
||||||
parser: OpenAPIParser;
|
parser: OpenAPIParser;
|
||||||
|
|
|
@ -8,6 +8,7 @@ describe('Models', () => {
|
||||||
let parser;
|
let parser;
|
||||||
|
|
||||||
test('should hoist oneOfs when mergin allOf', () => {
|
test('should hoist oneOfs when mergin allOf', () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const spec = require('./fixtures/oneOfHoist.json');
|
const spec = require('./fixtures/oneOfHoist.json');
|
||||||
parser = new OpenAPIParser(spec, undefined, opts);
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
expect(parser.mergeAllOf(spec.components.schemas.test)).toMatchSnapshot();
|
expect(parser.mergeAllOf(spec.components.schemas.test)).toMatchSnapshot();
|
||||||
|
|
|
@ -10,6 +10,13 @@
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"name": "test_name",
|
"name": "test_name",
|
||||||
"schema": { "type": "string" }
|
"schema": { "type": "string" }
|
||||||
|
},
|
||||||
|
"serializationParam": {
|
||||||
|
"in": "query",
|
||||||
|
"name": "serialization_test_name",
|
||||||
|
"schema": { "type": "array" },
|
||||||
|
"style": "form",
|
||||||
|
"explode": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"headers": {
|
"headers": {
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe('History service', () => {
|
||||||
expect(fn).toHaveBeenCalled();
|
expect(fn).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('History subscribe should return unsubsribe function', () => {
|
test('History subscribe should return unsubscribe function', () => {
|
||||||
const fn = jest.fn();
|
const fn = jest.fn();
|
||||||
const unsubscribe = history.subscribe(fn);
|
const unsubscribe = history.subscribe(fn);
|
||||||
history.emit();
|
history.emit();
|
||||||
|
|
|
@ -6,9 +6,9 @@ const opts = new RedocNormalizedOptions({});
|
||||||
|
|
||||||
describe('Models', () => {
|
describe('Models', () => {
|
||||||
describe('FieldModel', () => {
|
describe('FieldModel', () => {
|
||||||
let parser;
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const spec = require('../fixtures/fields.json');
|
const spec = require('../fixtures/fields.json');
|
||||||
parser = new OpenAPIParser(spec, undefined, opts);
|
const parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
|
|
||||||
test('basic field details', () => {
|
test('basic field details', () => {
|
||||||
const field = new FieldModel(
|
const field = new FieldModel(
|
||||||
|
@ -26,6 +26,23 @@ describe('Models', () => {
|
||||||
expect(field.schema.type).toEqual('string');
|
expect(field.schema.type).toEqual('string');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('field details relevant for parameter serialization', () => {
|
||||||
|
const field = new FieldModel(
|
||||||
|
parser,
|
||||||
|
{
|
||||||
|
$ref: '#/components/parameters/serializationParam',
|
||||||
|
},
|
||||||
|
'#/components/parameters/serializationParam',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(field.name).toEqual('serialization_test_name');
|
||||||
|
expect(field.in).toEqual('query');
|
||||||
|
expect(field.schema.type).toEqual('array');
|
||||||
|
expect(field.style).toEqual('form');
|
||||||
|
expect(field.explode).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
test('field name should populated from name even if $ref (headers)', () => {
|
test('field name should populated from name even if $ref (headers)', () => {
|
||||||
const field = new FieldModel(
|
const field = new FieldModel(
|
||||||
parser,
|
parser,
|
||||||
|
|
|
@ -22,7 +22,7 @@ describe('Models', () => {
|
||||||
expect(resp.type).toEqual('error');
|
expect(resp.type).toEqual('error');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('default should be sucessful by default', () => {
|
test('default should be successful by default', () => {
|
||||||
const resp = new ResponseModel(parser, 'default', false, {}, opts);
|
const resp = new ResponseModel(parser, 'default', false, {}, opts);
|
||||||
expect(resp.type).toEqual('success');
|
expect(resp.type).toEqual('success');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
import { SchemaModel } from '../../models/Schema';
|
import { SchemaModel } from '../../models/Schema';
|
||||||
import { OpenAPIParser } from '../../OpenAPIParser';
|
import { OpenAPIParser } from '../../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
|
||||||
|
|
|
@ -17,6 +17,7 @@ export class ApiInfoModel implements OpenAPIInfo {
|
||||||
constructor(private parser: OpenAPIParser) {
|
constructor(private parser: OpenAPIParser) {
|
||||||
Object.assign(this, parser.spec.info);
|
Object.assign(this, parser.spec.info);
|
||||||
this.description = parser.spec.info.description || '';
|
this.description = parser.spec.info.description || '';
|
||||||
|
|
||||||
const firstHeadingLinePos = this.description.search(/^##?\s+/m);
|
const firstHeadingLinePos = this.description.search(/^##?\s+/m);
|
||||||
if (firstHeadingLinePos > -1) {
|
if (firstHeadingLinePos > -1) {
|
||||||
this.description = this.description.substring(0, firstHeadingLinePos);
|
this.description = this.description.substring(0, firstHeadingLinePos);
|
||||||
|
|
|
@ -1,18 +1,36 @@
|
||||||
import { action, observable } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIParameter, Referenced } from '../../types';
|
import {
|
||||||
|
OpenAPIParameter,
|
||||||
|
OpenAPIParameterLocation,
|
||||||
|
OpenAPIParameterStyle,
|
||||||
|
Referenced,
|
||||||
|
} from '../../types';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
import { extractExtensions } from '../../utils/openapi';
|
import { extractExtensions } from '../../utils/openapi';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { SchemaModel } from './Schema';
|
import { SchemaModel } from './Schema';
|
||||||
|
|
||||||
|
function getDefaultStyleValue(parameterLocation: OpenAPIParameterLocation): OpenAPIParameterStyle {
|
||||||
|
switch (parameterLocation) {
|
||||||
|
case 'header':
|
||||||
|
return 'simple';
|
||||||
|
case 'query':
|
||||||
|
return 'form';
|
||||||
|
case 'path':
|
||||||
|
return 'simple';
|
||||||
|
default:
|
||||||
|
return 'form';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field or Parameter model ready to be used by components
|
* Field or Parameter model ready to be used by components
|
||||||
*/
|
*/
|
||||||
export class FieldModel {
|
export class FieldModel {
|
||||||
@observable
|
@observable
|
||||||
expanded: boolean = false;
|
expanded: boolean | undefined;
|
||||||
|
|
||||||
schema: SchemaModel;
|
schema: SchemaModel;
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -20,9 +38,13 @@ export class FieldModel {
|
||||||
description: string;
|
description: string;
|
||||||
example?: string;
|
example?: string;
|
||||||
deprecated: boolean;
|
deprecated: boolean;
|
||||||
in?: string;
|
in?: OpenAPIParameterLocation;
|
||||||
kind: string;
|
kind: string;
|
||||||
extensions?: Dict<any>;
|
extensions?: Dict<any>;
|
||||||
|
explode: boolean;
|
||||||
|
style?: OpenAPIParameterStyle;
|
||||||
|
|
||||||
|
serializationMime?: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
|
@ -35,11 +57,29 @@ export class FieldModel {
|
||||||
this.name = infoOrRef.name || info.name;
|
this.name = infoOrRef.name || info.name;
|
||||||
this.in = info.in;
|
this.in = info.in;
|
||||||
this.required = !!info.required;
|
this.required = !!info.required;
|
||||||
this.schema = new SchemaModel(parser, info.schema || {}, pointer, options);
|
|
||||||
|
let fieldSchema = info.schema;
|
||||||
|
let serializationMime = '';
|
||||||
|
if (!fieldSchema && info.in && info.content) {
|
||||||
|
serializationMime = Object.keys(info.content)[0];
|
||||||
|
fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options);
|
||||||
this.description =
|
this.description =
|
||||||
info.description === undefined ? this.schema.description || '' : info.description;
|
info.description === undefined ? this.schema.description || '' : info.description;
|
||||||
this.example = info.example || this.schema.example;
|
this.example = info.example || this.schema.example;
|
||||||
|
|
||||||
|
if (serializationMime) {
|
||||||
|
this.serializationMime = serializationMime;
|
||||||
|
} else if (info.style) {
|
||||||
|
this.style = info.style;
|
||||||
|
} else if (this.in) {
|
||||||
|
this.style = getDefaultStyleValue(this.in);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.explode = !!info.explode;
|
||||||
|
|
||||||
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
|
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
|
||||||
parser.exitRef(infoOrRef);
|
parser.exitRef(infoOrRef);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { action, observable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIExternalDocumentation, OpenAPITag } from '../../types';
|
import { OpenAPIExternalDocumentation, OpenAPITag } from '../../types';
|
||||||
import { safeSlugify } from '../../utils';
|
import { safeSlugify } from '../../utils';
|
||||||
import { MarkdownHeading } from '../MarkdownRenderer';
|
import { MarkdownHeading, MarkdownRenderer } from '../MarkdownRenderer';
|
||||||
import { ContentItemModel } from '../MenuBuilder';
|
import { ContentItemModel } from '../MenuBuilder';
|
||||||
import { IMenuItem, MenuItemGroupType } from '../MenuStore';
|
import { IMenuItem, MenuItemGroupType } from '../MenuStore';
|
||||||
|
|
||||||
|
@ -40,7 +40,15 @@ export class GroupModel implements IMenuItem {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.name = tagOrGroup['x-displayName'] || tagOrGroup.name;
|
this.name = tagOrGroup['x-displayName'] || tagOrGroup.name;
|
||||||
this.level = (tagOrGroup as MarkdownHeading).level || 1;
|
this.level = (tagOrGroup as MarkdownHeading).level || 1;
|
||||||
|
|
||||||
|
// remove sections from markdown, same as in ApiInfo
|
||||||
this.description = tagOrGroup.description || '';
|
this.description = tagOrGroup.description || '';
|
||||||
|
|
||||||
|
const items = (tagOrGroup as MarkdownHeading).items;
|
||||||
|
if (items && items.length) {
|
||||||
|
this.description = MarkdownRenderer.getTextBeforeHading(this.description, items[0].name);
|
||||||
|
}
|
||||||
|
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.externalDocs = (tagOrGroup as OpenAPITag).externalDocs;
|
this.externalDocs = (tagOrGroup as OpenAPITag).externalDocs;
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ export class MediaContentModel {
|
||||||
* @param isRequestType needed to know if skipe RO/RW fields in objects
|
* @param isRequestType needed to know if skipe RO/RW fields in objects
|
||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
public parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
info: Dict<OpenAPIMediaType>,
|
info: Dict<OpenAPIMediaType>,
|
||||||
public isRequestType: boolean,
|
public isRequestType: boolean,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
|
|
|
@ -27,9 +27,23 @@ import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
import { FieldModel } from './Field';
|
import { FieldModel } from './Field';
|
||||||
|
import { MediaContentModel } from './MediaContent';
|
||||||
import { RequestBodyModel } from './RequestBody';
|
import { RequestBodyModel } from './RequestBody';
|
||||||
import { ResponseModel } from './Response';
|
import { ResponseModel } from './Response';
|
||||||
|
|
||||||
|
interface XPayloadSample {
|
||||||
|
lang: 'payload';
|
||||||
|
label: string;
|
||||||
|
requestBodyContent: MediaContentModel;
|
||||||
|
source: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPayloadSample(
|
||||||
|
sample: XPayloadSample | OpenAPIXCodeSample,
|
||||||
|
): sample is XPayloadSample {
|
||||||
|
return sample.lang === 'payload' && (sample as any).requestBodyContent;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operation model ready to be used by components
|
* Operation model ready to be used by components
|
||||||
*/
|
*/
|
||||||
|
@ -62,7 +76,6 @@ export class OperationModel implements IMenuItem {
|
||||||
path: string;
|
path: string;
|
||||||
servers: OpenAPIServer[];
|
servers: OpenAPIServer[];
|
||||||
security: SecurityRequirementModel[];
|
security: SecurityRequirementModel[];
|
||||||
codeSamples: OpenAPIXCodeSample[];
|
|
||||||
extensions: Dict<any>;
|
extensions: Dict<any>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -77,8 +90,8 @@ export class OperationModel implements IMenuItem {
|
||||||
operationSpec.operationId !== undefined
|
operationSpec.operationId !== undefined
|
||||||
? 'operation/' + operationSpec.operationId
|
? 'operation/' + operationSpec.operationId
|
||||||
: parent !== undefined
|
: parent !== undefined
|
||||||
? parent.id + this.pointer
|
? parent.id + this.pointer
|
||||||
: this.pointer;
|
: this.pointer;
|
||||||
|
|
||||||
this.name = getOperationSummary(operationSpec);
|
this.name = getOperationSummary(operationSpec);
|
||||||
this.description = operationSpec.description;
|
this.description = operationSpec.description;
|
||||||
|
@ -89,7 +102,6 @@ export class OperationModel implements IMenuItem {
|
||||||
this.httpVerb = operationSpec.httpVerb;
|
this.httpVerb = operationSpec.httpVerb;
|
||||||
this.deprecated = !!operationSpec.deprecated;
|
this.deprecated = !!operationSpec.deprecated;
|
||||||
this.operationId = operationSpec.operationId;
|
this.operationId = operationSpec.operationId;
|
||||||
this.codeSamples = operationSpec['x-code-samples'] || [];
|
|
||||||
this.path = operationSpec.pathName;
|
this.path = operationSpec.pathName;
|
||||||
|
|
||||||
const pathInfo = parser.byRef<OpenAPIPath>(
|
const pathInfo = parser.byRef<OpenAPIPath>(
|
||||||
|
@ -144,6 +156,30 @@ export class OperationModel implements IMenuItem {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@memoize
|
||||||
|
get codeSamples() {
|
||||||
|
let samples: Array<OpenAPIXCodeSample | XPayloadSample> =
|
||||||
|
this.operationSpec['x-code-samples'] || [];
|
||||||
|
|
||||||
|
const requestBodyContent = this.requestBody && this.requestBody.content;
|
||||||
|
if (requestBodyContent && requestBodyContent.hasSample) {
|
||||||
|
const insertInx = Math.min(samples.length, this.options.payloadSampleIdx);
|
||||||
|
|
||||||
|
samples = [
|
||||||
|
...samples.slice(0, insertInx),
|
||||||
|
{
|
||||||
|
lang: 'payload',
|
||||||
|
label: 'Payload',
|
||||||
|
source: '',
|
||||||
|
requestBodyContent,
|
||||||
|
},
|
||||||
|
...samples.slice(insertInx),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return samples;
|
||||||
|
}
|
||||||
|
|
||||||
@memoize
|
@memoize
|
||||||
get parameters() {
|
get parameters() {
|
||||||
const _parameters = mergeParams(
|
const _parameters = mergeParams(
|
||||||
|
@ -154,11 +190,12 @@ export class OperationModel implements IMenuItem {
|
||||||
).map(paramOrRef => new FieldModel(this.parser, paramOrRef, this.pointer, this.options));
|
).map(paramOrRef => new FieldModel(this.parser, paramOrRef, this.pointer, this.options));
|
||||||
|
|
||||||
if (this.options.sortPropsAlphabetically) {
|
if (this.options.sortPropsAlphabetically) {
|
||||||
sortByField(_parameters, 'name');
|
return sortByField(_parameters, 'name');
|
||||||
}
|
}
|
||||||
if (this.options.requiredPropsFirst) {
|
if (this.options.requiredPropsFirst) {
|
||||||
sortByRequired(_parameters);
|
return sortByRequired(_parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _parameters;
|
return _parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ export class SchemaModel {
|
||||||
this.pointer = schemaOrRef.$ref || pointer || '';
|
this.pointer = schemaOrRef.$ref || pointer || '';
|
||||||
this.rawSchema = parser.deref(schemaOrRef);
|
this.rawSchema = parser.deref(schemaOrRef);
|
||||||
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
|
this.schema = parser.mergeAllOf(this.rawSchema, this.pointer, isChild);
|
||||||
|
|
||||||
this.init(parser, isChild);
|
this.init(parser, isChild);
|
||||||
|
|
||||||
parser.exitRef(schemaOrRef);
|
parser.exitRef(schemaOrRef);
|
||||||
|
@ -125,6 +126,13 @@ export class SchemaModel {
|
||||||
if (!isChild && getDiscriminator(schema) !== undefined) {
|
if (!isChild && getDiscriminator(schema) !== undefined) {
|
||||||
this.initDiscriminator(schema, parser);
|
this.initDiscriminator(schema, parser);
|
||||||
return;
|
return;
|
||||||
|
} else if (
|
||||||
|
isChild &&
|
||||||
|
Array.isArray(schema.oneOf) &&
|
||||||
|
schema.oneOf.find(s => s.$ref === this.pointer)
|
||||||
|
) {
|
||||||
|
// we hit allOf of the schema with the parent discriminator
|
||||||
|
delete schema.oneOf;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.oneOf !== undefined) {
|
if (schema.oneOf !== undefined) {
|
||||||
|
@ -216,7 +224,10 @@ export class SchemaModel {
|
||||||
) {
|
) {
|
||||||
const discriminator = getDiscriminator(schema)!;
|
const discriminator = getDiscriminator(schema)!;
|
||||||
this.discriminatorProp = discriminator.propertyName;
|
this.discriminatorProp = discriminator.propertyName;
|
||||||
const derived = parser.findDerived([...(schema.parentRefs || []), this.pointer]);
|
const implicitInversedMapping = parser.findDerived([
|
||||||
|
...(schema.parentRefs || []),
|
||||||
|
this.pointer,
|
||||||
|
]);
|
||||||
|
|
||||||
if (schema.oneOf) {
|
if (schema.oneOf) {
|
||||||
for (const variant of schema.oneOf) {
|
for (const variant of schema.oneOf) {
|
||||||
|
@ -224,19 +235,41 @@ export class SchemaModel {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const name = JsonPointer.baseName(variant.$ref);
|
const name = JsonPointer.baseName(variant.$ref);
|
||||||
derived[variant.$ref] = name;
|
implicitInversedMapping[variant.$ref] = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapping = discriminator.mapping || {};
|
const mapping = discriminator.mapping || {};
|
||||||
|
const explicitInversedMapping = {};
|
||||||
for (const name in mapping) {
|
for (const name in mapping) {
|
||||||
derived[mapping[name]] = name;
|
const $ref = mapping[name];
|
||||||
|
|
||||||
|
if (Array.isArray(explicitInversedMapping[$ref])) {
|
||||||
|
explicitInversedMapping[$ref].push(name);
|
||||||
|
} else {
|
||||||
|
// overrides implicit mapping here
|
||||||
|
explicitInversedMapping[$ref] = [name];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const refs = Object.keys(derived);
|
const inversedMapping = { ...implicitInversedMapping, ...explicitInversedMapping };
|
||||||
this.oneOf = refs.map(ref => {
|
|
||||||
const innerSchema = new SchemaModel(parser, parser.byRef(ref)!, ref, this.options, true);
|
const refs: Array<{ $ref; name }> = [];
|
||||||
innerSchema.title = derived[ref];
|
|
||||||
|
for (const $ref of Object.keys(inversedMapping)) {
|
||||||
|
const names = inversedMapping[$ref];
|
||||||
|
if (Array.isArray(names)) {
|
||||||
|
for (const name of names) {
|
||||||
|
refs.push({ $ref, name });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
refs.push({ $ref, name: names });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.oneOf = refs.map(({ $ref, name }) => {
|
||||||
|
const innerSchema = new SchemaModel(parser, parser.byRef($ref)!, $ref, this.options, true);
|
||||||
|
innerSchema.title = name;
|
||||||
return innerSchema;
|
return innerSchema;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -251,7 +284,7 @@ function buildFields(
|
||||||
const props = schema.properties || {};
|
const props = schema.properties || {};
|
||||||
const additionalProps = schema.additionalProperties;
|
const additionalProps = schema.additionalProperties;
|
||||||
const defaults = schema.default || {};
|
const defaults = schema.default || {};
|
||||||
const fields = Object.keys(props || []).map(fieldName => {
|
let fields = Object.keys(props || []).map(fieldName => {
|
||||||
let field = props[fieldName];
|
let field = props[fieldName];
|
||||||
|
|
||||||
if (!field) {
|
if (!field) {
|
||||||
|
@ -280,11 +313,11 @@ function buildFields(
|
||||||
});
|
});
|
||||||
|
|
||||||
if (options.sortPropsAlphabetically) {
|
if (options.sortPropsAlphabetically) {
|
||||||
sortByField(fields, 'name');
|
fields = sortByField(fields, 'name');
|
||||||
}
|
}
|
||||||
if (options.requiredPropsFirst) {
|
if (options.requiredPropsFirst) {
|
||||||
// if not sort alphabetically sort in the order from required keyword
|
// if not sort alphabetically sort in the order from required keyword
|
||||||
sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
|
fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof additionalProps === 'object' || additionalProps === true) {
|
if (typeof additionalProps === 'object' || additionalProps === true) {
|
||||||
|
|
61
src/theme.ts
61
src/theme.ts
|
@ -3,8 +3,8 @@ import { darken, desaturate, lighten, readableColor, transparentize } from 'poli
|
||||||
const defaultTheme: ThemeInterface = {
|
const defaultTheme: ThemeInterface = {
|
||||||
spacing: {
|
spacing: {
|
||||||
unit: 5,
|
unit: 5,
|
||||||
sectionHorizontal: ({spacing}) => spacing.unit * 8,
|
sectionHorizontal: ({ spacing }) => spacing.unit * 8,
|
||||||
sectionVertical: ({spacing}) => spacing.unit * 8,
|
sectionVertical: ({ spacing }) => spacing.unit * 8,
|
||||||
},
|
},
|
||||||
breakpoints: {
|
breakpoints: {
|
||||||
small: '50rem',
|
small: '50rem',
|
||||||
|
@ -15,31 +15,31 @@ const defaultTheme: ThemeInterface = {
|
||||||
tonalOffset: 0.3,
|
tonalOffset: 0.3,
|
||||||
primary: {
|
primary: {
|
||||||
main: '#232E72',
|
main: '#232E72',
|
||||||
light: ({colors}) => lighten(colors.tonalOffset, colors.primary.main),
|
light: ({ colors }) => lighten(colors.tonalOffset, colors.primary.main),
|
||||||
dark: ({colors}) => darken(colors.tonalOffset, colors.primary.main),
|
dark: ({ colors }) => darken(colors.tonalOffset, colors.primary.main),
|
||||||
contrastText: ({colors}) => readableColor(colors.primary.main),
|
contrastText: ({ colors }) => readableColor(colors.primary.main),
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
main: '#00aa13',
|
main: '#00aa13',
|
||||||
light: ({colors}) => lighten(colors.tonalOffset, colors.success.main),
|
light: ({ colors }) => lighten(colors.tonalOffset, colors.success.main),
|
||||||
dark: ({colors}) => darken(colors.tonalOffset, colors.success.main),
|
dark: ({ colors }) => darken(colors.tonalOffset, colors.success.main),
|
||||||
contrastText: ({colors}) => readableColor(colors.success.main),
|
contrastText: ({ colors }) => readableColor(colors.success.main),
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
main: '#d4ad03',
|
main: '#d4ad03',
|
||||||
light: ({colors}) => lighten(colors.tonalOffset, colors.warning.main),
|
light: ({ colors }) => lighten(colors.tonalOffset, colors.warning.main),
|
||||||
dark: ({colors}) => darken(colors.tonalOffset, colors.warning.main),
|
dark: ({ colors }) => darken(colors.tonalOffset, colors.warning.main),
|
||||||
contrastText: '#ffffff',
|
contrastText: '#ffffff',
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
main: '#e53935',
|
main: '#e53935',
|
||||||
light: ({colors}) => lighten(colors.tonalOffset, colors.error.main),
|
light: ({ colors }) => lighten(colors.tonalOffset, colors.error.main),
|
||||||
dark: ({colors}) => darken(colors.tonalOffset, colors.error.main),
|
dark: ({ colors }) => darken(colors.tonalOffset, colors.error.main),
|
||||||
contrastText: ({colors}) => readableColor(colors.error.main),
|
contrastText: ({ colors }) => readableColor(colors.error.main),
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
primary: '#333333',
|
primary: '#333333',
|
||||||
secondary: ({colors}) => lighten(colors.tonalOffset, colors.text.primary),
|
secondary: ({ colors }) => lighten(colors.tonalOffset, colors.text.primary),
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
dark: 'rgba(0,0,0, 0.1)',
|
dark: 'rgba(0,0,0, 0.1)',
|
||||||
|
@ -47,20 +47,20 @@ const defaultTheme: ThemeInterface = {
|
||||||
},
|
},
|
||||||
responses: {
|
responses: {
|
||||||
success: {
|
success: {
|
||||||
color: ({colors}) => colors.success.main,
|
color: ({ colors }) => colors.success.main,
|
||||||
backgroundColor: ({colors}) => transparentize(0.9, colors.success.main),
|
backgroundColor: ({ colors }) => transparentize(0.9, colors.success.main),
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
color: ({colors}) => colors.error.main,
|
color: ({ colors }) => colors.error.main,
|
||||||
backgroundColor: ({colors}) => transparentize(0.9, colors.error.main),
|
backgroundColor: ({ colors }) => transparentize(0.9, colors.error.main),
|
||||||
},
|
},
|
||||||
redirect: {
|
redirect: {
|
||||||
color: '#ffa500',
|
color: '#ffa500',
|
||||||
backgroundColor: ({colors}) => transparentize(0.9, colors.responses.redirect.color),
|
backgroundColor: ({ colors }) => transparentize(0.9, colors.responses.redirect.color),
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
color: '#87ceeb',
|
color: '#87ceeb',
|
||||||
backgroundColor: ({colors}) => transparentize(0.9, colors.responses.info.color),
|
backgroundColor: ({ colors }) => transparentize(0.9, colors.responses.info.color),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -110,22 +110,26 @@ const defaultTheme: ThemeInterface = {
|
||||||
code: {
|
code: {
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
fontFamily: 'Courier, monospace',
|
fontFamily: 'Courier, monospace',
|
||||||
lineHeight: ({typography}) => typography.lineHeight,
|
lineHeight: ({ typography }) => typography.lineHeight,
|
||||||
fontWeight: ({typography}) => typography.fontWeightRegular,
|
fontWeight: ({ typography }) => typography.fontWeightRegular,
|
||||||
color: '#e53935',
|
color: '#e53935',
|
||||||
backgroundColor: 'rgba(38, 50, 56, 0.05)',
|
backgroundColor: 'rgba(38, 50, 56, 0.05)',
|
||||||
wrap: false,
|
wrap: false,
|
||||||
},
|
},
|
||||||
links: {
|
links: {
|
||||||
color: ({colors}) => colors.primary.main,
|
color: ({ colors }) => colors.primary.main,
|
||||||
visited: ({typography}) => typography.links.color,
|
visited: ({ typography }) => typography.links.color,
|
||||||
hover: ({typography}) => lighten(0.2, typography.links.color),
|
hover: ({ typography }) => lighten(0.2, typography.links.color),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
width: '260px',
|
width: '260px',
|
||||||
backgroundColor: '#F3F6FB',
|
backgroundColor: '#F3F6FB',
|
||||||
textColor: '#232E72',
|
textColor: '#232E72',
|
||||||
|
activeTextColor: theme =>
|
||||||
|
theme.menu.textColor !== defaultTheme.menu!.textColor
|
||||||
|
? theme.menu.textColor
|
||||||
|
: theme.colors.primary.main,
|
||||||
groupItems: {
|
groupItems: {
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
},
|
},
|
||||||
|
@ -138,8 +142,8 @@ const defaultTheme: ThemeInterface = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
maxHeight: ({menu}) => menu.width,
|
maxHeight: ({ menu }) => menu.width,
|
||||||
maxWidth: ({menu}) => menu.width,
|
maxWidth: ({ menu }) => menu.width,
|
||||||
gutter: '2px',
|
gutter: '2px',
|
||||||
},
|
},
|
||||||
rightPanel: {
|
rightPanel: {
|
||||||
|
@ -148,7 +152,7 @@ const defaultTheme: ThemeInterface = {
|
||||||
textColor: '#ffffff',
|
textColor: '#ffffff',
|
||||||
},
|
},
|
||||||
codeSample: {
|
codeSample: {
|
||||||
backgroundColor: ({rightPanel}) => darken(0.1, rightPanel.backgroundColor),
|
backgroundColor: ({ rightPanel }) => darken(0.1, rightPanel.backgroundColor),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -308,6 +312,7 @@ export interface ResolvedThemeInterface {
|
||||||
width: string;
|
width: string;
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
textColor: string;
|
textColor: string;
|
||||||
|
activeTextColor: string;
|
||||||
groupItems: {
|
groupItems: {
|
||||||
textTransform: string;
|
textTransform: string;
|
||||||
};
|
};
|
||||||
|
|
5
src/types/components.d.ts
vendored
5
src/types/components.d.ts
vendored
|
@ -1,5 +0,0 @@
|
||||||
import { AppStore } from '../services/AppStore';
|
|
||||||
|
|
||||||
export interface BaseContainerProps {
|
|
||||||
store: AppStore;
|
|
||||||
}
|
|
|
@ -8,7 +8,11 @@ describe('Utils', () => {
|
||||||
const fn = (...args) => args;
|
const fn = (...args) => args;
|
||||||
|
|
||||||
const actual = mapWithLast(arr, fn);
|
const actual = mapWithLast(arr, fn);
|
||||||
const expected = [[1, false], [2, false], [3, true]];
|
const expected = [
|
||||||
|
[1, false],
|
||||||
|
[2, false],
|
||||||
|
[3, true],
|
||||||
|
];
|
||||||
expect(actual).toEqual(expected);
|
expect(actual).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -60,7 +64,7 @@ describe('Utils', () => {
|
||||||
test('should behave like Object.assign on the top level', () => {
|
test('should behave like Object.assign on the top level', () => {
|
||||||
const obj1 = { a: { a1: 'A1' }, c: 'C' };
|
const obj1 = { a: { a1: 'A1' }, c: 'C' };
|
||||||
const obj2 = { a: undefined, b: { b1: 'B1' } };
|
const obj2 = { a: undefined, b: { b1: 'B1' } };
|
||||||
expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
|
expect(mergeObjects({}, obj1, obj2)).toEqual({ ...obj1, ...obj2 });
|
||||||
});
|
});
|
||||||
test('should not merge array values, just override', () => {
|
test('should not merge array values, just override', () => {
|
||||||
const obj1 = { a: ['A', 'B'] };
|
const obj1 = { a: ['A', 'B'] };
|
||||||
|
|
|
@ -8,10 +8,13 @@ import {
|
||||||
mergeParams,
|
mergeParams,
|
||||||
normalizeServers,
|
normalizeServers,
|
||||||
pluralizeType,
|
pluralizeType,
|
||||||
|
serializeParameterValue,
|
||||||
|
sortByRequired,
|
||||||
} from '../';
|
} from '../';
|
||||||
|
|
||||||
import { OpenAPIParser } from '../../services';
|
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||||
import { OpenAPIParameter } from '../../types';
|
import { OpenAPIParameter, OpenAPIParameterLocation, OpenAPIParameterStyle } from '../../types';
|
||||||
|
import { expandDefaultServerVariables } from '../openapi';
|
||||||
|
|
||||||
describe('Utils', () => {
|
describe('Utils', () => {
|
||||||
describe('openapi getStatusCode', () => {
|
describe('openapi getStatusCode', () => {
|
||||||
|
@ -247,7 +250,7 @@ describe('Utils', () => {
|
||||||
expect(res).toEqual([{ url: 'http://base.com/sandbox/test', description: '' }]);
|
expect(res).toEqual([{ url: 'http://base.com/sandbox/test', description: '' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correcly resolve url with server relative path', () => {
|
it('should correctly resolve url with server relative path', () => {
|
||||||
const res = normalizeServers('http://base.com/subpath/spec.yaml', [
|
const res = normalizeServers('http://base.com/subpath/spec.yaml', [
|
||||||
{
|
{
|
||||||
url: '/sandbox/test',
|
url: '/sandbox/test',
|
||||||
|
@ -256,7 +259,7 @@ describe('Utils', () => {
|
||||||
expect(res).toEqual([{ url: 'http://base.com/sandbox/test', description: '' }]);
|
expect(res).toEqual([{ url: 'http://base.com/sandbox/test', description: '' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should correcly resolve url with relative path', () => {
|
it('should correctly resolve url with relative path', () => {
|
||||||
const res = normalizeServers('http://base.com/subpath/spec.yaml', [
|
const res = normalizeServers('http://base.com/subpath/spec.yaml', [
|
||||||
{
|
{
|
||||||
url: 'sandbox/test',
|
url: 'sandbox/test',
|
||||||
|
@ -296,11 +299,8 @@ describe('Utils', () => {
|
||||||
it('should expand variables', () => {
|
it('should expand variables', () => {
|
||||||
const servers = normalizeServers('', [
|
const servers = normalizeServers('', [
|
||||||
{
|
{
|
||||||
url: '{protocol}{host}{basePath}',
|
url: 'http://{host}{basePath}',
|
||||||
variables: {
|
variables: {
|
||||||
protocol: {
|
|
||||||
default: 'http://',
|
|
||||||
},
|
|
||||||
host: {
|
host: {
|
||||||
default: '127.0.0.1',
|
default: '127.0.0.1',
|
||||||
},
|
},
|
||||||
|
@ -318,9 +318,15 @@ describe('Utils', () => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(servers[0].url).toEqual('http://127.0.0.1/path/to/endpoint');
|
expect(expandDefaultServerVariables(servers[0].url, servers[0].variables)).toEqual(
|
||||||
expect(servers[1].url).toEqual('http://127.0.0.2:{port}');
|
'http://127.0.0.1/path/to/endpoint',
|
||||||
expect(servers[2].url).toEqual('http://127.0.0.3');
|
);
|
||||||
|
expect(expandDefaultServerVariables(servers[1].url, servers[1].variables)).toEqual(
|
||||||
|
'http://127.0.0.2:{port}',
|
||||||
|
);
|
||||||
|
expect(expandDefaultServerVariables(servers[2].url, servers[2].variables)).toEqual(
|
||||||
|
'http://127.0.0.3',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -328,7 +334,8 @@ describe('Utils', () => {
|
||||||
const itemConstraintSchema = (
|
const itemConstraintSchema = (
|
||||||
min: number | undefined = undefined,
|
min: number | undefined = undefined,
|
||||||
max: number | undefined = undefined,
|
max: number | undefined = undefined,
|
||||||
) => ({ type: 'array', minItems: min, maxItems: max });
|
multipleOf: number | undefined = undefined,
|
||||||
|
) => ({ type: 'array', minItems: min, maxItems: max, multipleOf });
|
||||||
|
|
||||||
it('should not have a humanized constraint without schema constraints', () => {
|
it('should not have a humanized constraint without schema constraints', () => {
|
||||||
expect(humanizeConstraints(itemConstraintSchema())).toHaveLength(0);
|
expect(humanizeConstraints(itemConstraintSchema())).toHaveLength(0);
|
||||||
|
@ -350,9 +357,21 @@ describe('Utils', () => {
|
||||||
expect(humanizeConstraints(itemConstraintSchema(7, 7))).toContain('7 items');
|
expect(humanizeConstraints(itemConstraintSchema(7, 7))).toContain('7 items');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should have a humazined constraint when justMinItems is set, and it is equal to 1', () => {
|
it('should have a humanized constraint when justMinItems is set, and it is equal to 1', () => {
|
||||||
expect(humanizeConstraints(itemConstraintSchema(1))).toContain('non-empty');
|
expect(humanizeConstraints(itemConstraintSchema(1))).toContain('non-empty');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should have a humanized constraint when multipleOf is set, and it is in format of /^0\\.0*1$/', () => {
|
||||||
|
expect(humanizeConstraints(itemConstraintSchema(undefined, undefined, 0.01))).toContain(
|
||||||
|
'decimal places <= 2',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a humanized constraint when multipleOf is set, and it is in format other than /^0\\.0*1$/', () => {
|
||||||
|
expect(humanizeConstraints(itemConstraintSchema(undefined, undefined, 0.5))).toContain(
|
||||||
|
'multiple of 0.5',
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('OpenAPI pluralizeType', () => {
|
describe('OpenAPI pluralizeType', () => {
|
||||||
|
@ -365,16 +384,623 @@ describe('Utils', () => {
|
||||||
expect(pluralizeType('array')).toEqual('arrays');
|
expect(pluralizeType('array')).toEqual('arrays');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pluralize complex dislay types', () => {
|
it('should pluralize complex display types', () => {
|
||||||
expect(pluralizeType('object (Pet)')).toEqual('objects (Pet)');
|
expect(pluralizeType('object (Pet)')).toEqual('objects (Pet)');
|
||||||
expect(pluralizeType('string <email>')).toEqual('strings <email>');
|
expect(pluralizeType('string <email>')).toEqual('strings <email>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pluralize oneOf-ed dislay types', () => {
|
it('should pluralize oneOf-ed display types', () => {
|
||||||
expect(pluralizeType('object or string')).toEqual('objects or strings');
|
expect(pluralizeType('object or string')).toEqual('objects or strings');
|
||||||
expect(pluralizeType('object (Pet) or number <int64>')).toEqual(
|
expect(pluralizeType('object (Pet) or number <int64>')).toEqual(
|
||||||
'objects (Pet) or numbers <int64>',
|
'objects (Pet) or numbers <int64>',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not pluralize display types that are already pluralized', () => {
|
||||||
|
expect(pluralizeType('strings')).toEqual('strings');
|
||||||
|
expect(pluralizeType('objects (Pet)')).toEqual('objects (Pet)');
|
||||||
|
expect(pluralizeType('strings <email>')).toEqual('strings <email>');
|
||||||
|
expect(pluralizeType('objects or strings')).toEqual('objects or strings');
|
||||||
|
expect(pluralizeType('objects (Pet) or numbers <int64>')).toEqual(
|
||||||
|
'objects (Pet) or numbers <int64>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('openapi serializeParameter', () => {
|
||||||
|
interface TestCase {
|
||||||
|
style: OpenAPIParameterStyle;
|
||||||
|
explode: boolean;
|
||||||
|
expected: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestValueTypeGroup {
|
||||||
|
value: any;
|
||||||
|
description: string;
|
||||||
|
cases: TestCase[];
|
||||||
|
}
|
||||||
|
interface TestLocationGroup {
|
||||||
|
location: OpenAPIParameterLocation;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
cases: TestValueTypeGroup[];
|
||||||
|
}
|
||||||
|
const testCases: TestLocationGroup[] = [
|
||||||
|
{
|
||||||
|
location: 'path',
|
||||||
|
name: 'id',
|
||||||
|
description: 'path parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '5' },
|
||||||
|
{ style: 'label', explode: false, expected: '.5' },
|
||||||
|
{ style: 'label', explode: true, expected: '.5' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=5' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '3,4,5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '3,4,5' },
|
||||||
|
{ style: 'label', explode: false, expected: '.3,4,5' },
|
||||||
|
{ style: 'label', explode: true, expected: '.3.4.5' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=3,4,5' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';id=3;id=4;id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: 'role,admin,firstName,Alex' },
|
||||||
|
{ style: 'simple', explode: true, expected: 'role=admin,firstName=Alex' },
|
||||||
|
{ style: 'label', explode: false, expected: '.role,admin,firstName,Alex' },
|
||||||
|
{ style: 'label', explode: true, expected: '.role=admin.firstName=Alex' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=role,admin,firstName,Alex' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';role=admin;firstName=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'query',
|
||||||
|
name: 'id',
|
||||||
|
description: 'query parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=3,4,5' },
|
||||||
|
{ style: 'spaceDelimited', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'spaceDelimited', explode: false, expected: 'id=3%204%205' },
|
||||||
|
{ style: 'pipeDelimited', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'pipeDelimited', explode: false, expected: 'id=3|4|5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'role=admin&firstName=Alex' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=role,admin,firstName,Alex' },
|
||||||
|
{ style: 'deepObject', explode: true, expected: 'id[role]=admin&id[firstName]=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'cookie',
|
||||||
|
name: 'id',
|
||||||
|
description: 'cookie parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=3,4,5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'role=admin&firstName=Alex' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=role,admin,firstName,Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'header',
|
||||||
|
name: 'x-id',
|
||||||
|
description: 'header parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '3,4,5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '3,4,5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: 'role,admin,firstName,Alex' },
|
||||||
|
{ style: 'simple', explode: true, expected: 'role=admin,firstName=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach(locationTestGroup => {
|
||||||
|
describe(locationTestGroup.description, () => {
|
||||||
|
locationTestGroup.cases.forEach(valueTypeTestGroup => {
|
||||||
|
describe(valueTypeTestGroup.description, () => {
|
||||||
|
valueTypeTestGroup.cases.forEach(testCase => {
|
||||||
|
it(`should serialize correctly when style is ${testCase.style} and explode is ${testCase.explode}`, () => {
|
||||||
|
const parameter: OpenAPIParameter = {
|
||||||
|
name: locationTestGroup.name,
|
||||||
|
in: locationTestGroup.location,
|
||||||
|
style: testCase.style,
|
||||||
|
explode: testCase.explode,
|
||||||
|
};
|
||||||
|
const serialized = serializeParameterValue(parameter, valueTypeTestGroup.value);
|
||||||
|
|
||||||
|
expect(serialized).toEqual(testCase.expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('advanced serialization', () => {
|
||||||
|
it('should serialize correctly query parameter with content with application/json', () => {
|
||||||
|
const parameter: OpenAPIParameter = {
|
||||||
|
name: 'id',
|
||||||
|
in: 'query',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const parser = new OpenAPIParser({ openapi: '3.0' } as any);
|
||||||
|
const opts = new RedocNormalizedOptions({});
|
||||||
|
|
||||||
|
const field = new FieldModel(parser, parameter, '', opts);
|
||||||
|
expect(serializeParameterValue(field, { name: 'test', age: 23 })).toEqual(
|
||||||
|
'id={"name":"test","age":23}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serialize correctly header parameter with content with application/json', () => {
|
||||||
|
const parameter: OpenAPIParameter = {
|
||||||
|
name: 'x-header',
|
||||||
|
in: 'header',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const parser = new OpenAPIParser({ openapi: '3.0' } as any);
|
||||||
|
const opts = new RedocNormalizedOptions({});
|
||||||
|
|
||||||
|
const field = new FieldModel(parser, parameter, '', opts);
|
||||||
|
expect(serializeParameterValue(field, { name: 'test', age: 23 })).toEqual(
|
||||||
|
'{"name":"test","age":23}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('OpenAPI sortByRequired', () => {
|
||||||
|
it('should equal to the old data when all items have no required props', () => {
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
name: 'loginName',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'displayName',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'space',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depIds',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depNames',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pwdControl',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'csfLevel',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'siteId',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(sortByRequired(fields as FieldModel[])).toEqual(fields);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('other item should be the same order when some of items are required', () => {
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
name: 'loginName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'displayName',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'space',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depIds',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depNames',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pwdControl',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'csfLevel',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'siteId',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const sortedFields = [
|
||||||
|
{
|
||||||
|
name: 'loginName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'displayName',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'space',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depIds',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depNames',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pwdControl',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'csfLevel',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'siteId',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(sortByRequired(fields as FieldModel[])).toEqual(sortedFields);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should the order of required items is as same as the order parameter ', () => {
|
||||||
|
const fields = [
|
||||||
|
{
|
||||||
|
name: 'loginName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'space',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depIds',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depNames',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pwdControl',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'csfLevel',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'siteId',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
expect(
|
||||||
|
sortByRequired(fields as FieldModel[], ['siteId', 'displayName', 'loginName', 'email']),
|
||||||
|
).toEqual([
|
||||||
|
{
|
||||||
|
name: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'loginName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'space',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depIds',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depNames',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pwdControl',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'csfLevel',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'siteId',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(sortByRequired(fields as FieldModel[], ['email', 'displayName'])).toEqual([
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'loginName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'space',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depIds',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depNames',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pwdControl',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'csfLevel',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'siteId',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(sortByRequired(fields as FieldModel[], ['displayName'])).toEqual([
|
||||||
|
{
|
||||||
|
name: 'displayName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'loginName',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'email',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'space',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'type',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depIds',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'depNames',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'password',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'pwdControl',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'csfLevel',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'priority',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'siteId',
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,6 +16,7 @@ function throttle(func, wait) {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
const remaining = wait - (now - previous);
|
const remaining = wait - (now - previous);
|
||||||
context = this;
|
context = this;
|
||||||
|
// eslint-disable-next-line prefer-rest-params
|
||||||
args = arguments;
|
args = arguments;
|
||||||
if (remaining <= 0 || remaining > wait) {
|
if (remaining <= 0 || remaining > wait) {
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import slugify from 'slugify';
|
||||||
import { format, parse } from 'url';
|
import { format, parse } from 'url';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps over array passing `isLast` bool to iterator as the second arguemnt
|
* Maps over array passing `isLast` bool to iterator as the second argument
|
||||||
*/
|
*/
|
||||||
export function mapWithLast<T, P>(array: T[], iteratee: (item: T, isLast: boolean) => P) {
|
export function mapWithLast<T, P>(array: T[], iteratee: (item: T, isLast: boolean) => P) {
|
||||||
const res: P[] = [];
|
const res: P[] = [];
|
||||||
|
@ -83,7 +83,7 @@ export function appendToMdHeading(md: string, heading: string, content: string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// credits https://stackoverflow.com/a/46973278/1749888
|
// credits https://stackoverflow.com/a/46973278/1749888
|
||||||
export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T => {
|
export const mergeObjects = (target: any, ...sources: any[]): any => {
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ const isMergebleObject = (item): boolean => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* slugify() returns empty string when failed to slugify.
|
* slugify() returns empty string when failed to slugify.
|
||||||
* so try to return minimun slugified-string with failed one which keeps original value
|
* so try to return minimum slugified-string with failed one which keeps original value
|
||||||
* the regex codes are referenced with https://gist.github.com/mathewbyrne/1280286
|
* the regex codes are referenced with https://gist.github.com/mathewbyrne/1280286
|
||||||
*/
|
*/
|
||||||
export function safeSlugify(value: string): string {
|
export function safeSlugify(value: string): string {
|
||||||
|
@ -189,8 +189,12 @@ export function removeQueryString(serverUrl: string): string {
|
||||||
function parseURL(url: string) {
|
function parseURL(url: string) {
|
||||||
if (typeof URL === 'undefined') {
|
if (typeof URL === 'undefined') {
|
||||||
// node
|
// node
|
||||||
return new (require('url')).URL(url);
|
return new (require('url').URL)(url);
|
||||||
} else {
|
} else {
|
||||||
return new URL(url);
|
return new URL(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function unescapeHTMLChars(str: string): string {
|
||||||
|
return str.replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(parseInt(code, 10)));
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user