mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-07 13:44:54 +03:00
pull Redocly/redoc.git master to fork
This commit is contained in:
commit
02f8819eb1
|
@ -9,4 +9,4 @@
|
|||
!webpack.config.ts
|
||||
|
||||
!package.json
|
||||
!yarn.lock
|
||||
!package-lock.json
|
||||
|
|
52
.eslintrc.js
Normal file
52
.eslintrc.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
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/explicit-module-boundary-types': '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',
|
||||
'@typescript-eslint/ban-types': ['error', { types: { object: false }, extendDefaults: true }],
|
||||
'@typescript-eslint/no-var-requires': '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
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
36
.github/CONTRIBUTING.md
vendored
36
.github/CONTRIBUTING.md
vendored
|
@ -15,56 +15,58 @@ 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:
|
||||
|
||||
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!
|
||||
4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development.
|
||||
5. Format your code with prettier (`yarn prettier`).
|
||||
4. Ensure the test suite passes (`npm test`). Tip: `npm test -- --watch TestName` is helpful in development.
|
||||
5. Format your code with prettier (`npm run prettier`).
|
||||
|
||||
## 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:
|
||||
|
||||
```bash
|
||||
$ yarn install # or npm
|
||||
$ npm install # or npm
|
||||
```
|
||||
|
||||
### Commonly used NPM scripts
|
||||
|
||||
``` bash
|
||||
# dev-server, watch and auto reload playground
|
||||
$ yarn start
|
||||
$ npm start
|
||||
|
||||
# start playground app in production environment
|
||||
$ yarn start:prod
|
||||
$ npm run start:prod
|
||||
|
||||
# runt tslint
|
||||
$ yarn lint
|
||||
$ npm run lint
|
||||
|
||||
# try autofix tslint issues
|
||||
$ yarn lint --fix
|
||||
$ npm run lint -- --fix
|
||||
|
||||
# run unit tests
|
||||
$ yarn unit
|
||||
$ npm run unit
|
||||
|
||||
# run e2e tests
|
||||
$ yarn e2e
|
||||
$ npm run e2e
|
||||
# Make sure you have created bundle before running e2e test
|
||||
# E.g. run `npm run bundle` and wait for the finishing process.
|
||||
|
||||
# open cypress UI to debug e2e test
|
||||
$ yarn cy:open
|
||||
$ npm run cy:open
|
||||
|
||||
# run the full test suite, include linting / unit / e2e
|
||||
$ yarn test
|
||||
# run the unit tests (includes linting and license checks)
|
||||
$ npm test
|
||||
|
||||
# prepare bundles
|
||||
$ yarn bundle
|
||||
$ npm run bundle
|
||||
|
||||
# format the code using prettier
|
||||
$ yarn prettier
|
||||
$ npm run prettier
|
||||
|
||||
# auto-generate changelog
|
||||
$ yarn changelog
|
||||
$ npm run changelog
|
||||
```
|
||||
|
||||
There are some other scripts available in the `scripts` section of the `package.json` file.
|
||||
|
|
42
.github/workflows/demo-deploy-s3.yml
vendored
Normal file
42
.github/workflows/demo-deploy-s3.yml
vendored
Normal file
|
@ -0,0 +1,42 @@
|
|||
name: Redoc demo CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]*.[0-9]*.[0-9]*
|
||||
|
||||
jobs:
|
||||
build-and-unit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- run: npm ci
|
||||
- run: npm run bundle
|
||||
- run: npm test
|
||||
deploy:
|
||||
needs: build-and-unit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: cache node modules
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: |
|
||||
npm-${{ hashFiles('package-lock.json') }}
|
||||
npm-
|
||||
- name: Configure AWS Credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-1
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build package
|
||||
run: npm run build:demo
|
||||
- name: Deploy to S3 bucket
|
||||
run: npm run deploy:demo
|
||||
- name: Invalidate
|
||||
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DEMO_DISTRIBUTION_ID }} --paths "/*"
|
17
.github/workflows/unit-tests.yml
vendored
17
.github/workflows/unit-tests.yml
vendored
|
@ -3,19 +3,10 @@ name: Unit Tests
|
|||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
build-and-unit:
|
||||
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: yarn install, build, and test
|
||||
run: |
|
||||
npm install -g yarn
|
||||
yarn install
|
||||
yarn bundle
|
||||
yarn test
|
||||
- run: npm ci
|
||||
- run: npm run bundle
|
||||
- run: npm test
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -34,4 +34,5 @@ cli/index.js
|
|||
/coverage
|
||||
.ghpages-tmp
|
||||
stats.json
|
||||
/package-lock.json
|
||||
yarn.lock
|
||||
.idea
|
||||
|
|
|
@ -2,7 +2,6 @@ language: node_js
|
|||
node_js:
|
||||
- '10'
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- "~/.cache"
|
||||
env:
|
||||
|
@ -10,11 +9,9 @@ env:
|
|||
- GH_REF: github.com/Redocly/redoc.git
|
||||
- GIT_AUTHOR_EMAIL: redoc-bot@users.noreply.github.com
|
||||
- GIT_AUTHOR_NAME: RedocBot
|
||||
- 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: 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: Pc86j2/rgCPAEWcjzPbbVFkL2SwNt4CpeXP69zedMi9RomQblMJa9R0wbAT6H5VCnPky2cpmxkzjFWOc91N9crzceg6aOoWkPr6pTTnTv+EL70i6+XSrAFpkgRjszprxnU1Bz+GcYEjP/zR8479fx8ooSl7MwHOaP7XiQyaBQAbY1CPlmpT+b4Ut7Fm5QnD90/NgPjbKkngl0kVUfNFdFOSfJ3QWLyFCUUSQ4DlxccJOTIaOH/n8u9Nz4NTuuHE3XeRuEsuj2SJutJnFBUYwsvugrdPvKWiubkewJfylp6EwABzByENsg6XxW9SIq80lMc3Oi7ld9L2lAgpj+8/42olnbMzH0F0rw/p1ccPAdVwQVV6YFaqCzivK5A5aX5LmGKwJ6SR9k1PgcWP6sKKMIsIEObbyM88Tke3QkreEz+cLg/3jjko7Vpb0tbqh8BtbpWV+exL4rX3r2C5Mb1Es1W597hN5LSczWYFgw0ZETpfbVZg6Ri1iZks0wpsT/E+c0q2scUaBVrdTZseHxUPB7mPDlXL1l9/i4sOxPyBHZtJRAzeTT/fOXfj4vuD+ihspXzoRRLaQbizlb8FpyPA47XdmBDpXi3OBiaIFLwvybEn7qM7rqvWxdz6vvCZv0t/AN3t3Qvh2vHKCshHecaa8NoJQHWrdFMHeecYHyeoujZ8=
|
||||
addons:
|
||||
chrome: stable
|
||||
apt:
|
||||
|
@ -35,6 +32,6 @@ deploy:
|
|||
tags: true
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: yarn deploy:demo
|
||||
script: npm run deploy:demo
|
||||
on:
|
||||
tags: true
|
||||
|
|
218
CHANGELOG.md
218
CHANGELOG.md
|
@ -1,3 +1,221 @@
|
|||
# [2.0.0-rc.40](https://github.com/Redocly/redoc/compare/v2.0.0-rc.39...v2.0.0-rc.40) (2020-08-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* invalid discriminator dropdown behaviour with enum ([be07197](https://github.com/Redocly/redoc/commit/be07197e6d1e85a3fd3e61189a36b288751c077d))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.39](https://github.com/Redocly/redoc/compare/v2.0.0-rc.38...v2.0.0-rc.39) (2020-08-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix broken dropdowns with SSR by using forked react-dropdown-aria ([c322639](https://github.com/Redocly/redoc/commit/c322639f7c3e7efbbd623ae83afb88faa91d9e67))
|
||||
* make callbacks expandable by keyboard ([#1354](https://github.com/Redocly/redoc/issues/1354)) ([46eee7b](https://github.com/Redocly/redoc/commit/46eee7b70c8ee9da0d8857a823c4df39a5f18b53))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.38](https://github.com/Redocly/redoc/compare/v2.0.0-rc.37...v2.0.0-rc.38) (2020-08-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not crash for invalid parameter.in value ([addf895](https://github.com/Redocly/redoc/commit/addf8956e33654a1586a8ac6ed7325519cd99da8)), closes [#1340](https://github.com/Redocly/redoc/issues/1340)
|
||||
* scale sideMenu labels according to computed font size ([#1356](https://github.com/Redocly/redoc/issues/1356)) ([fed9a06](https://github.com/Redocly/redoc/commit/fed9a061d59592ec17cedbe4fd392e1f74c21527))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.37](https://github.com/Redocly/redoc/compare/v2.0.0-rc.36...v2.0.0-rc.37) (2020-08-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add webhooks support ([#1304](https://github.com/Redocly/redoc/issues/1304)) ([41f81b4](https://github.com/Redocly/redoc/commit/41f81b4d96648fec6bf0c39799c0aa2dded48749))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.36](https://github.com/Redocly/redoc/compare/v2.0.0-rc.35...v2.0.0-rc.36) (2020-08-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* highlight json keys using different color ([#1287](https://github.com/Redocly/redoc/issues/1287)) ([c9596d4](https://github.com/Redocly/redoc/commit/c9596d4b6cd9dced9fdee77525e0da90960c562a))
|
||||
* make elements accessible by keyboard navigation tools ([#1339](https://github.com/Redocly/redoc/issues/1339)) ([2ce7189](https://github.com/Redocly/redoc/commit/2ce71895bc14f9189b4e6cbdb6d838898717823f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* new option simpleOneOfTypeLabel ([7af2efe](https://github.com/Redocly/redoc/commit/7af2efe731cdb16ebe5de6cb3e96f80cceb7d98d))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.35](https://github.com/Redocly/redoc/compare/v2.0.0-rc.34...v2.0.0-rc.35) (2020-07-24)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update EnumValues component ([#1324](https://github.com/Redocly/redoc/issues/1324)) ([de27ac0](https://github.com/Redocly/redoc/commit/de27ac03081d55967f5a479fb1352a83b8ceb8b2))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.34](https://github.com/Redocly/redoc/compare/v2.0.0-rc.33...v2.0.0-rc.34) (2020-07-24)
|
||||
|
||||
Same as rc.33 by mistake
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.33](https://github.com/Redocly/redoc/compare/v2.0.0-rc.31...v2.0.0-rc.33) (2020-07-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* default style and explode for params ([633d712](https://github.com/Redocly/redoc/commit/633d71293fa9af2bda3bf456a9258625ee2b94a1)), closes [#1016](https://github.com/Redocly/redoc/issues/1016)
|
||||
* fix contrast ratio for response titles ([47c6319](https://github.com/Redocly/redoc/commit/47c63192062d87b2b3205b915472930eaff6cc03))
|
||||
* fix expand variable for vars with hyphens or dots ([0904b3f](https://github.com/Redocly/redoc/commit/0904b3fec24edc56c4a4951501fe02ae22fd852b)), closes [#926](https://github.com/Redocly/redoc/issues/926)
|
||||
* make dropdowns accessible by keyboard ([e8a0d10](https://github.com/Redocly/redoc/commit/e8a0d105ca52204b0d6fd61f5e909d9dbbe6f147))
|
||||
* make endpoint dropdown accessible ([3d25005](https://github.com/Redocly/redoc/commit/3d25005f084f06ac01b8fa13eb1d69092e99fd27))
|
||||
* make properties focusable ([05fd754](https://github.com/Redocly/redoc/commit/05fd7543a29e0aeb364c1ba3f2d736656de7b3b7))
|
||||
* make response sections focusable ([442014c](https://github.com/Redocly/redoc/commit/442014c06d6a7d2260adf7bc5798dd29869f10c9))
|
||||
* make sample controls focusable ([006031c](https://github.com/Redocly/redoc/commit/006031c51787b617f2b0aed80a4b8486c5d2d3ca))
|
||||
* update focus styling ([30a27c1](https://github.com/Redocly/redoc/commit/30a27c116b366428570d0b5516b5b2b4bcd0c5fc))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add maxDisplayedEnumValues config and buttons for show/hide enums ([#1322](https://github.com/Redocly/redoc/issues/1322)) ([a2b018d](https://github.com/Redocly/redoc/commit/a2b018d393ee25fb8e9233f8123c29d14ab054c7))
|
||||
* array size info based on min max Items properties ([#1308](https://github.com/Redocly/redoc/issues/1308)) ([644e96a](https://github.com/Redocly/redoc/commit/644e96ae457047ce09f55aa1f14a42c41dbc1dc8))
|
||||
* new option sortEnumValuesAlphabetically ([#1321](https://github.com/Redocly/redoc/issues/1321)) ([a96a11a](https://github.com/Redocly/redoc/commit/a96a11a4dc8a509c6c3fba67dc4e065b66624e18))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.32](https://github.com/Redocly/redoc/compare/v2.0.0-rc.31...v2.0.0-rc.32) (2020-07-21)
|
||||
|
||||
Same as rc.31 by mistake
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.31](https://github.com/Redocly/redoc/compare/v2.0.0-rc.30...v2.0.0-rc.31) (2020-06-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not display long regexps ([#1295](https://github.com/Redocly/redoc/issues/1295)) ([2ede22c](https://github.com/Redocly/redoc/commit/2ede22c45cc970ea1ac296adbae1f6032744f823))
|
||||
* prevent body scrolling when user scrolls side menu ([#1300](https://github.com/Redocly/redoc/issues/1300)) ([865a56a](https://github.com/Redocly/redoc/commit/865a56a2a9a105ef7b3b9150767399ca7339195a))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.30](https://github.com/Redocly/redoc/compare/v2.0.0-rc.29...v2.0.0-rc.30) (2020-05-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add security headers to Docker nginx config ([#1244](https://github.com/Redocly/redoc/issues/1244)) ([4512436](https://github.com/Redocly/redoc/commit/4512436f1d88bd99558fe5f8384b37aa62562480))
|
||||
* keep 3-column layout on 13-inch mbp ([8d1d4c8](https://github.com/Redocly/redoc/commit/8d1d4c82e1377aecf936985ac13fa9bf5257562a))
|
||||
* proper search-index dispose ([9dd129d](https://github.com/Redocly/redoc/commit/9dd129d90b87f24ad20f084c44d48be50d750c94))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.29](https://github.com/Redocly/redoc/compare/v2.0.0-rc.28...v2.0.0-rc.29) (2020-05-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* depreacate x-code-samples, rename to x-codeSamples for consistency ([becc2f5](https://github.com/Redocly/redoc/commit/becc2f58568388b6500e6476874f27f62ff58ba9))
|
||||
* do not crash on incompatible allOf, console.warn instead ([6e607b9](https://github.com/Redocly/redoc/commit/6e607b9a2928b062c7705087432c0f0d88e74f5d)), closes [#1156](https://github.com/Redocly/redoc/issues/1156)
|
||||
* download button opens in new tab instead of downloading ([b59faad](https://github.com/Redocly/redoc/commit/b59faada8210a4c8f61fa0e850b7d844574a46d1)), closes [#1247](https://github.com/Redocly/redoc/issues/1247)
|
||||
* fix broken md headings with ampersand ([8460659](https://github.com/Redocly/redoc/commit/846065916d58cf628f0bc93c74be429ecdea12e7)), closes [#1173](https://github.com/Redocly/redoc/issues/1173)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** add the --title option to the serve subcommand ([#1160](https://github.com/Redocly/redoc/issues/1160)) ([10414fc](https://github.com/Redocly/redoc/commit/10414fc6d5c0f91b5e93b1ed2326e4e508611324))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.28](https://github.com/Redocly/redoc/compare/v2.0.0-rc.27...v2.0.0-rc.28) (2020-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* encode URLs in json samples linkify (xss) ([62c01da](https://github.com/Redocly/redoc/commit/62c01da420fca2137674ae562d4ecba54db97da9)), thanks to @masatokinugawa
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.27](https://github.com/Redocly/redoc/compare/v2.0.0-rc.26...v2.0.0-rc.27) (2020-04-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add callbacks support ([#1224](https://github.com/Redocly/redoc/issues/1224)) ([57e93ec](https://github.com/Redocly/redoc/commit/57e93ec4355de2659fcb5449b14b7ed738c6c276))
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.26](https://github.com/Redocly/redoc/compare/v2.0.0-rc.25...v2.0.0-rc.26) (2020-03-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* crash to wrong spelling in localeCompare ([3908a7c](https://github.com/Redocly/redoc/commit/3908a7c46448d277b82318659cdea65db52f9e70)), closes [#1218](https://github.com/Redocly/redoc/issues/1218)
|
||||
|
||||
|
||||
|
||||
# [2.0.0-rc.25](https://github.com/Redocly/redoc/compare/v2.0.0-rc.24...v2.0.0-rc.25) (2020-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* do not collapse top level on Collapse All in json samples ([#1209](https://github.com/Redocly/redoc/issues/1209)) ([830371b](https://github.com/Redocly/redoc/commit/830371b5d1edf4ba7a138b3b3d78148d020e0349))
|
||||
* fix passing boolean value to showExtensions options ([#1211](https://github.com/Redocly/redoc/issues/1211)) ([c6eaa02](https://github.com/Redocly/redoc/commit/c6eaa0281bb0f62b019c865e4aefb863ce84d628))
|
||||
* improve names for some theme settings ([a0bd27c](https://github.com/Redocly/redoc/commit/a0bd27c75427a39abc9c753b0654678eed2f3851))
|
||||
* sort discriminator entries by mapping order ([#1216](https://github.com/Redocly/redoc/issues/1216)) ([ac4f915](https://github.com/Redocly/redoc/commit/ac4f915494f289d1c97ffdfe3af59efd94734f8c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add x-explicitMappingOnly extension ([#1215](https://github.com/Redocly/redoc/issues/1215)) ([ea5b0aa](https://github.com/Redocly/redoc/commit/ea5b0aabf9133d11d3a8fcb79f9515d21e0d7ac0))
|
||||
|
||||
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
|
|
19
README.md
19
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 |
|
||||
|
||||
## 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/)
|
||||
- [Zuora](https://www.zuora.com/developer/api-reference/)
|
||||
- [Shopify Draft Orders](https://help.shopify.com/api/draft-orders)
|
||||
- [Discourse](http://docs.discourse.org)
|
||||
- [Commbox](https://www.commbox.io/api/)
|
||||
- [APIs.guru](https://apis.guru/api-doc/)
|
||||
- [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.
|
||||
|
||||
### 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
|
||||
|
||||
or using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
|
||||
|
||||
npm install redoc --save
|
||||
|
||||
### 2. Reference redoc script in HTML
|
||||
For **CDN**:
|
||||
```html
|
||||
|
@ -211,7 +211,7 @@ You can inject Security Definitions widget into any place of your specification
|
|||
ReDoc makes use of the following [vendor extensions](https://swagger.io/specification/#specificationExtensions):
|
||||
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
||||
* [`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-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples
|
||||
* [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests
|
||||
* [`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
|
||||
|
@ -230,7 +230,10 @@ You can use all of the following options with standalone version on <redoc> tag
|
|||
* `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`.
|
||||
* `hideSchemaTitles` - do not display schema `title` next to to the type
|
||||
* `simpleOneOfTypeLabel` - show only unique oneOf types in the label without titles
|
||||
* `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).
|
||||
|
|
|
@ -17,7 +17,7 @@ const localDistDir = './benchmark/revisions/local/bundles';
|
|||
sh.rm('-rf', localDistDir);
|
||||
console.log(`Building local dist: ${localDistDir}`);
|
||||
sh.mkdir('-p', localDistDir);
|
||||
exec(`yarn bundle:lib --output-path ${localDistDir}`);
|
||||
exec(`npm run bundle:lib --output-path ${localDistDir}`);
|
||||
|
||||
const revisions = [];
|
||||
for (const arg of args) {
|
||||
|
@ -119,7 +119,7 @@ function buildRevisionDist(revision) {
|
|||
|
||||
const pwd = sh.pwd();
|
||||
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);
|
||||
return distDir;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,11 @@ YargsParser.command(
|
|||
describe: 'path or URL to your spec',
|
||||
});
|
||||
|
||||
yargs.options('title', {
|
||||
describe: 'Page Title',
|
||||
type: 'string',
|
||||
});
|
||||
|
||||
yargs.option('s', {
|
||||
alias: 'ssr',
|
||||
describe: 'Enable server-side rendering',
|
||||
|
@ -73,6 +78,7 @@ YargsParser.command(
|
|||
async argv => {
|
||||
const config: Options = {
|
||||
ssr: argv.ssr as boolean,
|
||||
title: argv.title as string,
|
||||
watch: argv.watch as boolean,
|
||||
templateFileName: argv.template as string,
|
||||
templateOptions: argv.templateOptions || {},
|
||||
|
|
1946
cli/package-lock.json
generated
Normal file
1946
cli/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc-cli",
|
||||
"version": "0.9.5",
|
||||
"version": "0.9.12",
|
||||
"description": "ReDoc's Command Line Interface",
|
||||
"main": "index.js",
|
||||
"bin": "index.js",
|
||||
|
@ -11,25 +11,25 @@
|
|||
"node": ">= 8"
|
||||
},
|
||||
"dependencies": {
|
||||
"chokidar": "^3.0.2",
|
||||
"handlebars": "^4.1.2",
|
||||
"chokidar": "^3.4.1",
|
||||
"handlebars": "^4.7.6",
|
||||
"isarray": "^2.0.5",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mobx": "^4.2.0",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"redoc": "2.0.0-rc.21",
|
||||
"styled-components": "^4.3.2",
|
||||
"tslib": "^1.10.0",
|
||||
"yargs": "^13.3.0"
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"redoc": "^2.0.0-rc.40",
|
||||
"styled-components": "^5.1.1",
|
||||
"tslib": "^2.0.0",
|
||||
"yargs": "^15.4.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chokidar": "^2.1.3",
|
||||
"@types/handlebars": "^4.0.39",
|
||||
"@types/mkdirp": "^0.5.2"
|
||||
"@types/handlebars": "^4.1.0",
|
||||
"@types/mkdirp": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
2076
cli/yarn.lock
2076
cli/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -7,12 +7,12 @@
|
|||
|
||||
FROM node:alpine
|
||||
|
||||
RUN apk update && apk add --no-cache git
|
||||
RUN apk update && apk add --no-cache git
|
||||
|
||||
# Install dependencies
|
||||
WORKDIR /build
|
||||
COPY package.json yarn.lock /build/
|
||||
RUN yarn install --frozen-lockfile --ignore-optional --ignore-scripts
|
||||
COPY package.json package-lock.json /build/
|
||||
RUN npm ci --no-optional --ignore-scripts
|
||||
|
||||
# copy only required for the build files
|
||||
COPY src /build/src
|
||||
|
|
|
@ -12,6 +12,12 @@ Serve local file:
|
|||
docker run -it --rm -p 80:80 \
|
||||
-v $(pwd)/demo/swagger.yaml:/usr/share/nginx/html/swagger.yaml \
|
||||
-e SPEC_URL=swagger.yaml redocly/redoc
|
||||
|
||||
Serve local file and watch for updates:
|
||||
|
||||
docker run -it --rm -p 80:80 \
|
||||
-v $(pwd)/demo/:/usr/share/nginx/html/swagger/ \
|
||||
-e SPEC_URL=swagger/swagger.yaml redocly/redoc
|
||||
|
||||
## Runtime configuration options
|
||||
|
||||
|
|
|
@ -21,6 +21,13 @@ http {
|
|||
alias /usr/share/nginx/html/;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
# Add security headers
|
||||
add_header 'X-Frame-Options' 'deny always';
|
||||
add_header 'X-XSS-Protection' '"1; mode=block" always';
|
||||
add_header 'X-Content-Type-Options' 'nosniff always';
|
||||
add_header 'Referrer-Policy' 'strict-origin-when-cross-origin';
|
||||
|
||||
# Set access control header
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
#
|
||||
|
@ -36,11 +43,25 @@ http {
|
|||
return 204;
|
||||
}
|
||||
if ($request_method = 'POST') {
|
||||
# Add security headers
|
||||
add_header 'X-Frame-Options' 'deny always';
|
||||
add_header 'X-XSS-Protection' '"1; mode=block" always';
|
||||
add_header 'X-Content-Type-Options' 'nosniff always';
|
||||
add_header 'Referrer-Policy' 'strict-origin-when-cross-origin';
|
||||
|
||||
# Set access control header
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||
}
|
||||
if ($request_method = 'GET') {
|
||||
# Add security headers
|
||||
add_header 'X-Frame-Options' 'deny always';
|
||||
add_header 'X-XSS-Protection' '"1; mode=block" always';
|
||||
add_header 'X-Content-Type-Options' 'nosniff always';
|
||||
add_header 'Referrer-Policy' 'strict-origin-when-cross-origin';
|
||||
|
||||
# Set access control header
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||
|
|
|
@ -13,10 +13,7 @@ const demos = [
|
|||
},
|
||||
{ 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/graphhopper.com/1.0/swagger.yaml',
|
||||
label: 'GraphHopper',
|
||||
},
|
||||
{ value: 'https://docs.graphhopper.com/openapi.json', label: 'GraphHopper' },
|
||||
];
|
||||
|
||||
const DEFAULT_SPEC = 'openapi.yaml';
|
||||
|
@ -100,11 +97,14 @@ class DemoApp extends React.Component<
|
|||
src="https://ghbtns.com/github-btn.html?user=Redocly&repo=redoc&type=star&count=true&size=large"
|
||||
frameBorder="0"
|
||||
scrolling="0"
|
||||
width="150px"
|
||||
width="160px"
|
||||
height="30px"
|
||||
/>
|
||||
</Heading>
|
||||
<RedocStandalone specUrl={proxiedUrl} options={{ scrollYOffset: 'nav' }} />
|
||||
<RedocStandalone
|
||||
specUrl={proxiedUrl}
|
||||
options={{ scrollYOffset: 'nav', untrustedSpec: true }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ paths:
|
|||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
x-code-samples:
|
||||
x-codeSamples:
|
||||
- lang: 'C#'
|
||||
source: |
|
||||
PetStore.v1.Pet pet = new PetStore.v1.Pet();
|
||||
|
@ -162,7 +162,7 @@ paths:
|
|||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
x-code-samples:
|
||||
x-codeSamples:
|
||||
- lang: PHP
|
||||
source: |
|
||||
$form = new \PetStore\Entities\Pet();
|
||||
|
@ -489,6 +489,234 @@ paths:
|
|||
description: Invalid ID supplied
|
||||
'404':
|
||||
description: Order not found
|
||||
/store/subscribe:
|
||||
post:
|
||||
tags:
|
||||
- store
|
||||
summary: Subscribe to the Store events
|
||||
description: Add subscription for a store events
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
callbackUrl:
|
||||
type: string
|
||||
format: uri
|
||||
description: This URL will be called by the server when the desired event will occur
|
||||
example: https://myserver.com/send/callback/here
|
||||
eventName:
|
||||
type: string
|
||||
description: Event name for the subscription
|
||||
enum:
|
||||
- orderInProgress
|
||||
- orderShipped
|
||||
- orderDelivered
|
||||
example: orderInProgress
|
||||
required:
|
||||
- callbackUrl
|
||||
- eventName
|
||||
responses:
|
||||
'201':
|
||||
description: Subscription added
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
subscriptionId:
|
||||
type: string
|
||||
example: AAA-123-BBB-456
|
||||
callbacks:
|
||||
orderInProgress:
|
||||
'{$request.body#/callbackUrl}?event={$request.body#/eventName}':
|
||||
servers:
|
||||
- url: //callback-url.path-level/v1
|
||||
description: Path level server 1
|
||||
- url: //callback-url.path-level/v2
|
||||
description: Path level server 2
|
||||
post:
|
||||
summary: Order in Progress (Summary)
|
||||
description: A callback triggered every time an Order is updated status to "inProgress" (Description)
|
||||
externalDocs:
|
||||
description: Find out more
|
||||
url: 'https://more-details.com/demo'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-10-19T16:46:45Z'
|
||||
status:
|
||||
type: string
|
||||
example: 'inProgress'
|
||||
application/xml:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
example: |
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<orderId>123</orderId>
|
||||
<status>inProgress</status>
|
||||
<timestamp>2018-10-19T16:46:45Z</timestamp>
|
||||
</root>
|
||||
responses:
|
||||
'200':
|
||||
description: Callback successfully processed and no retries will be performed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
someProp:
|
||||
type: string
|
||||
example: '123'
|
||||
'299':
|
||||
description: Response for cancelling subscription
|
||||
'500':
|
||||
description: Callback processing failed and retries will be performed
|
||||
x-codeSamples:
|
||||
- lang: 'C#'
|
||||
source: |
|
||||
PetStore.v1.Pet pet = new PetStore.v1.Pet();
|
||||
pet.setApiKey("your api key");
|
||||
pet.petType = PetStore.v1.Pet.TYPE_DOG;
|
||||
pet.name = "Rex";
|
||||
// set other fields
|
||||
PetStoreResponse response = pet.create();
|
||||
if (response.statusCode == HttpStatusCode.Created)
|
||||
{
|
||||
// Successfully created
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something wrong -- check response for errors
|
||||
Console.WriteLine(response.getRawResponse());
|
||||
}
|
||||
- lang: PHP
|
||||
source: |
|
||||
$form = new \PetStore\Entities\Pet();
|
||||
$form->setPetType("Dog");
|
||||
$form->setName("Rex");
|
||||
// set other fields
|
||||
try {
|
||||
$pet = $client->pets()->create($form);
|
||||
} catch (UnprocessableEntityException $e) {
|
||||
var_dump($e->getErrors());
|
||||
}
|
||||
put:
|
||||
description: Order in Progress (Only Description)
|
||||
servers:
|
||||
- url: //callback-url.operation-level/v1
|
||||
description: Operation level server 1 (Operation override)
|
||||
- url: //callback-url.operation-level/v2
|
||||
description: Operation level server 2 (Operation override)
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-10-19T16:46:45Z'
|
||||
status:
|
||||
type: string
|
||||
example: 'inProgress'
|
||||
application/xml:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
example: |
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<root>
|
||||
<orderId>123</orderId>
|
||||
<status>inProgress</status>
|
||||
<timestamp>2018-10-19T16:46:45Z</timestamp>
|
||||
</root>
|
||||
responses:
|
||||
'200':
|
||||
description: Callback successfully processed and no retries will be performed
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
someProp:
|
||||
type: string
|
||||
example: '123'
|
||||
orderShipped:
|
||||
'{$request.body#/callbackUrl}?event={$request.body#/eventName}':
|
||||
post:
|
||||
description: |
|
||||
Very long description
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
culpa qui officia deserunt mollit anim id est laborum.
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-10-19T16:46:45Z'
|
||||
estimatedDeliveryDate:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-11-11T16:00:00Z'
|
||||
responses:
|
||||
'200':
|
||||
description: Callback successfully processed and no retries will be performed
|
||||
orderDelivered:
|
||||
'http://notificationServer.com?url={$request.body#/callbackUrl}&event={$request.body#/eventName}':
|
||||
post:
|
||||
deprecated: true
|
||||
summary: Order delivered
|
||||
description: A callback triggered every time an Order is delivered to the recipient
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
orderId:
|
||||
type: string
|
||||
example: '123'
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
example: '2018-10-19T16:46:45Z'
|
||||
responses:
|
||||
'200':
|
||||
description: Callback successfully processed and no retries will be performed
|
||||
/user:
|
||||
post:
|
||||
tags:
|
||||
|
@ -955,7 +1183,23 @@ components:
|
|||
examples:
|
||||
Order:
|
||||
value:
|
||||
quantity: 1,
|
||||
shipDate: 2018-10-19T16:46:45Z,
|
||||
status: placed,
|
||||
quantity: 1
|
||||
shipDate: '2018-10-19T16:46:45Z'
|
||||
status: placed
|
||||
complete: false
|
||||
x-webhooks:
|
||||
newPet:
|
||||
post:
|
||||
summary: New pet
|
||||
description: Information about a new pet in the systems
|
||||
operationId: newPet
|
||||
tags:
|
||||
- pet
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Pet"
|
||||
responses:
|
||||
"200":
|
||||
description: Return a 200 status to indicate that the data was received successfully
|
|
@ -26,7 +26,7 @@ const specUrl =
|
|||
(userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml');
|
||||
|
||||
let store;
|
||||
const options: RedocRawOptions = { nativeScrollbars: false };
|
||||
const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 };
|
||||
|
||||
async function init() {
|
||||
const spec = await loadAndBundleSpec(specUrl);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
|
||||
import * as ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
|
||||
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||
import { compact } from 'lodash';
|
||||
import { resolve } from 'path';
|
||||
|
@ -7,17 +7,14 @@ import * as webpack from 'webpack';
|
|||
|
||||
const VERSION = JSON.stringify(require('../package.json').version);
|
||||
const REVISION = JSON.stringify(
|
||||
require('child_process')
|
||||
.execSync('git rev-parse --short HEAD')
|
||||
.toString()
|
||||
.trim(),
|
||||
require('child_process').execSync('git rev-parse --short HEAD').toString().trim(),
|
||||
);
|
||||
|
||||
function root(filename) {
|
||||
return resolve(__dirname + '/' + filename);
|
||||
}
|
||||
|
||||
const tsLoader = env => ({
|
||||
const tsLoader = (env) => ({
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
compilerOptions: {
|
||||
|
@ -27,7 +24,7 @@ const tsLoader = env => ({
|
|||
},
|
||||
});
|
||||
|
||||
const babelLoader = mode => ({
|
||||
const babelLoader = () => ({
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
generatorOpts: {
|
||||
|
@ -38,13 +35,6 @@ const babelLoader = mode => ({
|
|||
['@babel/plugin-syntax-decorators', { legacy: true }],
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-jsx',
|
||||
[
|
||||
'babel-plugin-styled-components',
|
||||
{
|
||||
minify: true,
|
||||
displayName: mode !== 'production',
|
||||
},
|
||||
],
|
||||
]),
|
||||
},
|
||||
});
|
||||
|
@ -114,7 +104,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
use: compact([
|
||||
mode !== 'production' ? babelHotLoader : undefined,
|
||||
tsLoader(env),
|
||||
babelLoader(mode),
|
||||
babelLoader(),
|
||||
]),
|
||||
exclude: [/node_modules/],
|
||||
},
|
||||
|
@ -161,7 +151,9 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
ignore(/js-yaml\/dumper\.js$/),
|
||||
ignore(/json-schema-ref-parser\/lib\/dereference\.js/),
|
||||
ignore(/^\.\/SearchWorker\.worker$/),
|
||||
new CopyWebpackPlugin(['demo/openapi.yaml']),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: ['demo/openapi.yaml'],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ ReDoc makes use of the following [vendor extensions](https://swagger.io/specific
|
|||
### Swagger Object vendor extensions
|
||||
Extend OpenAPI root [Swagger Object](https://swagger.io/specification/#oasObject)
|
||||
#### x-servers
|
||||
Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#server-object). Currently doesn't support templates.
|
||||
Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#serverObject). Currently doesn't support templates.
|
||||
|
||||
#### x-tagGroups
|
||||
|
||||
|
@ -162,13 +162,13 @@ x-traitTag: true
|
|||
|
||||
### Operation Object vendor extensions
|
||||
Extends OpenAPI [Operation Object](http://swagger.io/specification/#operationObject)
|
||||
#### x-code-samples
|
||||
#### x-codeSamples
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-code-samples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation |
|
||||
| x-codeSamples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation |
|
||||
|
||||
###### Usage in ReDoc
|
||||
`x-code-samples` are rendered on the right panel of ReDoc
|
||||
`x-codeSamples` are rendered on the right panel of ReDoc
|
||||
|
||||
#### <a name="codeSampleObject"></a>Code Sample Object
|
||||
Operation code sample
|
||||
|
@ -306,3 +306,37 @@ Player:
|
|||
x-additionalPropertiesName: attribute-name
|
||||
type: string
|
||||
```
|
||||
|
||||
#### x-explicitMappingOnly
|
||||
**ATTENTION**: This is ReDoc-specific vendor extension. It won't be supported by other tools.
|
||||
|
||||
Extends the `discriminator` property of the schema object.
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-explicitMappingOnly | boolean | limit the discriminator selectpicker to the explicit mappings only |
|
||||
|
||||
###### Usage in ReDoc
|
||||
ReDoc uses this extension to filter the `discriminator` mappings shown in the selectpicker.
|
||||
When set to `true`, the selectpicker will only list the the explicitly defined mappings. When `false`,
|
||||
the default behavior is kept, i.e. explicit and implicit mappings will be shown.
|
||||
|
||||
###### x-explicitMappingOnly example
|
||||
|
||||
|
||||
```yaml
|
||||
Pet:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- photoUrls
|
||||
discriminator:
|
||||
propertyName: petType
|
||||
x-explicitMappingOnly: true
|
||||
mapping:
|
||||
cat: "#/components/schemas/Cat"
|
||||
bee: "#/components/schemas/HoneyBee"
|
||||
```
|
||||
|
||||
Will show in the selectpicker only the items `cat` and `bee`, even though the `Dog` class inherits from
|
||||
the `Pet` class.
|
||||
|
|
|
@ -6,7 +6,7 @@ describe('Menu', () => {
|
|||
it('should have valid items count', () => {
|
||||
cy.get('.menu-content')
|
||||
.find('li')
|
||||
.should('have.length', 6 + (2 + 8 + 1 + 4 + 2) + (1 + 8));
|
||||
.should('have.length', 34);
|
||||
});
|
||||
|
||||
it('should sync active menu items while scroll', () => {
|
||||
|
|
|
@ -27,6 +27,8 @@ describe('Search', () => {
|
|||
it('should support arrow navigation', () => {
|
||||
getSearchInput().type('int', { force: true });
|
||||
|
||||
cy.wait(500);
|
||||
|
||||
getSearchInput().type('{downarrow}', { force: true });
|
||||
getResult(0).should('have.class', 'active');
|
||||
|
||||
|
|
19504
package-lock.json
generated
Normal file
19504
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
153
package.json
153
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"version": "2.0.0-rc.21",
|
||||
"version": "2.0.0-rc.40",
|
||||
"description": "ReDoc",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -42,87 +42,89 @@
|
|||
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json",
|
||||
"prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"",
|
||||
"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",
|
||||
"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",
|
||||
"build:demo": "webpack --mode=production --config demo/webpack.config.ts",
|
||||
"deploy:demo": "npm run build:demo && deploy-to-gh-pages --update demo/dist",
|
||||
"deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read",
|
||||
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause' --summary",
|
||||
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.7.5",
|
||||
"@babel/plugin-syntax-decorators": "7.7.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.7.4",
|
||||
"@babel/plugin-syntax-jsx": "7.7.4",
|
||||
"@babel/plugin-syntax-typescript": "7.7.4",
|
||||
"@cypress/webpack-preprocessor": "4.1.1",
|
||||
"@hot-loader/react-dom": "^16.11.0",
|
||||
"@types/chai": "4.2.7",
|
||||
"@types/dompurify": "^2.0.0",
|
||||
"@types/enzyme": "^3.10.4",
|
||||
"@babel/core": "^7.10.5",
|
||||
"@babel/plugin-syntax-decorators": "^7.10.4",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-syntax-jsx": "^7.10.4",
|
||||
"@babel/plugin-syntax-typescript": "^7.10.4",
|
||||
"@cypress/webpack-preprocessor": "^5.4.2",
|
||||
"@hot-loader/react-dom": "^16.12.0",
|
||||
"@types/chai": "^4.2.12",
|
||||
"@types/dompurify": "^2.0.2",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/enzyme-to-json": "^1.5.3",
|
||||
"@types/jest": "^24.0.23",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/json-pointer": "^1.0.30",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/lunr": "^2.3.2",
|
||||
"@types/lodash": "^4.14.158",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/mark.js": "^8.11.5",
|
||||
"@types/marked": "^0.7.2",
|
||||
"@types/prismjs": "^1.16.0",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/prismjs": "^1.16.1",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/react": "^16.9.16",
|
||||
"@types/react-dom": "^16.9.4",
|
||||
"@types/react-tabs": "^2.3.1",
|
||||
"@types/styled-components": "^4.4.1",
|
||||
"@types/tapable": "1.0.4",
|
||||
"@types/webpack": "^4.41.0",
|
||||
"@types/webpack-env": "^1.14.1",
|
||||
"@types/yargs": "^13.0.3",
|
||||
"babel-loader": "8.0.6",
|
||||
"babel-plugin-styled-components": "^1.10.6",
|
||||
"@types/react": "^16.9.43",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-tabs": "^2.3.2",
|
||||
"@types/styled-components": "^5.1.1",
|
||||
"@types/tapable": "^1.0.6",
|
||||
"@types/webpack": "^4.41.21",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"@types/yargs": "^15.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||
"@typescript-eslint/parser": "^3.7.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-styled-components": "^1.10.7",
|
||||
"beautify-benchmark": "^0.2.4",
|
||||
"bundlesize": "^0.18.0",
|
||||
"conventional-changelog-cli": "^2.0.28",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"core-js": "^3.5.0",
|
||||
"coveralls": "^3.0.9",
|
||||
"css-loader": "^3.3.0",
|
||||
"cypress": "~3.7.0",
|
||||
"conventional-changelog-cli": "^2.0.34",
|
||||
"copy-webpack-plugin": "^6.0.3",
|
||||
"core-js": "^3.6.5",
|
||||
"coveralls": "^3.1.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"cypress": "^4.11.0",
|
||||
"deploy-to-gh-pages": "^1.3.7",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.15.1",
|
||||
"enzyme-to-json": "^3.4.3",
|
||||
"fork-ts-checker-webpack-plugin": "3.1.1",
|
||||
"html-webpack-plugin": "^3.1.0",
|
||||
"jest": "^24.9.0",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.2",
|
||||
"enzyme-to-json": "^3.5.0",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-react": "^7.20.3",
|
||||
"fork-ts-checker-webpack-plugin": "^5.0.11",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"jest": "^26.1.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"lodash": "^4.17.15",
|
||||
"mobx": "^4.3.1",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier-eslint": "^9.0.1",
|
||||
"lodash": "^4.17.19",
|
||||
"mobx": "^5.15.4",
|
||||
"prettier": "^2.0.5",
|
||||
"raf": "^3.4.1",
|
||||
"react": "^16.12.0",
|
||||
"react-hot-loader": "^4.12.18",
|
||||
"react-dom": "^16.12.0",
|
||||
"rimraf": "^3.0.0",
|
||||
"shelljs": "^0.8.3",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"style-loader": "^1.0.1",
|
||||
"styled-components": "^4.4.1",
|
||||
"ts-jest": "24.2.0",
|
||||
"ts-loader": "6.2.1",
|
||||
"ts-node": "^8.5.4",
|
||||
"tslint": "^5.20.1",
|
||||
"tslint-react": "^4.1.0",
|
||||
"typescript": "^3.7.3",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"rimraf": "^3.0.2",
|
||||
"shelljs": "^0.8.4",
|
||||
"source-map-loader": "^1.0.1",
|
||||
"style-loader": "^1.2.1",
|
||||
"styled-components": "^5.1.1",
|
||||
"ts-jest": "^26.1.3",
|
||||
"ts-loader": "^8.0.1",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "^3.9.7",
|
||||
"unfetch": "^4.1.0",
|
||||
"url-polyfill": "^1.1.7",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack-cli": "^3.3.10",
|
||||
"webpack-dev-server": "^3.9.0",
|
||||
"webpack-node-externals": "^1.6.0",
|
||||
"workerize-loader": "^1.1.0",
|
||||
"url-polyfill": "^1.1.10",
|
||||
"webpack": "^4.44.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-node-externals": "^2.5.0",
|
||||
"workerize-loader": "^1.3.0",
|
||||
"yaml-js": "^0.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -133,28 +135,29 @@
|
|||
"styled-components": "^4.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@redocly/react-dropdown-aria": "^2.0.11",
|
||||
"@types/node": "^13.11.1",
|
||||
"classnames": "^2.2.6",
|
||||
"decko": "^1.2.0",
|
||||
"dompurify": "^2.0.7",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"dompurify": "^2.0.12",
|
||||
"eventemitter3": "^4.0.4",
|
||||
"json-pointer": "^0.6.0",
|
||||
"json-schema-ref-parser": "^6.1.0",
|
||||
"lunr": "2.3.8",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "^0.7.0",
|
||||
"memoize-one": "~5.1.1",
|
||||
"mobx-react": "^6.1.4",
|
||||
"openapi-sampler": "1.0.0-beta.15",
|
||||
"mobx-react": "^6.2.2",
|
||||
"openapi-sampler": "^1.0.0-beta.16",
|
||||
"perfect-scrollbar": "^1.4.0",
|
||||
"polished": "^3.4.2",
|
||||
"prismjs": "^1.17.1",
|
||||
"polished": "^3.6.5",
|
||||
"prismjs": "^1.20.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-dropdown": "^1.6.4",
|
||||
"react-tabs": "^3.0.0",
|
||||
"slugify": "^1.3.6",
|
||||
"react-tabs": "^3.1.1",
|
||||
"slugify": "^1.4.4",
|
||||
"stickyfill": "^1.1.1",
|
||||
"swagger2openapi": "^5.3.1",
|
||||
"tslib": "^1.10.0",
|
||||
"swagger2openapi": "^6.2.1",
|
||||
"tslib": "^2.0.0",
|
||||
"url-template": "^2.0.8"
|
||||
},
|
||||
"bundlesize": [
|
||||
|
|
|
@ -34,14 +34,14 @@ export class CopyButtonWrapper extends React.PureComponent<
|
|||
|
||||
renderCopyButton = () => {
|
||||
return (
|
||||
<span onClick={this.copy}>
|
||||
<button onClick={this.copy}>
|
||||
<Tooltip
|
||||
title={ClipboardService.isSupported() ? 'Copied' : 'Not supported in your browser'}
|
||||
open={this.state.tooltipShown}
|
||||
>
|
||||
Copy
|
||||
</Tooltip>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -82,9 +82,9 @@ export const PrismDiv = styled.div`
|
|||
}
|
||||
}
|
||||
|
||||
/* .property.token.string {
|
||||
.token.property.string {
|
||||
color: white;
|
||||
} */
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
|
|
|
@ -1,113 +1,135 @@
|
|||
import Dropdown from 'react-dropdown';
|
||||
import Dropdown from '@redocly/react-dropdown-aria';
|
||||
|
||||
import styled from '../styled-components';
|
||||
|
||||
export interface DropdownOption {
|
||||
label: string;
|
||||
idx: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface DropdownProps {
|
||||
options: DropdownOption[];
|
||||
value: DropdownOption;
|
||||
onChange: (val: DropdownOption) => void;
|
||||
value: string;
|
||||
onChange: (option: DropdownOption) => void;
|
||||
ariaLabel: string;
|
||||
}
|
||||
|
||||
export const StyledDropdown = styled(Dropdown)`
|
||||
min-width: 100px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: auto;
|
||||
font-family: ${props => props.theme.typography.headings.fontFamily};
|
||||
|
||||
.Dropdown-control {
|
||||
font-family: ${props => props.theme.typography.headings.fontFamily};
|
||||
position: relative;
|
||||
font-size: 0.929em;
|
||||
width: 100%;
|
||||
line-height: 1.5em;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
border-color: rgba(38, 50, 56, 0.5);
|
||||
color: #263238;
|
||||
outline: none;
|
||||
padding: 0.15em 1.5em 0.2em 0.5em;
|
||||
border-radius: 2px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
margin-top: 5px;
|
||||
background: white;
|
||||
|
||||
&& {
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
border-color: ${props => props.theme.colors.primary.main};
|
||||
color: ${props => props.theme.colors.primary.main};
|
||||
box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
.Dropdown-arrow {
|
||||
border-color: ${props => props.theme.colors.primary.main} transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 0.35em 0.35em 0;
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
right: 0.3em;
|
||||
top: 50%;
|
||||
margin-top: -0.125em;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.Dropdown-menu {
|
||||
position: absolute;
|
||||
margin-top: 2px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
z-index: 10;
|
||||
min-width: 100px;
|
||||
|
||||
outline: none;
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
border: 1px solid rgba(38, 50, 56, 0.5);
|
||||
vertical-align: bottom;
|
||||
padding: 2px 0px 2px 6px;
|
||||
position: relative;
|
||||
width: auto;
|
||||
background: white;
|
||||
border: 1px solid rgba(38, 50, 56, 0.2);
|
||||
box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);
|
||||
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.Dropdown-option {
|
||||
font-size: 0.9em;
|
||||
color: #263238;
|
||||
font-family: ${(props) => props.theme.typography.headings.fontFamily};
|
||||
font-size: 0.929em;
|
||||
line-height: 1.5em;
|
||||
cursor: pointer;
|
||||
padding: 0.4em;
|
||||
|
||||
&.is-selected {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
border: 1px solid ${(props) => props.theme.colors.primary.main};
|
||||
color: ${(props) => props.theme.colors.primary.main};
|
||||
box-shadow: 0px 0px 0px 1px ${(props) => props.theme.colors.primary.main};
|
||||
}
|
||||
.dropdown-selector {
|
||||
display: inline-flex;
|
||||
padding: 0;
|
||||
height: auto;
|
||||
padding-right: 20px;
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.dropdown-selector-value {
|
||||
font-family: ${(props) => props.theme.typography.headings.fontFamily};
|
||||
position: relative;
|
||||
font-size: 0.929em;
|
||||
width: 100%;
|
||||
line-height: 1;
|
||||
vertical-align: middle;
|
||||
color: #263238;
|
||||
left: 0;
|
||||
transition: color 0.25s ease, text-shadow 0.25s ease;
|
||||
}
|
||||
.dropdown-arrow {
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
border-color: ${(props) => props.theme.colors.primary.main} transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 0.35em 0.35em 0;
|
||||
width: 0;
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(38, 50, 56, 0.12);
|
||||
.dropdown-selector-content {
|
||||
position: absolute;
|
||||
margin-top: 2px;
|
||||
left: -2px;
|
||||
right: 0;
|
||||
|
||||
z-index: 10;
|
||||
min-width: 100px;
|
||||
|
||||
background: white;
|
||||
border: 1px solid rgba(38, 50, 56, 0.2);
|
||||
box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);
|
||||
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.dropdown-option {
|
||||
font-size: 0.9em;
|
||||
color: #263238;
|
||||
cursor: pointer;
|
||||
padding: 0.4em;
|
||||
background-color: #ffffff;
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(38, 50, 56, 0.12);
|
||||
}
|
||||
}
|
||||
input {
|
||||
cursor: pointer;
|
||||
height: 1px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SimpleDropdown = styled(StyledDropdown)`
|
||||
margin-left: 10px;
|
||||
text-transform: none;
|
||||
font-size: 0.969em;
|
||||
&& {
|
||||
margin-left: 10px;
|
||||
text-transform: none;
|
||||
font-size: 0.969em;
|
||||
|
||||
.Dropdown-control {
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
padding: 0 1.2em 0 0;
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
color: ${props => props.theme.colors.primary.main};
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
.dropdown-selector-value {
|
||||
color: ${(props) => props.theme.colors.primary.main};
|
||||
text-shadow: 0px 0px 0px ${(props) => props.theme.colors.primary.main};
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -62,7 +62,7 @@ export const PropertyNameCell = styled(PropertyCell)`
|
|||
vertical-align: top;
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
font-size: 0.929em;
|
||||
font-size: 13px;
|
||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||
|
||||
&.deprecated {
|
||||
|
|
|
@ -5,8 +5,19 @@ import { PropertyNameCell } from './fields-layout';
|
|||
import { ShelfIcon } from './shelfs';
|
||||
|
||||
export const ClickablePropertyNameCell = styled(PropertyNameCell)`
|
||||
cursor: pointer;
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font-size: 13px;
|
||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
&:focus {
|
||||
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
|
||||
}
|
||||
}
|
||||
${ShelfIcon} {
|
||||
height: ${({ theme }) => theme.schema.arrow.size};
|
||||
width: ${({ theme }) => theme.schema.arrow.size};
|
||||
|
@ -97,3 +108,14 @@ export const ConstraintItem = styled(FieldLabel)`
|
|||
}
|
||||
${extensionsHook('ConstraintItem')};
|
||||
`;
|
||||
|
||||
export const ToggleButton = styled.button`
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
color: ${({ theme }) => theme.colors.text.secondary};
|
||||
margin-left: ${({ theme }) => theme.spacing.unit}px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
outline-color: ${({ theme }) => theme.colors.text.secondary};
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
|
|
@ -14,6 +14,7 @@ export const linkifyMixin = className => css`
|
|||
line-height: 1;
|
||||
width: 20px;
|
||||
display: inline-block;
|
||||
outline: 0;
|
||||
}
|
||||
${className}:before {
|
||||
content: '';
|
||||
|
|
|
@ -80,6 +80,7 @@ export function PerfectScrollbarWrap(
|
|||
<div
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
overscrollBehavior: 'contain',
|
||||
msOverflowStyle: '-ms-autohiding-scrollbar',
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -5,13 +5,22 @@ export const SampleControls = styled.div`
|
|||
opacity: 0.4;
|
||||
transition: opacity 0.3s ease;
|
||||
text-align: right;
|
||||
|
||||
> span {
|
||||
display: inline-block;
|
||||
&:focus-within {
|
||||
opacity: 1;
|
||||
}
|
||||
> button {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
padding: 2px 10px;
|
||||
font-family: ${({ theme }) => theme.typography.fontFamily};
|
||||
font-size: ${({ theme }) => theme.typography.fontSize};
|
||||
line-height: ${({ theme }) => theme.typography.lineHeight};
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
|
||||
:hover {
|
||||
:hover,
|
||||
:focus {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import styled from '../styled-components';
|
||||
import { darken } from 'polished';
|
||||
|
||||
export const OneOfList = styled.ul`
|
||||
export const OneOfList = styled.div`
|
||||
margin: 0 0 3px 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
|
@ -15,7 +14,7 @@ export const OneOfLabel = styled.span`
|
|||
}
|
||||
`;
|
||||
|
||||
export const OneOfButton = styled.li<{ active: boolean }>`
|
||||
export const OneOfButton = styled.button<{ active: boolean }>`
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 5px;
|
||||
|
@ -23,12 +22,21 @@ export const OneOfButton = styled.li<{ active: boolean }>`
|
|||
cursor: pointer;
|
||||
border: 1px solid ${props => props.theme.colors.primary.main};
|
||||
padding: 2px 10px;
|
||||
line-height: 1.5em;
|
||||
outline: none;
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px ${props => props.theme.colors.primary.main};
|
||||
}
|
||||
|
||||
${props => {
|
||||
if (props.active) {
|
||||
return `
|
||||
color: white;
|
||||
background-color: ${props.theme.colors.primary.main};
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
background-color: ${darken(0.15, props.theme.colors.primary.main)};
|
||||
}
|
||||
`;
|
||||
} else {
|
||||
return `
|
||||
|
|
|
@ -26,6 +26,7 @@ class IntShelfIcon extends React.PureComponent<{
|
|||
x="0"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
y="0"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 " />
|
||||
</svg>
|
||||
|
@ -50,10 +51,17 @@ export const ShelfIcon = styled(IntShelfIcon)`
|
|||
|
||||
export const Badge = styled.span<{ type: string }>`
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
padding: 2px 8px;
|
||||
margin: 0;
|
||||
background-color: ${props => props.theme.colors[props.type].main};
|
||||
color: ${props => props.theme.colors[props.type].contrastText};
|
||||
font-size: ${props => props.theme.typography.code.fontSize};
|
||||
vertical-align: text-top;
|
||||
vertical-align: middle;
|
||||
line-height: 1.6;
|
||||
border-radius: 4px;
|
||||
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
|
||||
font-size: 12px;
|
||||
+ span[type] {
|
||||
margin-left: 4px;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -16,7 +16,7 @@ export const Tabs = styled(ReactTabs)`
|
|||
padding: 5px 10px;
|
||||
display: inline-block;
|
||||
|
||||
background-color: ${({ theme }) => theme.codeSample.backgroundColor};
|
||||
background-color: ${({ theme }) => theme.codeBlock.backgroundColor};
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.5);
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
|
@ -24,7 +24,7 @@ export const Tabs = styled(ReactTabs)`
|
|||
color: ${({ theme }) => darken(theme.colors.tonalOffset, theme.rightPanel.textColor)};
|
||||
margin: 0
|
||||
${({ theme }) => `${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px`};
|
||||
border: 1px solid ${({ theme }) => darken(0.05, theme.codeSample.backgroundColor)};
|
||||
border: 1px solid ${({ theme }) => darken(0.05, theme.codeBlock.backgroundColor)};
|
||||
border-radius: 5px;
|
||||
min-width: 60px;
|
||||
font-size: 0.9em;
|
||||
|
@ -33,6 +33,9 @@ export const Tabs = styled(ReactTabs)`
|
|||
&.react-tabs__tab--selected {
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
background: ${({ theme }) => theme.rightPanel.textColor};
|
||||
&:focus {
|
||||
outline: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:only-child {
|
||||
|
@ -58,7 +61,7 @@ export const Tabs = styled(ReactTabs)`
|
|||
}
|
||||
}
|
||||
> .react-tabs__tab-panel {
|
||||
background: ${({ theme }) => theme.codeSample.backgroundColor};
|
||||
background: ${({ theme }) => theme.codeBlock.backgroundColor};
|
||||
& > div,
|
||||
& > pre {
|
||||
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||
|
|
|
@ -81,7 +81,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
<p>
|
||||
Download OpenAPI specification:
|
||||
<DownloadButton
|
||||
download={downloadFilename}
|
||||
download={downloadFilename || true}
|
||||
target="_blank"
|
||||
href={downloadLink}
|
||||
onClick={this.handleDownloadClick}
|
||||
|
@ -100,7 +100,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
)) ||
|
||||
null}
|
||||
</StyledMarkdownBlock>
|
||||
<Markdown source={store.spec.info.description} />
|
||||
<Markdown source={store.spec.info.description} data-role="redoc-description" />
|
||||
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
|
||||
</MiddlePanel>
|
||||
</Row>
|
||||
|
|
35
src/components/CallbackSamples/CallbackReqSamples.tsx
Normal file
35
src/components/CallbackSamples/CallbackReqSamples.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import styled from '../../styled-components';
|
||||
import { DropdownProps } from '../../common-elements';
|
||||
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
|
||||
import { OperationModel } from '../../services/models';
|
||||
import { XPayloadSample } from '../../services/models/Operation';
|
||||
import { isPayloadSample } from '../../services';
|
||||
|
||||
export interface PayloadSampleProps {
|
||||
callback: OperationModel;
|
||||
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||
}
|
||||
|
||||
export class CallbackPayloadSample extends React.Component<PayloadSampleProps> {
|
||||
render() {
|
||||
const payloadSample = this.props.callback.codeSamples.find(sample =>
|
||||
isPayloadSample(sample),
|
||||
) as XPayloadSample | undefined;
|
||||
|
||||
if (!payloadSample) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PayloadSampleWrapper>
|
||||
<PayloadSamples content={payloadSample.requestBodyContent} />
|
||||
</PayloadSampleWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const PayloadSampleWrapper = styled.div`
|
||||
margin-top: 15px;
|
||||
`;
|
79
src/components/CallbackSamples/CallbackSamples.tsx
Normal file
79
src/components/CallbackSamples/CallbackSamples.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import styled from '../../styled-components';
|
||||
import { RightPanelHeader } from '../../common-elements';
|
||||
import { RedocNormalizedOptions } from '../../services';
|
||||
import { CallbackModel } from '../../services/models';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { GenericChildrenSwitcher } from '../GenericChildrenSwitcher/GenericChildrenSwitcher';
|
||||
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||
import { InvertedSimpleDropdown, MimeLabel } from '../PayloadSamples/styled.elements';
|
||||
import { CallbackPayloadSample } from './CallbackReqSamples';
|
||||
|
||||
export interface CallbackSamplesProps {
|
||||
callbacks: CallbackModel[];
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CallbackSamples extends React.Component<CallbackSamplesProps> {
|
||||
static contextType = OptionsContext;
|
||||
context: RedocNormalizedOptions;
|
||||
|
||||
private renderDropdown = props => {
|
||||
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { callbacks } = this.props;
|
||||
|
||||
if (!callbacks || callbacks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const operations = callbacks
|
||||
.map(callback => callback.operations.map(operation => operation))
|
||||
.reduce((a, b) => a.concat(b), []);
|
||||
|
||||
const hasSamples = operations.some(operation => operation.codeSamples.length > 0);
|
||||
|
||||
if (!hasSamples) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dropdownOptions = operations.map((callback, idx) => {
|
||||
return {
|
||||
value: `${callback.httpVerb.toUpperCase()}: ${callback.name}`,
|
||||
idx,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RightPanelHeader> Callback payload samples </RightPanelHeader>
|
||||
|
||||
<SamplesWrapper>
|
||||
<GenericChildrenSwitcher
|
||||
items={operations}
|
||||
renderDropdown={this.renderDropdown}
|
||||
label={'Callback'}
|
||||
options={dropdownOptions}
|
||||
>
|
||||
{callback => (
|
||||
<CallbackPayloadSample
|
||||
key="callbackPayloadSample"
|
||||
callback={callback}
|
||||
renderDropdown={this.renderDropdown}
|
||||
/>
|
||||
)}
|
||||
</GenericChildrenSwitcher>
|
||||
</SamplesWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const SamplesWrapper = styled.div`
|
||||
background: ${({ theme }) => theme.codeBlock.backgroundColor};
|
||||
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||
`;
|
46
src/components/Callbacks/CallbackDetails.tsx
Normal file
46
src/components/Callbacks/CallbackDetails.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { OperationModel } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { Endpoint } from '../Endpoint/Endpoint';
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { Extensions } from '../Fields/Extensions';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { Parameters } from '../Parameters/Parameters';
|
||||
import { ResponsesList } from '../Responses/ResponsesList';
|
||||
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
||||
import { CallbackDetailsWrap } from './styled.elements';
|
||||
|
||||
export interface CallbackDetailsProps {
|
||||
operation: OperationModel;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class CallbackDetails extends React.Component<CallbackDetailsProps> {
|
||||
render() {
|
||||
const { operation } = this.props;
|
||||
const { description, externalDocs } = operation;
|
||||
const hasDescription = !!(description || externalDocs);
|
||||
|
||||
return (
|
||||
<CallbackDetailsWrap>
|
||||
{hasDescription && (
|
||||
<Description>
|
||||
{description !== undefined && <Markdown source={description} />}
|
||||
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
|
||||
</Description>
|
||||
)}
|
||||
<Endpoint operation={this.props.operation} inverted={true} compact={true} />
|
||||
<Extensions extensions={operation.extensions} />
|
||||
<SecurityRequirements securities={operation.security} />
|
||||
<Parameters parameters={operation.parameters} body={operation.requestBody} />
|
||||
<ResponsesList responses={operation.responses} isCallback={operation.isCallback} />
|
||||
</CallbackDetailsWrap>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const Description = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.spacing.unit * 3}px;
|
||||
`;
|
30
src/components/Callbacks/CallbackOperation.tsx
Normal file
30
src/components/Callbacks/CallbackOperation.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { OperationModel } from '../../services/models';
|
||||
import { StyledCallbackTitle } from './styled.elements';
|
||||
import { CallbackDetails } from './CallbackDetails';
|
||||
|
||||
@observer
|
||||
export class CallbackOperation extends React.Component<{ callbackOperation: OperationModel }> {
|
||||
toggle = () => {
|
||||
this.props.callbackOperation.toggle();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, expanded, httpVerb, deprecated } = this.props.callbackOperation;
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledCallbackTitle
|
||||
onClick={this.toggle}
|
||||
name={name}
|
||||
opened={expanded}
|
||||
httpVerb={httpVerb}
|
||||
deprecated={deprecated}
|
||||
/>
|
||||
{expanded && <CallbackDetails operation={this.props.callbackOperation} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
57
src/components/Callbacks/CallbackTitle.tsx
Normal file
57
src/components/Callbacks/CallbackTitle.tsx
Normal file
|
@ -0,0 +1,57 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { darken } from 'polished';
|
||||
import { ShelfIcon } from '../../common-elements';
|
||||
import { OperationBadge } from '../SideMenu/styled.elements';
|
||||
import { shortenHTTPVerb } from '../../utils/openapi';
|
||||
import styled from '../../styled-components';
|
||||
import { Badge } from '../../common-elements/';
|
||||
import { l } from '../../services/Labels';
|
||||
|
||||
export interface CallbackTitleProps {
|
||||
name: string;
|
||||
opened?: boolean;
|
||||
httpVerb: string;
|
||||
deprecated?: boolean;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export class CallbackTitle extends React.PureComponent<CallbackTitleProps> {
|
||||
render() {
|
||||
const { name, opened, className, onClick, httpVerb, deprecated } = this.props;
|
||||
|
||||
return (
|
||||
<CallbackTitleWrapper className={className} onClick={onClick || undefined}>
|
||||
<OperationBadgeStyled type={httpVerb}>{shortenHTTPVerb(httpVerb)}</OperationBadgeStyled>
|
||||
<ShelfIcon size={'1.5em'} direction={opened ? 'down' : 'right'} float={'left'} />
|
||||
<CallbackName deprecated={deprecated}>{name}</CallbackName>
|
||||
{deprecated ? <Badge type="warning"> {l('deprecated')} </Badge> : null}
|
||||
</CallbackTitleWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const CallbackTitleWrapper = styled.button`
|
||||
border: 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
& > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
${ShelfIcon} {
|
||||
polygon {
|
||||
fill: ${({ theme }) => darken(theme.colors.tonalOffset, theme.colors.gray[100])};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CallbackName = styled.span<{ deprecated?: boolean }>`
|
||||
text-decoration: ${(props) => (props.deprecated ? 'line-through' : 'none')};
|
||||
margin-right: 8px;
|
||||
`;
|
||||
|
||||
const OperationBadgeStyled = styled(OperationBadge)`
|
||||
margin: 0px 5px 0px 0px;
|
||||
`;
|
40
src/components/Callbacks/CallbacksList.tsx
Normal file
40
src/components/Callbacks/CallbacksList.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { CallbackModel } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { CallbackOperation } from './CallbackOperation';
|
||||
|
||||
export interface CallbacksListProps {
|
||||
callbacks: CallbackModel[];
|
||||
}
|
||||
|
||||
export class CallbacksList extends React.PureComponent<CallbacksListProps> {
|
||||
render() {
|
||||
const { callbacks } = this.props;
|
||||
|
||||
if (!callbacks || callbacks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CallbacksHeader> Callbacks </CallbacksHeader>
|
||||
{callbacks.map(callback => {
|
||||
return callback.operations.map((operation, index) => {
|
||||
return (
|
||||
<CallbackOperation key={`${callback.name}_${index}`} callbackOperation={operation} />
|
||||
);
|
||||
});
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const CallbacksHeader = styled.h3`
|
||||
font-size: 1.3em;
|
||||
padding: 0.2em 0;
|
||||
margin: 3em 0 1.1em;
|
||||
color: ${({ theme }) => theme.colors.text.primary};
|
||||
font-weight: normal;
|
||||
`;
|
3
src/components/Callbacks/index.ts
Normal file
3
src/components/Callbacks/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export * from './CallbackOperation';
|
||||
export * from './CallbackTitle';
|
||||
export * from './CallbacksList';
|
20
src/components/Callbacks/styled.elements.ts
Normal file
20
src/components/Callbacks/styled.elements.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import styled from '../../styled-components';
|
||||
import { CallbackTitle } from './CallbackTitle';
|
||||
import { darken } from 'polished';
|
||||
|
||||
export const StyledCallbackTitle = styled(CallbackTitle)`
|
||||
padding: 10px;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.5em;
|
||||
background-color: ${({ theme }) => theme.colors.gray[100]};
|
||||
cursor: pointer;
|
||||
outline-color: ${({ theme }) => darken(theme.colors.tonalOffset, theme.colors.gray[100])};
|
||||
`;
|
||||
|
||||
export const CallbackDetailsWrap = styled.div`
|
||||
padding: 10px 25px;
|
||||
background-color: ${({ theme }) => theme.colors.gray[50]};
|
||||
margin-bottom: 5px;
|
||||
margin-top: 5px;
|
||||
`;
|
|
@ -3,7 +3,6 @@ import * as React from 'react';
|
|||
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
||||
|
||||
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||
import { ContentItemModel } from '../../services/MenuBuilder';
|
||||
import { GroupModel, OperationModel } from '../../services/models';
|
||||
|
@ -24,6 +23,9 @@ export class ContentItems extends React.Component<{
|
|||
return item.type == "tag" ? item.name == `${activeSelection}` : true;
|
||||
});
|
||||
return filteredItems.map(item => <ContentItem item={item} key={item.id} />);
|
||||
// return items.map(item => {
|
||||
// return <ContentItem key={item.id} item={item} />;
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export interface DropdownOrLabelProps extends DropdownProps {
|
|||
export function DropdownOrLabel(props: DropdownOrLabelProps): JSX.Element {
|
||||
const { Label = MimeLabel, Dropdown = SimpleDropdown } = props;
|
||||
if (props.options.length === 1) {
|
||||
return <Label>{props.options[0].label}</Label>;
|
||||
return <Label>{props.options[0].value}</Label>;
|
||||
}
|
||||
return <Dropdown {...props} />;
|
||||
return <Dropdown {...props} searchable={false} />;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ export interface EndpointProps {
|
|||
|
||||
hideHostname?: boolean;
|
||||
inverted?: boolean;
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export interface EndpointState {
|
||||
|
@ -49,7 +50,9 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
|
|||
{options => (
|
||||
<OperationEndpointWrap>
|
||||
<EndpointInfo onClick={this.toggle} expanded={expanded} inverted={inverted}>
|
||||
<HttpVerb type={operation.httpVerb}> {operation.httpVerb}</HttpVerb>{' '}
|
||||
<HttpVerb type={operation.httpVerb} compact={this.props.compact}>
|
||||
{operation.httpVerb}
|
||||
</HttpVerb>
|
||||
<ServerRelativeURL>{operation.path}</ServerRelativeURL>
|
||||
<ShelfIcon
|
||||
float={'right'}
|
||||
|
@ -59,7 +62,7 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
|
|||
style={{ marginRight: '-25px' }}
|
||||
/>
|
||||
</EndpointInfo>
|
||||
<ServersOverlay expanded={expanded}>
|
||||
<ServersOverlay expanded={expanded} aria-hidden={!expanded}>
|
||||
{operation.servers.map(server => {
|
||||
const normalizedUrl = options.expandDefaultServerVariables
|
||||
? expandDefaultServerVariables(server.url, server.variables)
|
||||
|
|
|
@ -14,11 +14,16 @@ export const ServerRelativeURL = styled.span`
|
|||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
export const EndpointInfo = styled.div<{ expanded?: boolean; inverted?: boolean }>`
|
||||
export const EndpointInfo = styled.button<{ expanded?: boolean; inverted?: boolean }>`
|
||||
outline: 0;
|
||||
color: inherit;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
padding: 10px 30px 10px ${props => (props.inverted ? '10px' : '20px')};
|
||||
border-radius: ${props => (props.inverted ? '0' : '4px 4px 0 0')};
|
||||
background-color: ${props =>
|
||||
props.inverted ? 'transparent' : props.theme.codeSample.backgroundColor};
|
||||
props.inverted ? 'transparent' : props.theme.codeBlock.backgroundColor};
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
|
@ -32,16 +37,19 @@ export const EndpointInfo = styled.div<{ expanded?: boolean; inverted?: boolean
|
|||
.${ServerRelativeURL} {
|
||||
color: ${props => (props.inverted ? props.theme.colors.text.primary : '#ffffff')}
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: inset 0 2px 2px rgba(0, 0, 0, 0.45), 0 2px 0 rgba(128, 128, 128, 0.25);
|
||||
}
|
||||
`;
|
||||
|
||||
export const HttpVerb = styled.span.attrs((props: { type: string }) => ({
|
||||
export const HttpVerb = styled.span.attrs((props: { type: string; compact?: boolean }) => ({
|
||||
className: `http-verb ${props.type}`,
|
||||
}))<{ type: string }>`
|
||||
font-size: 0.929em;
|
||||
line-height: 20px;
|
||||
background-color: ${(props: any) => props.theme.colors.http[props.type] || '#999999'};
|
||||
}))<{ type: string; compact?: boolean }>`
|
||||
font-size: ${props => (props.compact ? '0.8em' : '0.929em')};
|
||||
line-height: ${props => (props.compact ? '18px' : '20px')};
|
||||
background-color: ${props => props.theme.colors.http[props.type] || '#999999'};
|
||||
color: #ffffff;
|
||||
padding: 3px 10px;
|
||||
padding: ${props => (props.compact ? '2px 8px' : '3px 10px')};
|
||||
text-transform: uppercase;
|
||||
font-family: ${props => props.theme.typography.headings.fontFamily};
|
||||
margin: 0;
|
||||
|
@ -59,8 +67,8 @@ export const ServersOverlay = styled.div<{ expanded: boolean }>`
|
|||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
transition: all 0.25s ease;
|
||||
|
||||
${props => (props.expanded ? '' : 'transform: translateY(-50%) scaleY(0);')}
|
||||
visibility: hidden;
|
||||
${props => (props.expanded ? 'visibility: visible;' : 'transform: translateY(-50%) scaleY(0);')}
|
||||
`;
|
||||
|
||||
export const ServerItem = styled.div`
|
||||
|
|
|
@ -6,7 +6,10 @@ const ErrorWrapper = styled.div`
|
|||
color: red;
|
||||
`;
|
||||
|
||||
export class ErrorBoundary extends React.Component<{}, { error?: Error }> {
|
||||
export class ErrorBoundary extends React.Component<
|
||||
React.PropsWithChildren<unknown>,
|
||||
{ error?: Error }
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = { error: undefined };
|
||||
|
|
|
@ -3,28 +3,62 @@ import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
|||
|
||||
import { l } from '../../services/Labels';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import styled from '../../styled-components';
|
||||
import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
|
||||
|
||||
export interface EnumValuesProps {
|
||||
values: string[];
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
||||
export interface EnumValuesState {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesState> {
|
||||
state: EnumValuesState = {
|
||||
collapsed: true,
|
||||
};
|
||||
|
||||
static contextType = OptionsContext;
|
||||
|
||||
private toggle() {
|
||||
this.setState({ collapsed: !this.state.collapsed });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { values, type } = this.props;
|
||||
const { enumSkipQuotes } = this.context;
|
||||
const { collapsed } = this.state;
|
||||
|
||||
// TODO: provide context interface in more elegant way
|
||||
const { enumSkipQuotes, maxDisplayedEnumValues } = this.context as RedocRawOptions;
|
||||
|
||||
if (!values.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayedItems =
|
||||
this.state.collapsed && maxDisplayedEnumValues
|
||||
? values.slice(0, maxDisplayedEnumValues)
|
||||
: values;
|
||||
|
||||
const showToggleButton = maxDisplayedEnumValues
|
||||
? values.length > maxDisplayedEnumValues
|
||||
: false;
|
||||
|
||||
const toggleButtonText = maxDisplayedEnumValues
|
||||
? collapsed
|
||||
? `… ${values.length - maxDisplayedEnumValues} more`
|
||||
: 'Hide'
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FieldLabel>
|
||||
{type === 'array' ? l('enumArray') : ''}{' '}
|
||||
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||
</FieldLabel>{' '}
|
||||
{values.map((value, idx) => {
|
||||
{displayedItems.map((value, idx) => {
|
||||
const exampleValue = enumSkipQuotes ? value : JSON.stringify(value);
|
||||
return (
|
||||
<React.Fragment key={idx}>
|
||||
|
@ -32,7 +66,25 @@ export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
|||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
{showToggleButton ? (
|
||||
<ToggleButton
|
||||
onClick={() => {
|
||||
this.toggle();
|
||||
}}
|
||||
>
|
||||
{toggleButtonText}
|
||||
</ToggleButton>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ToggleButton = styled.span`
|
||||
color: ${props => props.theme.colors.primary.main};
|
||||
vertical-align: middle;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
|
|
@ -23,6 +23,7 @@ export interface FieldProps extends SchemaOptions {
|
|||
showExamples?: boolean;
|
||||
|
||||
field: FieldModel;
|
||||
expandByDefault?: boolean;
|
||||
|
||||
renderDiscriminatorSwitch?: (opts: FieldProps) => JSX.Element;
|
||||
}
|
||||
|
@ -30,23 +31,42 @@ export interface FieldProps extends SchemaOptions {
|
|||
@observer
|
||||
export class Field extends React.Component<FieldProps> {
|
||||
toggle = () => {
|
||||
this.props.field.toggle();
|
||||
if (this.props.field.expanded === undefined && this.props.expandByDefault) {
|
||||
this.props.field.expanded = false;
|
||||
} else {
|
||||
this.props.field.toggle();
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyPress = e => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.toggle();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className, field, isLast } = this.props;
|
||||
const { name, expanded, deprecated, required, kind } = field;
|
||||
const { className, field, isLast, expandByDefault } = this.props;
|
||||
const { name, deprecated, required, kind } = field;
|
||||
const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular;
|
||||
|
||||
const expanded = field.expanded === undefined ? expandByDefault : field.expanded;
|
||||
|
||||
const paramName = withSubSchema ? (
|
||||
<ClickablePropertyNameCell
|
||||
onClick={this.toggle}
|
||||
className={deprecated ? 'deprecated' : ''}
|
||||
kind={kind}
|
||||
title={name}
|
||||
>
|
||||
<PropertyBullet />
|
||||
{name}
|
||||
<ShelfIcon direction={expanded ? 'down' : 'right'} />
|
||||
<button
|
||||
onClick={this.toggle}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
aria-label="expand properties"
|
||||
>
|
||||
{name}
|
||||
<ShelfIcon direction={expanded ? 'down' : 'right'} />
|
||||
</button>
|
||||
{required && <RequiredLabel> required </RequiredLabel>}
|
||||
</ClickablePropertyNameCell>
|
||||
) : (
|
||||
|
@ -65,7 +85,7 @@ export class Field extends React.Component<FieldProps> {
|
|||
<FieldDetails {...this.props} />
|
||||
</PropertyDetailsCell>
|
||||
</tr>
|
||||
{field.expanded && withSubSchema && (
|
||||
{expanded && withSubSchema && (
|
||||
<tr key={field.name + 'inner'}>
|
||||
<PropertyCellWithInner colSpan={2}>
|
||||
<InnerPropertiesWrap>
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
TypeName,
|
||||
TypePrefix,
|
||||
TypeTitle,
|
||||
ToggleButton,
|
||||
} from '../../common-elements/fields';
|
||||
import { serializeParameterValue } from '../../utils/openapi';
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
|
@ -23,10 +24,24 @@ import { Badge } from '../../common-elements/';
|
|||
import { l } from '../../services/Labels';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
|
||||
export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||
const MAX_PATTERN_LENGTH = 45;
|
||||
|
||||
export class FieldDetails extends React.PureComponent<FieldProps, { patternShown: boolean }> {
|
||||
state = {
|
||||
patternShown: false,
|
||||
};
|
||||
|
||||
static contextType = OptionsContext;
|
||||
|
||||
togglePattern = () => {
|
||||
this.setState({
|
||||
patternShown: !this.state.patternShown,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
||||
const { patternShown } = this.state;
|
||||
const { enumSkipQuotes, hideSchemaTitles } = this.context;
|
||||
|
||||
const { schema, description, example, deprecated } = field;
|
||||
|
@ -62,7 +77,20 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
|
|||
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||
<ConstraintsView constraints={schema.constraints} />
|
||||
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>}
|
||||
{schema.pattern && <PatternLabel> {schema.pattern} </PatternLabel>}
|
||||
{schema.pattern && (
|
||||
<>
|
||||
<PatternLabel>
|
||||
{patternShown || schema.pattern.length < MAX_PATTERN_LENGTH
|
||||
? schema.pattern
|
||||
: `${schema.pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
|
||||
</PatternLabel>
|
||||
{schema.pattern.length > MAX_PATTERN_LENGTH && (
|
||||
<ToggleButton onClick={this.togglePattern}>
|
||||
{patternShown ? 'Hide pattern' : 'Show pattern'}
|
||||
</ToggleButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
||||
</div>
|
||||
{deprecated && (
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { DropdownProps, DropdownOption } from '../../common-elements/dropdown';
|
||||
import { DropdownLabel, DropdownWrapper } from '../PayloadSamples/styled.elements';
|
||||
|
||||
export interface GenericChildrenSwitcherProps<T> {
|
||||
items?: T[];
|
||||
options: DropdownOption[];
|
||||
label?: string;
|
||||
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||
children: (activeItem: T) => JSX.Element;
|
||||
}
|
||||
|
||||
export interface GenericChildrenSwitcherState {
|
||||
activeItemIdx: number;
|
||||
}
|
||||
/**
|
||||
* TODO: Refactor this component:
|
||||
* Implement rendering dropdown/label directly in this component
|
||||
* Accept as a parameter mapper-function for building dropdown option labels
|
||||
*/
|
||||
@observer
|
||||
export class GenericChildrenSwitcher<T> extends React.Component<
|
||||
GenericChildrenSwitcherProps<T>,
|
||||
GenericChildrenSwitcherState
|
||||
> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeItemIdx: 0,
|
||||
};
|
||||
}
|
||||
|
||||
switchItem = ({ idx }) => {
|
||||
if (this.props.items) {
|
||||
this.setState({
|
||||
activeItemIdx: idx,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { items } = this.props;
|
||||
|
||||
if (!items || !items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Wrapper = ({ children }) =>
|
||||
this.props.label ? (
|
||||
<DropdownWrapper>
|
||||
<DropdownLabel>{this.props.label}</DropdownLabel>
|
||||
{children}
|
||||
</DropdownWrapper>
|
||||
) : (
|
||||
children
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper>
|
||||
{this.props.renderDropdown({
|
||||
value: this.props.options[this.state.activeItemIdx].value,
|
||||
options: this.props.options,
|
||||
onChange: this.switchItem,
|
||||
ariaLabel: this.props.label || 'Callback',
|
||||
})}
|
||||
</Wrapper>
|
||||
|
||||
{this.props.children(items[this.state.activeItemIdx])}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -30,8 +30,8 @@ class Json extends React.PureComponent<JsonProps> {
|
|||
<JsonViewerWrap>
|
||||
<SampleControls>
|
||||
{renderCopyButton()}
|
||||
<span onClick={this.expandAll}> Expand all </span>
|
||||
<span onClick={this.collapseAll}> Collapse all </span>
|
||||
<button onClick={this.expandAll}> Expand all </button>
|
||||
<button onClick={this.collapseAll}> Collapse all </button>
|
||||
</SampleControls>
|
||||
<OptionsContext.Consumer>
|
||||
{options => (
|
||||
|
@ -57,11 +57,10 @@ class Json extends React.PureComponent<JsonProps> {
|
|||
|
||||
collapseAll = () => {
|
||||
const elements = this.node.getElementsByClassName('collapsible');
|
||||
for (const expanded of Array.prototype.slice.call(elements)) {
|
||||
// const collapsed = elements[i];
|
||||
if ((expanded.parentNode as Element)!.classList.contains('redoc-json')) {
|
||||
continue;
|
||||
}
|
||||
// skip first item to avoid collapsing whole object/array
|
||||
const elementsArr = Array.prototype.slice.call(elements, 1);
|
||||
|
||||
for (const expanded of elementsArr) {
|
||||
(expanded.parentNode as Element)!.classList.add('collapsed');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,11 +17,12 @@ export type MarkdownProps = BaseMarkdownProps &
|
|||
StylingMarkdownProps & {
|
||||
source: string;
|
||||
className?: string;
|
||||
'data-role'?: string;
|
||||
};
|
||||
|
||||
export class Markdown extends React.Component<MarkdownProps> {
|
||||
render() {
|
||||
const { source, inline, compact, className } = this.props;
|
||||
const { source, inline, compact, className, 'data-role': dataRole } = this.props;
|
||||
const renderer = new MarkdownRenderer();
|
||||
return (
|
||||
<SanitizedMarkdownHTML
|
||||
|
@ -29,6 +30,7 @@ export class Markdown extends React.Component<MarkdownProps> {
|
|||
inline={inline}
|
||||
compact={compact}
|
||||
className={className}
|
||||
data-role={dataRole}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span');
|
|||
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
|
||||
|
||||
export function SanitizedMarkdownHTML(
|
||||
props: StylingMarkdownProps & { html: string; className?: string },
|
||||
props: StylingMarkdownProps & { html: string; className?: string; 'data-role'?: string },
|
||||
) {
|
||||
const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock;
|
||||
|
||||
|
@ -22,6 +22,7 @@ export function SanitizedMarkdownHTML(
|
|||
dangerouslySetInnerHTML={{
|
||||
__html: sanitize(options.untrustedSpec, props.html),
|
||||
}}
|
||||
data-role={props['data-role']}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -19,11 +19,13 @@ export const linksCss = css`
|
|||
}
|
||||
`;
|
||||
|
||||
export const StyledMarkdownBlock = styled(PrismDiv as StyledComponent<
|
||||
'div',
|
||||
ResolvedThemeInterface,
|
||||
{ compact?: boolean; inline?: boolean }
|
||||
>)`
|
||||
export const StyledMarkdownBlock = styled(
|
||||
PrismDiv as StyledComponent<
|
||||
'div',
|
||||
ResolvedThemeInterface,
|
||||
{ compact?: boolean; inline?: boolean }
|
||||
>,
|
||||
)`
|
||||
|
||||
font-family: ${props => props.theme.typography.fontFamily};
|
||||
font-weight: ${props => props.theme.typography.fontWeightRegular};
|
||||
|
@ -80,7 +82,7 @@ export const StyledMarkdownBlock = styled(PrismDiv as StyledComponent<
|
|||
pre {
|
||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||
white-space:${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
|
||||
background-color: #263238;
|
||||
background-color: ${({ theme }) => theme.codeBlock.backgroundColor};
|
||||
color: white;
|
||||
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||
overflow-x: auto;
|
||||
|
|
|
@ -20,9 +20,9 @@ export interface MediaTypesSwitchProps {
|
|||
|
||||
@observer
|
||||
export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
|
||||
switchMedia = ({ value }) => {
|
||||
switchMedia = ({ idx }) => {
|
||||
if (this.props.content) {
|
||||
this.props.content.activate(parseInt(value, 10));
|
||||
this.props.content.activate(idx);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -35,8 +35,8 @@ export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
|
|||
|
||||
const options = content.mediaTypes.map((mime, idx) => {
|
||||
return {
|
||||
label: mime.name,
|
||||
value: idx.toString(),
|
||||
value: mime.name,
|
||||
idx,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -54,9 +54,10 @@ export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
|
|||
<>
|
||||
<Wrapper>
|
||||
{this.props.renderDropdown({
|
||||
value: options[activeMimeIdx],
|
||||
value: options[activeMimeIdx].value,
|
||||
options,
|
||||
onChange: this.switchMedia,
|
||||
ariaLabel: 'Content type',
|
||||
})}
|
||||
</Wrapper>
|
||||
{this.props.children(content.active)}
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
import * as React from 'react';
|
||||
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elements';
|
||||
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
|
||||
import { ShareLink } from '../../common-elements/linkify';
|
||||
import { OperationModel } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { CallbacksList } from '../Callbacks';
|
||||
import { CallbackSamples } from '../CallbackSamples/CallbackSamples';
|
||||
import { Endpoint } from '../Endpoint/Endpoint';
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { Extensions } from '../Fields/Extensions';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { Parameters } from '../Parameters/Parameters';
|
||||
import { RequestSamples } from '../RequestSamples/RequestSamples';
|
||||
import { ResponsesList } from '../Responses/ResponsesList';
|
||||
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
|
||||
|
||||
import { OperationModel as OperationType } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
import { Extensions } from '../Fields/Extensions';
|
||||
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
||||
|
||||
const OperationRow = styled(Row)`
|
||||
backface-visibility: hidden;
|
||||
contain: content;
|
||||
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
|
@ -32,7 +29,7 @@ const Description = styled.div`
|
|||
`;
|
||||
|
||||
export interface OperationProps {
|
||||
operation: OperationType;
|
||||
operation: OperationModel;
|
||||
}
|
||||
|
||||
@observer
|
||||
|
@ -40,19 +37,22 @@ export class Operation extends React.Component<OperationProps> {
|
|||
render() {
|
||||
const { operation } = this.props;
|
||||
|
||||
const { name: summary, description, deprecated, externalDocs } = operation;
|
||||
const { name: summary, description, deprecated, externalDocs, isWebhook } = operation;
|
||||
const hasDescription = !!(description || externalDocs);
|
||||
|
||||
return (
|
||||
<OptionsContext.Consumer>
|
||||
{options => (
|
||||
{(options) => (
|
||||
<OperationRow>
|
||||
<MiddlePanel>
|
||||
<H2>
|
||||
<ShareLink to={operation.id} />
|
||||
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
||||
{isWebhook && <Badge type="primary"> Webhook </Badge>}
|
||||
</H2>
|
||||
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
|
||||
{options.pathInMiddlePanel && !isWebhook && (
|
||||
<Endpoint operation={operation} inverted={true} />
|
||||
)}
|
||||
{hasDescription && (
|
||||
<Description>
|
||||
{description !== undefined && <Markdown source={description} />}
|
||||
|
@ -63,11 +63,13 @@ export class Operation extends React.Component<OperationProps> {
|
|||
<SecurityRequirements securities={operation.security} />
|
||||
<Parameters parameters={operation.parameters} body={operation.requestBody} />
|
||||
<ResponsesList responses={operation.responses} />
|
||||
<CallbacksList callbacks={operation.callbacks} />
|
||||
</MiddlePanel>
|
||||
<DarkRightPanel>
|
||||
{!options.pathInMiddlePanel && <Endpoint operation={operation} />}
|
||||
{!options.pathInMiddlePanel && !isWebhook && <Endpoint operation={operation} />}
|
||||
<RequestSamples operation={operation} />
|
||||
<ResponseSamples operation={operation} />
|
||||
<CallbackSamples callbacks={operation.callbacks} />
|
||||
</DarkRightPanel>
|
||||
</OperationRow>
|
||||
)}
|
||||
|
|
|
@ -26,7 +26,7 @@ export interface ParametersProps {
|
|||
const PARAM_PLACES = ['path', 'query', 'cookie', 'header'];
|
||||
|
||||
export class Parameters extends React.PureComponent<ParametersProps> {
|
||||
orderParams(params: FieldModel[]): Dict<FieldModel[]> {
|
||||
orderParams(params: FieldModel[]): Record<string, FieldModel[]> {
|
||||
const res = {};
|
||||
params.forEach(param => {
|
||||
safePush(res, param.in, param);
|
||||
|
@ -67,7 +67,7 @@ function DropdownWithinHeader(props) {
|
|||
);
|
||||
}
|
||||
|
||||
function BodyContent(props: { content: MediaContentModel; description?: string }): JSX.Element {
|
||||
export function BodyContent(props: { content: MediaContentModel; description?: string }): JSX.Element {
|
||||
const { content, description } = props;
|
||||
return (
|
||||
<MediaTypesSwitch content={content} renderDropdown={DropdownWithinHeader}>
|
||||
|
|
|
@ -29,7 +29,12 @@ export function ExternalExample({ example, mimeType }: ExampleProps) {
|
|||
return (
|
||||
<StyledPre>
|
||||
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}
|
||||
</a>
|
||||
</StyledPre>
|
||||
|
|
|
@ -21,9 +21,9 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps, Media
|
|||
state = {
|
||||
activeIdx: 0,
|
||||
};
|
||||
switchMedia = ({ value }) => {
|
||||
switchMedia = ({ idx }) => {
|
||||
this.setState({
|
||||
activeIdx: parseInt(value, 10),
|
||||
activeIdx: idx,
|
||||
});
|
||||
};
|
||||
render() {
|
||||
|
@ -41,8 +41,8 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps, Media
|
|||
if (examplesNames.length > 1) {
|
||||
const options = examplesNames.map((name, idx) => {
|
||||
return {
|
||||
label: examples[name].summary || name,
|
||||
value: idx.toString(),
|
||||
value: examples[name].summary || name,
|
||||
idx,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -54,9 +54,10 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps, Media
|
|||
<DropdownWrapper>
|
||||
<DropdownLabel>Example</DropdownLabel>
|
||||
{this.props.renderDropdown({
|
||||
value: options[activeIdx],
|
||||
value: options[activeIdx].value,
|
||||
options,
|
||||
onChange: this.switchMedia,
|
||||
ariaLabel: 'Example',
|
||||
})}
|
||||
</DropdownWrapper>
|
||||
<div>
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
// @ts-ignore
|
||||
import Dropdown from 'react-dropdown';
|
||||
|
||||
import { transparentize } from 'polished';
|
||||
import styled from '../../styled-components';
|
||||
|
||||
import { StyledDropdown } from '../../common-elements';
|
||||
|
||||
export const MimeLabel = styled.div`
|
||||
padding: 12px;
|
||||
padding: 0.9em;
|
||||
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||
margin: 0 0 10px 0;
|
||||
display: block;
|
||||
font-family: ${({ theme }) => theme.typography.headings.fontFamily};
|
||||
font-size: 0.929em;
|
||||
line-height: 1.5em;
|
||||
`;
|
||||
|
||||
export const DropdownLabel = styled.span`
|
||||
|
@ -29,31 +28,45 @@ export const DropdownWrapper = styled.div`
|
|||
`;
|
||||
|
||||
export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
||||
margin-left: 10px;
|
||||
text-transform: none;
|
||||
font-size: 0.929em;
|
||||
margin: 0 0 10px 0;
|
||||
display: block;
|
||||
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||
.Dropdown-control {
|
||||
margin-top: 0;
|
||||
}
|
||||
.Dropdown-control,
|
||||
.Dropdown-control:hover {
|
||||
&& {
|
||||
margin-left: 10px;
|
||||
text-transform: none;
|
||||
font-size: 0.929em;
|
||||
margin: 0 0 10px 0;
|
||||
display: block;
|
||||
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
padding: 0.9em 1.6em 0.9em 0.9em;
|
||||
background: transparent;
|
||||
color: ${({ theme }) => theme.rightPanel.textColor};
|
||||
box-shadow: none;
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
&:focus-within {
|
||||
background-color: ${({ theme }) => transparentize(0.3, theme.rightPanel.backgroundColor)};
|
||||
}
|
||||
|
||||
.Dropdown-arrow {
|
||||
.dropdown-arrow {
|
||||
border-top-color: ${({ theme }) => theme.rightPanel.textColor};
|
||||
}
|
||||
}
|
||||
.Dropdown-menu {
|
||||
margin: 0;
|
||||
margin-top: 2px;
|
||||
.dropdown-selector-value {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
color: ${({ theme }) => theme.rightPanel.textColor};
|
||||
}
|
||||
|
||||
.dropdown-selector-content {
|
||||
margin: 0;
|
||||
margin-top: 2px;
|
||||
.dropdown-option {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ export const ApiContentWrap = styled.div`
|
|||
z-index: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: calc(100% - ${props => props.theme.menu.width});
|
||||
width: calc(100% - ${props => props.theme.sidebar.width});
|
||||
${media.lessThan('small', true)`
|
||||
width: 100%;
|
||||
`};
|
||||
|
@ -46,7 +46,7 @@ export const BackgroundStub = styled.div`
|
|||
width: ${({ theme }) => {
|
||||
if (theme.rightPanel.width.endsWith('%')) {
|
||||
const percents = parseInt(theme.rightPanel.width, 10);
|
||||
return `calc((100% - ${theme.menu.width}) * ${percents / 100})`;
|
||||
return `calc((100% - ${theme.sidebar.width}) * ${percents / 100})`;
|
||||
} else {
|
||||
return theme.rightPanel.width;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { Code } from './styled.elements';
|
||||
import { ShelfIcon } from '../../common-elements';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
|
||||
|
@ -17,7 +18,12 @@ export class ResponseTitle extends React.PureComponent<ResponseTitleProps> {
|
|||
render() {
|
||||
const { title, type, empty, code, opened, className, onClick } = this.props;
|
||||
return (
|
||||
<div className={className} onClick={(!empty && onClick) || undefined}>
|
||||
<button
|
||||
className={className}
|
||||
onClick={(!empty && onClick) || undefined}
|
||||
aria-expanded={opened}
|
||||
disabled={empty}
|
||||
>
|
||||
{!empty && (
|
||||
<ShelfIcon
|
||||
size={'1.5em'}
|
||||
|
@ -26,9 +32,9 @@ export class ResponseTitle extends React.PureComponent<ResponseTitleProps> {
|
|||
float={'left'}
|
||||
/>
|
||||
)}
|
||||
<strong>{code} </strong>
|
||||
<Code>{code} </Code>
|
||||
<Markdown compact={true} inline={true} source={title} />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,20 +4,21 @@ import styled from '../../styled-components';
|
|||
import { ResponseView } from './Response';
|
||||
|
||||
const ResponsesHeader = styled.h3`
|
||||
font-size: 18px;
|
||||
font-size: 1.3em;
|
||||
padding: 0.2em 0;
|
||||
margin: 3em 0 1.1em;
|
||||
color: #253137;
|
||||
color: ${({ theme }) => theme.colors.text.primary};
|
||||
font-weight: normal;
|
||||
`;
|
||||
|
||||
export interface ResponseListProps {
|
||||
responses: ResponseModel[];
|
||||
isCallback?: boolean;
|
||||
}
|
||||
|
||||
export class ResponsesList extends React.PureComponent<ResponseListProps> {
|
||||
render() {
|
||||
const { responses } = this.props;
|
||||
const { responses, isCallback } = this.props;
|
||||
|
||||
if (!responses || responses.length === 0) {
|
||||
return null;
|
||||
|
@ -25,7 +26,7 @@ export class ResponsesList extends React.PureComponent<ResponseListProps> {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<ResponsesHeader> Responses </ResponsesHeader>
|
||||
<ResponsesHeader>{isCallback ? 'Callback responses' : 'Responses'}</ResponsesHeader>
|
||||
{responses.map(response => {
|
||||
return <ResponseView key={response.code} response={response} />;
|
||||
})}
|
||||
|
|
|
@ -5,6 +5,10 @@ import styled from '../../styled-components';
|
|||
import { ResponseTitle } from './ResponseTitle';
|
||||
|
||||
export const StyledResponseTitle = styled(ResponseTitle)`
|
||||
display: block;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 4px;
|
||||
|
@ -12,10 +16,13 @@ export const StyledResponseTitle = styled(ResponseTitle)`
|
|||
background-color: #f2f2f2;
|
||||
cursor: pointer;
|
||||
|
||||
color: ${props => props.theme.colors.responses[props.type].color};
|
||||
background-color: ${props => props.theme.colors.responses[props.type].backgroundColor};
|
||||
|
||||
${props =>
|
||||
color: ${(props) => props.theme.colors.responses[props.type].color};
|
||||
background-color: ${(props) => props.theme.colors.responses[props.type].backgroundColor};
|
||||
&:focus {
|
||||
outline: auto;
|
||||
outline-color: ${(props) => props.theme.colors.responses[props.type].color};
|
||||
}
|
||||
${(props) =>
|
||||
(props.empty &&
|
||||
`
|
||||
cursor: default;
|
||||
|
@ -25,6 +32,10 @@ cursor: default;
|
|||
width: 1.5em;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
`) ||
|
||||
''};
|
||||
|
@ -39,3 +50,7 @@ export const HeadersCaption = styled(UnderlinedHeader.withComponent('caption'))`
|
|||
margin-top: 1em;
|
||||
caption-side: top;
|
||||
`;
|
||||
|
||||
export const Code = styled.strong`
|
||||
vertical-align: top;
|
||||
`;
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Schema, SchemaProps } from './Schema';
|
|||
|
||||
import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements';
|
||||
import styled from '../../styled-components';
|
||||
import {humanizeConstraints} from "../../utils";
|
||||
|
||||
const PaddedSchema = styled.div`
|
||||
padding-left: ${({ theme }) => theme.spacing.unit * 2}px;
|
||||
|
@ -12,9 +13,16 @@ const PaddedSchema = styled.div`
|
|||
export class ArraySchema extends React.PureComponent<SchemaProps> {
|
||||
render() {
|
||||
const itemsSchema = this.props.schema.items!;
|
||||
const itemConstraintSchema = (
|
||||
min: number | undefined = undefined,
|
||||
max: number | undefined = undefined,
|
||||
) => ({ type: 'array', minItems: min, maxItems: max });
|
||||
|
||||
const minMaxItems = humanizeConstraints(itemConstraintSchema(itemsSchema.schema.minItems, itemsSchema.schema.maxItems));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ArrayOpenningLabel> Array </ArrayOpenningLabel>
|
||||
<ArrayOpenningLabel> Array ({minMaxItems})</ArrayOpenningLabel>
|
||||
<PaddedSchema>
|
||||
<Schema {...this.props} schema={itemsSchema} />
|
||||
</PaddedSchema>
|
||||
|
|
|
@ -21,7 +21,7 @@ export class DiscriminatorDropdown extends React.Component<{
|
|||
});
|
||||
|
||||
options.sort((a, b) => {
|
||||
return enumOrder[a.label] > enumOrder[b.label] ? 1 : -1;
|
||||
return enumOrder[a.value] > enumOrder[b.value] ? 1 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -33,21 +33,21 @@ export class DiscriminatorDropdown extends React.Component<{
|
|||
|
||||
const options = parent.oneOf.map((subSchema, idx) => {
|
||||
return {
|
||||
value: idx.toString(),
|
||||
label: subSchema.title,
|
||||
value: subSchema.title,
|
||||
idx,
|
||||
};
|
||||
});
|
||||
|
||||
const activeItem = options[parent.activeOneOf];
|
||||
const activeValue = options[parent.activeOneOf].value;
|
||||
|
||||
this.sortOptions(options, enumValues);
|
||||
|
||||
return (
|
||||
<StyledDropdown value={activeItem} options={options} onChange={this.changeActiveChild} />
|
||||
<StyledDropdown value={activeValue} options={options} onChange={this.changeActiveChild} />
|
||||
);
|
||||
}
|
||||
|
||||
changeActiveChild = ({ value }) => {
|
||||
const idx = parseInt(value, 10);
|
||||
this.props.parent.activateOneOf(idx);
|
||||
changeActiveChild = (option: DropdownOption) => {
|
||||
this.props.parent.activateOneOf(option.idx);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import { DiscriminatorDropdown } from './DiscriminatorDropdown';
|
|||
import { SchemaProps } from './Schema';
|
||||
|
||||
import { mapWithLast } from '../../utils';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
|
||||
export interface ObjectSchemaProps extends SchemaProps {
|
||||
discriminator?: {
|
||||
|
@ -19,6 +20,8 @@ export interface ObjectSchemaProps extends SchemaProps {
|
|||
|
||||
@observer
|
||||
export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
||||
static contextType = OptionsContext;
|
||||
|
||||
get parentSchema() {
|
||||
return this.props.discriminator!.parentSchema;
|
||||
}
|
||||
|
@ -41,6 +44,8 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
|||
})
|
||||
: fields;
|
||||
|
||||
const expandByDefault = this.context.expandSingleSchemaField && filteredFields.length === 1;
|
||||
|
||||
return (
|
||||
<PropertiesTable>
|
||||
{showTitle && <PropertiesTableCaption>{this.props.schema.title}</PropertiesTableCaption>}
|
||||
|
@ -51,6 +56,7 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
|||
key={field.name}
|
||||
isLast={isLast}
|
||||
field={field}
|
||||
expandByDefault={expandByDefault}
|
||||
renderDiscriminatorSwitch={
|
||||
(discriminator &&
|
||||
discriminator.fieldName === field.name &&
|
||||
|
|
|
@ -43,9 +43,10 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
|||
|
||||
if (discriminatorProp !== undefined) {
|
||||
if (!oneOf || !oneOf.length) {
|
||||
throw new Error(
|
||||
console.warn(
|
||||
`Looks like you are using discriminator wrong: you don't have any definition inherited from the ${schema.title}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<ObjectSchema
|
||||
|
|
|
@ -80,7 +80,7 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
|
|||
}
|
||||
|
||||
const MediaSamplesWrap = styled.div`
|
||||
background: ${({ theme }) => theme.codeSample.backgroundColor};
|
||||
background: ${({ theme }) => theme.codeBlock.backgroundColor};
|
||||
& > div,
|
||||
& > pre {
|
||||
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||
|
|
|
@ -7,6 +7,7 @@ import { MenuItem } from '../SideMenu/MenuItem';
|
|||
import { MarkerService } from '../../services/MarkerService';
|
||||
import { SearchResult } from '../../services/SearchWorker.worker';
|
||||
|
||||
import { bind, debounce } from 'decko';
|
||||
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||
import {
|
||||
ClearIcon,
|
||||
|
@ -96,7 +97,6 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
|||
setResults(results: SearchResult[], term: string) {
|
||||
this.setState({
|
||||
results,
|
||||
term,
|
||||
});
|
||||
this.props.marker.mark(term);
|
||||
}
|
||||
|
|
|
@ -19,14 +19,14 @@ export const SearchInput = styled.input.attrs(() => ({
|
|||
border: 0;
|
||||
border-bottom: 1px solid
|
||||
${({ theme }) =>
|
||||
(getLuminance(theme.menu.backgroundColor) > 0.5 ? darken : lighten)(
|
||||
(getLuminance(theme.sidebar.backgroundColor) > 0.5 ? darken : lighten)(
|
||||
0.1,
|
||||
theme.menu.backgroundColor,
|
||||
theme.sidebar.backgroundColor,
|
||||
)};
|
||||
font-family: ${({ theme }) => theme.typography.fontFamily};
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
color: ${props => props.theme.menu.textColor};
|
||||
color: ${props => props.theme.sidebar.textColor};
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
`;
|
||||
|
@ -51,18 +51,18 @@ export const SearchIcon = styled((props: { className?: string }) => (
|
|||
width: 0.9em;
|
||||
|
||||
path {
|
||||
fill: ${props => props.theme.menu.textColor};
|
||||
fill: ${props => props.theme.sidebar.textColor};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchResultsBox = styled.div`
|
||||
padding: ${props => props.theme.spacing.unit}px 0;
|
||||
background-color: ${({ theme }) => darken(0.05, theme.menu.backgroundColor)}};
|
||||
color: ${props => props.theme.menu.textColor};
|
||||
background-color: ${({ theme }) => darken(0.05, theme.sidebar.backgroundColor)}};
|
||||
color: ${props => props.theme.sidebar.textColor};
|
||||
min-height: 150px;
|
||||
max-height: 250px;
|
||||
border-top: ${({ theme }) => darken(0.1, theme.menu.backgroundColor)}};
|
||||
border-bottom: ${({ theme }) => darken(0.1, theme.menu.backgroundColor)}};
|
||||
border-top: ${({ theme }) => darken(0.1, theme.sidebar.backgroundColor)}};
|
||||
border-bottom: ${({ theme }) => darken(0.1, theme.sidebar.backgroundColor)}};
|
||||
margin-top: 10px;
|
||||
line-height: 1.4;
|
||||
font-size: 0.9em;
|
||||
|
@ -73,7 +73,7 @@ export const SearchResultsBox = styled.div`
|
|||
|
||||
&:hover,
|
||||
&.active {
|
||||
background-color: ${({ theme }) => darken(0.1, theme.menu.backgroundColor)};
|
||||
background-color: ${({ theme }) => darken(0.1, theme.sidebar.backgroundColor)};
|
||||
}
|
||||
|
||||
> svg {
|
||||
|
|
|
@ -97,7 +97,7 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
|||
scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
|
||||
<tr key="bearer">
|
||||
<th> Bearer format </th>
|
||||
<td> "{scheme.http.bearerFormat}" </td>
|
||||
<td> "{scheme.http.bearerFormat}" </td>
|
||||
</tr>
|
||||
),
|
||||
]
|
||||
|
@ -105,7 +105,11 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
|||
<tr>
|
||||
<th> Connect URL </th>
|
||||
<td>
|
||||
<a target="_blank" href={scheme.openId.connectUrl}>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={scheme.openId.connectUrl}
|
||||
>
|
||||
{scheme.openId.connectUrl}
|
||||
</a>
|
||||
</td>
|
||||
|
|
|
@ -4,14 +4,20 @@ import { ClipboardService } from '../../services';
|
|||
|
||||
export class SelectOnClick extends React.PureComponent {
|
||||
private child: HTMLDivElement | null;
|
||||
handleClick = () => {
|
||||
selectElement = () => {
|
||||
ClipboardService.selectElement(this.child);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<div ref={el => (this.child = el)} onClick={this.handleClick}>
|
||||
<div
|
||||
ref={el => (this.child = el)}
|
||||
onClick={this.selectElement}
|
||||
onFocus={this.selectElement}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ import { IMenuItem, OperationModel } from '../../services';
|
|||
import { shortenHTTPVerb } from '../../utils/openapi';
|
||||
import { MenuItems } from './MenuItems';
|
||||
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
||||
import { l } from '../../services/Labels';
|
||||
|
||||
export interface MenuItemProps {
|
||||
item: IMenuItem;
|
||||
|
@ -103,7 +104,11 @@ export class OperationMenuItemContent extends React.Component<OperationMenuItemC
|
|||
deprecated={item.deprecated}
|
||||
ref={this.ref}
|
||||
>
|
||||
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
||||
{item.isWebhook ? (
|
||||
<OperationBadge type="hook">{l('webhook')}</OperationBadge>
|
||||
) : (
|
||||
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
||||
)}
|
||||
<MenuItemTitle width="calc(100% - 38px)">
|
||||
{item.name}
|
||||
{this.props.children}
|
||||
|
|
|
@ -25,7 +25,7 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
|||
>
|
||||
<MenuItems items={store.items} onActivate={this.activate} root={true} setActiveSelection={this.props.setActiveSelection} />
|
||||
<RedocAttribution>
|
||||
<a target="_blank" href="https://github.com/Redocly/redoc">
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://github.com/Redocly/redoc">
|
||||
Documentation Powered by ReDoc
|
||||
</a>
|
||||
</RedocAttribution>
|
||||
|
|
|
@ -2,12 +2,12 @@ import * as classnames from 'classnames';
|
|||
import { darken } from 'polished';
|
||||
|
||||
import { deprecatedCss, ShelfIcon } from '../../common-elements';
|
||||
import styled, { css } from '../../styled-components';
|
||||
import styled, { css, ResolvedThemeInterface } from '../../styled-components';
|
||||
|
||||
export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
|
||||
className: `operation-type ${props.type}`,
|
||||
}))<{ type: string }>`
|
||||
width: 32px;
|
||||
width: 9ex;
|
||||
display: inline-block;
|
||||
height: ${props => props.theme.typography.code.fontSize};
|
||||
line-height: ${props => props.theme.typography.code.fontSize};
|
||||
|
@ -16,7 +16,7 @@ export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
|
|||
background-repeat: no-repeat;
|
||||
background-position: 6px 4px;
|
||||
font-size: 7px;
|
||||
font-family: Verdana; // web-safe
|
||||
font-family: Verdana, sans-serif; // web-safe
|
||||
color: white;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
|
@ -60,13 +60,17 @@ export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
|
|||
&.head {
|
||||
background-color: ${props => props.theme.colors.http.head};
|
||||
}
|
||||
|
||||
&.hook {
|
||||
background-color: ${props => props.theme.colors.primary.main};
|
||||
}
|
||||
`;
|
||||
|
||||
function menuItemActiveBg(depth, { theme }): string {
|
||||
function menuItemActiveBg(depth, { theme }: { theme: ResolvedThemeInterface }): string {
|
||||
if (depth > 1) {
|
||||
return darken(0.1, theme.menu.backgroundColor);
|
||||
return darken(0.1, theme.sidebar.backgroundColor);
|
||||
} else if (depth === 1) {
|
||||
return darken(0.05, theme.menu.backgroundColor);
|
||||
return darken(0.05, theme.sidebar.backgroundColor);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
@ -94,21 +98,21 @@ export const MenuItemLi = styled.li<{ depth: number }>`
|
|||
export const menuItemDepth = {
|
||||
0: css`
|
||||
opacity: 0.7;
|
||||
text-transform: ${({ theme }) => theme.menu.groupItems.textTransform};
|
||||
text-transform: ${({ theme }) => theme.sidebar.groupItems.textTransform};
|
||||
font-size: 0.8em;
|
||||
padding-bottom: 0;
|
||||
cursor: default;
|
||||
color: ${props => props.theme.menu.textColor};
|
||||
color: ${props => props.theme.sidebar.textColor};
|
||||
`,
|
||||
1: css`
|
||||
font-size: 0.929em;
|
||||
text-transform: ${({ theme }) => theme.menu.level1Items.textTransform};
|
||||
text-transform: ${({ theme }) => theme.sidebar.level1Items.textTransform};
|
||||
&:hover {
|
||||
color: ${props => props.theme.menu.activeTextColor};
|
||||
color: ${props => props.theme.sidebar.activeTextColor};
|
||||
}
|
||||
`,
|
||||
2: css`
|
||||
color: ${props => props.theme.menu.textColor};
|
||||
color: ${props => props.theme.sidebar.textColor};
|
||||
`,
|
||||
};
|
||||
|
||||
|
@ -126,7 +130,8 @@ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
|
|||
}),
|
||||
}))<MenuItemLabelType>`
|
||||
cursor: pointer;
|
||||
color: ${props => (props.active ? props.theme.menu.activeTextColor : props.theme.menu.textColor)};
|
||||
color: ${props =>
|
||||
props.active ? props.theme.sidebar.activeTextColor : props.theme.sidebar.textColor};
|
||||
margin: 0;
|
||||
padding: 12.5px ${props => props.theme.spacing.unit * 4}px;
|
||||
${({ depth, type, theme }) =>
|
||||
|
@ -144,10 +149,10 @@ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
|
|||
}
|
||||
|
||||
${ShelfIcon} {
|
||||
height: ${({ theme }) => theme.menu.arrow.size};
|
||||
width: ${({ theme }) => theme.menu.arrow.size};
|
||||
height: ${({ theme }) => theme.sidebar.arrow.size};
|
||||
width: ${({ theme }) => theme.sidebar.arrow.size};
|
||||
polygon {
|
||||
fill: ${({ theme }) => theme.menu.arrow.color};
|
||||
fill: ${({ theme }) => theme.sidebar.arrow.color};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -172,8 +177,8 @@ export const RedocAttribution = styled.div`
|
|||
a,
|
||||
a:visited,
|
||||
a:hover {
|
||||
color: ${theme.menu.textColor} !important;
|
||||
border-top: 1px solid ${darken(0.1, theme.menu.backgroundColor)};
|
||||
color: ${theme.sidebar.textColor} !important;
|
||||
border-top: 1px solid ${darken(0.1, theme.sidebar.backgroundColor)};
|
||||
padding: ${theme.spacing.unit}px 0;
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -26,14 +26,14 @@ export interface StickySidebarState {
|
|||
const stickyfill = Stickyfill && Stickyfill();
|
||||
|
||||
const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
||||
width: ${props => props.theme.menu.width};
|
||||
background-color: ${props => props.theme.menu.backgroundColor};
|
||||
width: ${props => props.theme.sidebar.width};
|
||||
background-color: ${props => props.theme.sidebar.backgroundColor};
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
backface-visibility: hidden;
|
||||
contain: strict;
|
||||
/* contain: strict; TODO: breaks layout since Chrome 80*/
|
||||
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
|
@ -44,7 +44,7 @@ const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
|||
position: fixed;
|
||||
z-index: 20;
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.menu.backgroundColor};
|
||||
background: ${({ theme }) => theme.sidebar.backgroundColor};
|
||||
display: ${props => (props.open ? 'flex' : 'none')};
|
||||
`};
|
||||
|
||||
|
|
59
src/components/__tests__/Callbacks.test.tsx
Normal file
59
src/components/__tests__/Callbacks.test.tsx
Normal file
|
@ -0,0 +1,59 @@
|
|||
/* tslint:disable:no-implicit-dependencies */
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
|
||||
import { OpenAPIParser } from '../../services';
|
||||
import { CallbackModel } from '../../services/models/Callback';
|
||||
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
||||
import { CallbacksList, CallbackTitle, CallbackOperation } from '../Callbacks';
|
||||
import * as simpleCallbackFixture from './fixtures/simple-callback.json';
|
||||
|
||||
const options = new RedocNormalizedOptions({});
|
||||
describe('Components', () => {
|
||||
describe('Callbacks', () => {
|
||||
it('should correctly render CallbackView', () => {
|
||||
const parser = new OpenAPIParser(simpleCallbackFixture, undefined, options);
|
||||
const callback = new CallbackModel(
|
||||
parser,
|
||||
'Test.Callback',
|
||||
{ $ref: '#/components/callbacks/Test' },
|
||||
'',
|
||||
options,
|
||||
);
|
||||
// There should be 1 operation defined in simple-callback.json, just get it manually for readability.
|
||||
const callbackViewElement = shallow(
|
||||
<CallbackOperation key={callback.name} callbackOperation={callback.operations[0]} />,
|
||||
).getElement();
|
||||
expect(callbackViewElement.props).toBeDefined();
|
||||
expect(callbackViewElement.props.children).toBeDefined();
|
||||
expect(callbackViewElement.props.children.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('should correctly render CallbackTitle', () => {
|
||||
const callbackTitleViewElement = shallow(
|
||||
<CallbackTitle name={'Test'} className={'.test'} onClick={undefined} httpVerb={'get'} />,
|
||||
).getElement();
|
||||
expect(callbackTitleViewElement.props).toBeDefined();
|
||||
expect(callbackTitleViewElement.props.className).toEqual('.test');
|
||||
expect(callbackTitleViewElement.props.onClick).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should correctly render CallbacksList', () => {
|
||||
const parser = new OpenAPIParser(simpleCallbackFixture, undefined, options);
|
||||
const callback = new CallbackModel(
|
||||
parser,
|
||||
'Test.Callback',
|
||||
{ $ref: '#/components/callbacks/Test' },
|
||||
'',
|
||||
options,
|
||||
);
|
||||
const callbacksListViewElement = shallow(
|
||||
<CallbacksList callbacks={[callback]} />,
|
||||
).getElement();
|
||||
expect(callbacksListViewElement.props).toBeDefined();
|
||||
expect(callbacksListViewElement.props.children).toBeDefined();
|
||||
expect(callbacksListViewElement.props.children.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable import/no-internal-modules */
|
||||
/* tslint:disable:no-implicit-dependencies */
|
||||
|
||||
import { shallow } from 'enzyme';
|
||||
|
|
|
@ -25,11 +25,11 @@ describe('Components', () => {
|
|||
|
||||
test('should collapse/uncollapse', () => {
|
||||
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 > button[children=" Expand all "]');
|
||||
expandAll.simulate('click');
|
||||
expect(component.html()).toContain('class="hoverable"'); // all are collapsed
|
||||
|
||||
const collapseAll = component.find('div > span[children=" Collapse all "]');
|
||||
const collapseAll = component.find('div > button[children=" Collapse all "]');
|
||||
collapseAll.simulate('click');
|
||||
expect(component.html()).not.toContain('class="hoverable"'); // all are collapsed
|
||||
});
|
||||
|
@ -37,7 +37,7 @@ describe('Components', () => {
|
|||
test('should collapse/uncollapse', () => {
|
||||
ClipboardService.copySelected = jest.fn();
|
||||
|
||||
const copy = component.find('span[onClick]').first();
|
||||
const copy = component.find('button[onClick]').first();
|
||||
copy.simulate('click');
|
||||
|
||||
expect(ClipboardService.copySelected as jest.Mock).toHaveBeenCalled();
|
||||
|
|
|
@ -9,7 +9,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"deprecated": false,
|
||||
"description": "",
|
||||
"example": undefined,
|
||||
"expanded": false,
|
||||
"expanded": undefined,
|
||||
"explode": false,
|
||||
"in": undefined,
|
||||
"kind": "field",
|
||||
|
@ -59,7 +59,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"deprecated": false,
|
||||
"description": "",
|
||||
"example": undefined,
|
||||
"expanded": false,
|
||||
"expanded": undefined,
|
||||
"explode": false,
|
||||
"in": undefined,
|
||||
"kind": "field",
|
||||
|
|
66
src/components/__tests__/fixtures/simple-callback.json
Normal file
66
src/components/__tests__/fixtures/simple-callback.json
Normal file
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.0",
|
||||
"title": "Foo"
|
||||
},
|
||||
"components": {
|
||||
"callbacks": {
|
||||
"Test": {
|
||||
"/test": {
|
||||
"post": {
|
||||
"operationId": "testCallback",
|
||||
"description": "Test callback.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "TestTitle",
|
||||
"type": "object",
|
||||
"description": "Test description",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "The type of response.",
|
||||
"enum": [
|
||||
"TestResponse.Complete"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"FAILURE",
|
||||
"SUCCESS"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Test-Header",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"example": "1",
|
||||
"description": "This is a test header parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Test response."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,11 @@ export * from './Responses/ResponsesList';
|
|||
export * from './Responses/ResponseTitle';
|
||||
export * from './ResponseSamples/ResponseSamples';
|
||||
export * from './PayloadSamples/PayloadSamples';
|
||||
export * from './PayloadSamples/styled.elements';
|
||||
export * from './MediaTypeSwitch/MediaTypesSwitch';
|
||||
export * from './Parameters/Parameters';
|
||||
export * from './PayloadSamples/Example';
|
||||
export * from './DropdownOrLabel/DropdownOrLabel';
|
||||
|
||||
export * from './ErrorBoundary';
|
||||
export * from './StoreBuilder';
|
||||
|
|
11
src/index.ts
11
src/index.ts
|
@ -1,5 +1,14 @@
|
|||
export * from './components';
|
||||
export { MiddlePanel, Row, RightPanel, Section } from './common-elements/';
|
||||
export {
|
||||
MiddlePanel,
|
||||
Row,
|
||||
RightPanel,
|
||||
Section,
|
||||
StyledDropdown,
|
||||
SimpleDropdown,
|
||||
DropdownOption,
|
||||
} from './common-elements/';
|
||||
export { OpenAPIEncoding } from './types';
|
||||
export * from './services';
|
||||
export * from './utils';
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ import {
|
|||
SECURITY_DEFINITIONS_JSX_NAME,
|
||||
} from '../utils/openapi';
|
||||
|
||||
import { IS_BROWSER } from '../utils';
|
||||
|
||||
export interface StoreState {
|
||||
menu: {
|
||||
activeItemIdx: number;
|
||||
|
@ -101,6 +103,9 @@ export class AppStore {
|
|||
dispose() {
|
||||
this.scroll.dispose();
|
||||
this.menu.dispose();
|
||||
if (this.search) {
|
||||
this.search.dispose();
|
||||
}
|
||||
if (this.disposer != null) {
|
||||
this.disposer();
|
||||
}
|
||||
|
@ -131,16 +136,16 @@ export class AppStore {
|
|||
|
||||
const elements: Element[] = [];
|
||||
for (let i = start; i < end; i++) {
|
||||
let elem = this.menu.getElementAt(i);
|
||||
const elem = this.menu.getElementAt(i);
|
||||
if (!elem) {
|
||||
continue;
|
||||
}
|
||||
if (this.menu.flatItems[i].type === 'section') {
|
||||
elem = elem.parentElement!.parentElement;
|
||||
}
|
||||
if (elem) {
|
||||
elements.push(elem);
|
||||
}
|
||||
elements.push(elem);
|
||||
}
|
||||
|
||||
if (idx === -1 && IS_BROWSER) {
|
||||
const $description = document.querySelector('[data-role="redoc-description"]');
|
||||
if ($description) elements.push($description);
|
||||
}
|
||||
|
||||
this.marker.addOnly(elements);
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface LabelsConfig {
|
|||
nullable: string;
|
||||
recursive: string;
|
||||
arrayOf: string;
|
||||
webhook: string;
|
||||
}
|
||||
|
||||
export type LabelsConfigRaw = Partial<LabelsConfig>;
|
||||
|
@ -22,6 +23,7 @@ const labels: LabelsConfig = {
|
|||
nullable: 'Nullable',
|
||||
recursive: 'Recursive',
|
||||
arrayOf: 'Array of ',
|
||||
webhook: 'Event',
|
||||
};
|
||||
|
||||
export function setRedocLabels(_labels?: LabelsConfigRaw) {
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import { OpenAPIOperation, OpenAPIParameter, OpenAPISpec, OpenAPITag, Referenced } from '../types';
|
||||
import {
|
||||
OpenAPIOperation,
|
||||
OpenAPIParameter,
|
||||
OpenAPISpec,
|
||||
OpenAPITag,
|
||||
Referenced,
|
||||
OpenAPIServer,
|
||||
OpenAPIPaths,
|
||||
} from '../types';
|
||||
import {
|
||||
isOperationName,
|
||||
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||
setSecuritySchemePrefix,
|
||||
JsonPointer,
|
||||
} from '../utils';
|
||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||
import { GroupModel, OperationModel } from './models';
|
||||
|
@ -15,12 +24,15 @@ export type TagInfo = OpenAPITag & {
|
|||
};
|
||||
|
||||
export type ExtendedOpenAPIOperation = {
|
||||
pointer: string;
|
||||
pathName: string;
|
||||
httpVerb: string;
|
||||
pathParameters: Array<Referenced<OpenAPIParameter>>;
|
||||
pathServers: Array<OpenAPIServer> | undefined;
|
||||
isWebhook: boolean;
|
||||
} & OpenAPIOperation;
|
||||
|
||||
export type TagsInfoMap = Dict<TagInfo>;
|
||||
export type TagsInfoMap = Record<string, TagInfo>;
|
||||
|
||||
export interface TagGroup {
|
||||
name: string;
|
||||
|
@ -209,41 +221,49 @@ export class MenuBuilder {
|
|||
tags[tag.name] = { ...tag, operations: [] };
|
||||
}
|
||||
|
||||
const paths = spec.paths;
|
||||
for (const pathName of Object.keys(paths)) {
|
||||
const path = paths[pathName];
|
||||
const operations = Object.keys(path).filter(isOperationName);
|
||||
for (const operationName of operations) {
|
||||
const operationInfo = path[operationName];
|
||||
let operationTags = operationInfo.tags;
|
||||
getTags(spec.paths);
|
||||
if (spec['x-webhooks']) {
|
||||
getTags(spec['x-webhooks'], true);
|
||||
}
|
||||
|
||||
if (!operationTags || !operationTags.length) {
|
||||
// empty tag
|
||||
operationTags = [''];
|
||||
}
|
||||
function getTags(paths: OpenAPIPaths, isWebhook?: boolean) {
|
||||
for (const pathName of Object.keys(paths)) {
|
||||
const path = paths[pathName];
|
||||
const operations = Object.keys(path).filter(isOperationName);
|
||||
for (const operationName of operations) {
|
||||
const operationInfo = path[operationName];
|
||||
let operationTags = operationInfo.tags;
|
||||
|
||||
for (const tagName of operationTags) {
|
||||
let tag = tags[tagName];
|
||||
if (tag === undefined) {
|
||||
tag = {
|
||||
name: tagName,
|
||||
operations: [],
|
||||
};
|
||||
tags[tagName] = tag;
|
||||
if (!operationTags || !operationTags.length) {
|
||||
// empty tag
|
||||
operationTags = [''];
|
||||
}
|
||||
if (tag['x-traitTag']) {
|
||||
continue;
|
||||
|
||||
for (const tagName of operationTags) {
|
||||
let tag = tags[tagName];
|
||||
if (tag === undefined) {
|
||||
tag = {
|
||||
name: tagName,
|
||||
operations: [],
|
||||
};
|
||||
tags[tagName] = tag;
|
||||
}
|
||||
if (tag['x-traitTag']) {
|
||||
continue;
|
||||
}
|
||||
tag.operations.push({
|
||||
...operationInfo,
|
||||
pathName,
|
||||
pointer: JsonPointer.compile(['paths', pathName, operationName]),
|
||||
httpVerb: operationName,
|
||||
pathParameters: path.parameters || [],
|
||||
pathServers: path.servers,
|
||||
isWebhook: !!isWebhook,
|
||||
});
|
||||
}
|
||||
tag.operations.push({
|
||||
...operationInfo,
|
||||
pathName,
|
||||
httpVerb: operationName,
|
||||
pathParameters: path.parameters || [],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ export class OpenAPIParser {
|
|||
* @param obj object to dereference
|
||||
* @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)) {
|
||||
const resolved = this.byRef<T>(obj.$ref)!;
|
||||
const visited = this._refCounter.visited(obj.$ref);
|
||||
|
@ -239,7 +239,9 @@ export class OpenAPIParser {
|
|||
receiver.type !== undefined &&
|
||||
subSchema.type !== undefined
|
||||
) {
|
||||
throw new Error(`Incompatible types in allOf at "${$ref}"`);
|
||||
console.warn(
|
||||
`Incompatible types in allOf at "${$ref}": "${receiver.type}" and "${subSchema.type}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (subSchema.type !== undefined) {
|
||||
|
@ -296,8 +298,8 @@ export class OpenAPIParser {
|
|||
* returns map of definition pointer to definition name
|
||||
* @param $refs array of references to find derived from
|
||||
*/
|
||||
findDerived($refs: string[]): Dict<string[]> {
|
||||
const res: Dict<string[]> = {};
|
||||
findDerived($refs: string[]): Record<string, string[] | string> {
|
||||
const res: Record<string, string[]> = {};
|
||||
const schemas = (this.spec.components && this.spec.components.schemas) || {};
|
||||
for (const defName in schemas) {
|
||||
const def = this.deref(schemas[defName]);
|
||||
|
|
|
@ -12,6 +12,7 @@ export interface RedocRawOptions {
|
|||
expandResponses?: string | 'all';
|
||||
requiredPropsFirst?: boolean | string;
|
||||
sortPropsAlphabetically?: boolean | string;
|
||||
sortEnumValuesAlphabetically?: boolean | string;
|
||||
noAutoAuth?: boolean | string;
|
||||
nativeScrollbars?: boolean | string;
|
||||
pathInMiddlePanel?: boolean | string;
|
||||
|
@ -25,17 +26,20 @@ export interface RedocRawOptions {
|
|||
menuToggle?: boolean | string;
|
||||
jsonSampleExpandLevel?: number | string | 'all';
|
||||
hideSchemaTitles?: boolean | string;
|
||||
simpleOneOfTypeLabel?: boolean | string;
|
||||
payloadSampleIdx?: number;
|
||||
expandSingleSchemaField?: boolean | string;
|
||||
|
||||
unstable_ignoreMimeParameters?: boolean;
|
||||
|
||||
allowedMdComponents?: Dict<MDXComponentMeta>;
|
||||
allowedMdComponents?: Record<string, MDXComponentMeta>;
|
||||
|
||||
labels?: LabelsConfigRaw;
|
||||
|
||||
enumSkipQuotes?: boolean | string;
|
||||
|
||||
expandDefaultServerVariables?: boolean;
|
||||
maxDisplayedEnumValues?: number;
|
||||
}
|
||||
|
||||
function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
|
||||
|
@ -48,6 +52,16 @@ function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): bool
|
|||
return val;
|
||||
}
|
||||
|
||||
function argValueToNumber(value: number | string | undefined): number | undefined {
|
||||
if (typeof value === 'string') {
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export class RedocNormalizedOptions {
|
||||
static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) {
|
||||
if (value === 'all') {
|
||||
|
@ -55,7 +69,7 @@ export class RedocNormalizedOptions {
|
|||
}
|
||||
if (typeof value === 'string') {
|
||||
const res = {};
|
||||
value.split(',').forEach(code => {
|
||||
value.split(',').forEach((code) => {
|
||||
res[code.trim()] = true;
|
||||
});
|
||||
return res;
|
||||
|
@ -111,11 +125,18 @@ export class RedocNormalizedOptions {
|
|||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return value.split(',').map(ext => ext.trim());
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
|
||||
return value;
|
||||
switch (value) {
|
||||
case 'true':
|
||||
return true;
|
||||
case 'false':
|
||||
return false;
|
||||
default:
|
||||
return value.split(',').map((ext) => ext.trim());
|
||||
}
|
||||
}
|
||||
|
||||
static normalizePayloadSampleIdx(value: RedocRawOptions['payloadSampleIdx']): number {
|
||||
|
@ -146,6 +167,7 @@ export class RedocNormalizedOptions {
|
|||
expandResponses: { [code: string]: boolean } | 'all';
|
||||
requiredPropsFirst: boolean;
|
||||
sortPropsAlphabetically: boolean;
|
||||
sortEnumValuesAlphabetically: boolean;
|
||||
noAutoAuth: boolean;
|
||||
nativeScrollbars: boolean;
|
||||
pathInMiddlePanel: boolean;
|
||||
|
@ -159,17 +181,32 @@ export class RedocNormalizedOptions {
|
|||
jsonSampleExpandLevel: number;
|
||||
enumSkipQuotes: boolean;
|
||||
hideSchemaTitles: boolean;
|
||||
simpleOneOfTypeLabel: boolean;
|
||||
payloadSampleIdx: number;
|
||||
expandSingleSchemaField: boolean;
|
||||
|
||||
/* tslint:disable-next-line */
|
||||
unstable_ignoreMimeParameters: boolean;
|
||||
allowedMdComponents: Dict<MDXComponentMeta>;
|
||||
allowedMdComponents: Record<string, MDXComponentMeta>;
|
||||
|
||||
expandDefaultServerVariables: boolean;
|
||||
maxDisplayedEnumValues?: number;
|
||||
|
||||
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
||||
raw = { ...defaults, ...raw };
|
||||
const hook = raw.theme && raw.theme.extensionsHook;
|
||||
|
||||
// migrate from old theme
|
||||
if ((raw.theme as any)?.menu && !raw.theme?.sidebar) {
|
||||
console.warn('Theme setting "menu" is deprecated. Rename to "sidebar"');
|
||||
raw.theme!.sidebar = (raw.theme as any).menu;
|
||||
}
|
||||
|
||||
if ((raw.theme as any)?.codeSample && !raw.theme?.codeBlock) {
|
||||
console.warn('Theme setting "codeSample" is deprecated. Rename to "codeBlock"');
|
||||
raw.theme!.codeBlock = (raw.theme as any).codeSample;
|
||||
}
|
||||
|
||||
this.theme = resolveTheme(
|
||||
mergeObjects({} as any, defaultTheme, { ...raw.theme, extensionsHook: undefined }),
|
||||
);
|
||||
|
@ -184,6 +221,7 @@ export class RedocNormalizedOptions {
|
|||
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
|
||||
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
||||
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
|
||||
this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically);
|
||||
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
|
||||
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
||||
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
||||
|
@ -199,12 +237,15 @@ export class RedocNormalizedOptions {
|
|||
);
|
||||
this.enumSkipQuotes = argValueToBoolean(raw.enumSkipQuotes);
|
||||
this.hideSchemaTitles = argValueToBoolean(raw.hideSchemaTitles);
|
||||
this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel);
|
||||
this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx);
|
||||
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
|
||||
|
||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||
|
||||
this.allowedMdComponents = raw.allowedMdComponents || {};
|
||||
|
||||
this.expandDefaultServerVariables = argValueToBoolean(raw.expandDefaultServerVariables);
|
||||
this.maxDisplayedEnumValues = argValueToNumber(raw.maxDisplayedEnumValues);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ const EVENT = 'scroll';
|
|||
export class ScrollService {
|
||||
private _scrollParent: Window | HTMLElement | undefined;
|
||||
private _emiter: EventEmitter;
|
||||
private _prevOffsetY: number = 0;
|
||||
private _prevOffsetY = 0;
|
||||
constructor(private options: RedocNormalizedOptions) {
|
||||
this._scrollParent = IS_BROWSER ? window : undefined;
|
||||
this._emiter = new EventEmitter();
|
||||
|
|
|
@ -4,21 +4,23 @@ import { OperationModel } from './models';
|
|||
|
||||
import Worker from './SearchWorker.worker';
|
||||
|
||||
let worker: new () => Worker;
|
||||
|
||||
if (IS_BROWSER) {
|
||||
try {
|
||||
// tslint:disable-next-line
|
||||
worker = require('workerize-loader?inline&fallback=false!./SearchWorker.worker');
|
||||
} catch (e) {
|
||||
function getWorker() {
|
||||
let worker: new () => Worker;
|
||||
if (IS_BROWSER) {
|
||||
try {
|
||||
// tslint:disable-next-line
|
||||
worker = require('workerize-loader?inline&fallback=false!./SearchWorker.worker');
|
||||
} catch (e) {
|
||||
worker = require('./SearchWorker.worker').default;
|
||||
}
|
||||
} else {
|
||||
worker = require('./SearchWorker.worker').default;
|
||||
}
|
||||
} else {
|
||||
worker = require('./SearchWorker.worker').default;
|
||||
return new worker();
|
||||
}
|
||||
|
||||
export class SearchStore<T> {
|
||||
searchWorker = new worker();
|
||||
searchWorker = getWorker();
|
||||
|
||||
indexItems(groups: Array<IMenuItem | OperationModel>) {
|
||||
const recurse = items => {
|
||||
|
@ -38,6 +40,11 @@ export class SearchStore<T> {
|
|||
this.searchWorker.add(title, body, meta);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
(this.searchWorker as any).terminate();
|
||||
(this.searchWorker as any).dispose();
|
||||
}
|
||||
|
||||
search(q: string) {
|
||||
return this.searchWorker.search<T>(q);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ export default class Worker {
|
|||
search: typeof search = search;
|
||||
toJS = toJS;
|
||||
load = load;
|
||||
dispose = dispose;
|
||||
}
|
||||
|
||||
export interface SearchDocument {
|
||||
|
@ -29,22 +30,28 @@ export interface SearchResult<T = string> {
|
|||
|
||||
let store: any[] = [];
|
||||
|
||||
let resolveIndex: (v: lunr.Index) => void = () => {
|
||||
throw new Error('Should not be called');
|
||||
};
|
||||
|
||||
const index: Promise<lunr.Index> = new Promise(resolve => {
|
||||
resolveIndex = resolve;
|
||||
});
|
||||
|
||||
lunr.tokenizer.separator = /\s+/;
|
||||
|
||||
const builder = new lunr.Builder();
|
||||
builder.field('title');
|
||||
builder.field('description');
|
||||
builder.ref('ref');
|
||||
let builder: lunr.Builder;
|
||||
|
||||
builder.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer);
|
||||
let resolveIndex: (v: lunr.Index) => void;
|
||||
|
||||
let index: Promise<lunr.Index>;
|
||||
|
||||
function initEmpty() {
|
||||
builder = new lunr.Builder();
|
||||
builder.field('title');
|
||||
builder.field('description');
|
||||
builder.ref('ref');
|
||||
|
||||
builder.pipeline.add(lunr.trimmer, lunr.stopWordFilter, lunr.stemmer);
|
||||
|
||||
index = new Promise(resolve => {
|
||||
resolveIndex = resolve;
|
||||
});
|
||||
}
|
||||
|
||||
initEmpty();
|
||||
|
||||
const expandTerm = term => '*' + lunr.stemmer(new lunr.Token(term, {})) + '*';
|
||||
|
||||
|
@ -70,6 +77,11 @@ export async function load(state: any) {
|
|||
resolveIndex(lunr.Index.load(state.index));
|
||||
}
|
||||
|
||||
export async function dispose() {
|
||||
store = [];
|
||||
initEmpty();
|
||||
}
|
||||
|
||||
export async function search<Meta = string>(
|
||||
q: string,
|
||||
limit = 0,
|
||||
|
|
|
@ -2,6 +2,7 @@ import { OpenAPIExternalDocumentation, OpenAPISpec } from '../types';
|
|||
|
||||
import { ContentItemModel, MenuBuilder } from './MenuBuilder';
|
||||
import { ApiInfoModel } from './models/ApiInfo';
|
||||
import { WebhookModel } from './models/Webhook';
|
||||
import { SecuritySchemesModel } from './models/SecuritySchemes';
|
||||
import { OpenAPIParser } from './OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
|
@ -15,6 +16,7 @@ export class SpecStore {
|
|||
externalDocs?: OpenAPIExternalDocumentation;
|
||||
contentItems: ContentItemModel[];
|
||||
securitySchemes: SecuritySchemesModel;
|
||||
webhooks?: WebhookModel;
|
||||
|
||||
constructor(
|
||||
spec: OpenAPISpec,
|
||||
|
@ -26,5 +28,6 @@ export class SpecStore {
|
|||
this.externalDocs = this.parser.spec.externalDocs;
|
||||
this.contentItems = MenuBuilder.buildStructure(this.parser, this.options);
|
||||
this.securitySchemes = new SecuritySchemesModel(this.parser);
|
||||
this.webhooks = new WebhookModel(this.parser, options, this.parser.spec['x-webhooks']);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ describe('Models', () => {
|
|||
let parser;
|
||||
|
||||
test('should hoist oneOfs when mergin allOf', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const spec = require('./fixtures/oneOfHoist.json');
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
expect(parser.mergeAllOf(spec.components.schemas.test)).toMatchSnapshot();
|
||||
|
|
64
src/services/__tests__/fixtures/callback.json
Normal file
64
src/services/__tests__/fixtures/callback.json
Normal file
|
@ -0,0 +1,64 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.0",
|
||||
"title": "Foo"
|
||||
},
|
||||
"components": {
|
||||
"callbacks": {
|
||||
"Test": {
|
||||
"post": {
|
||||
"operationId": "testCallback",
|
||||
"description": "Test callback.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "TestTitle",
|
||||
"type": "object",
|
||||
"description": "Test description",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "The type of response.",
|
||||
"enum": [
|
||||
"TestResponse.Complete"
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"FAILURE",
|
||||
"SUCCESS"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "X-Test-Header",
|
||||
"in": "header",
|
||||
"required": true,
|
||||
"example": "1",
|
||||
"description": "This is a test header parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Test response."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user