mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 14:14:56 +03:00
update redoc
This commit is contained in:
parent
7a8a289c71
commit
13bd59be41
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
|
@ -90,5 +90,5 @@ There are some other scripts available in the `scripts` section of the `package.
|
||||||
- **`src/services/models`**: contains classes for OpenAPI entities (e.g. Response, Operations, etc)
|
- **`src/services/models`**: contains classes for OpenAPI entities (e.g. Response, Operations, etc)
|
||||||
- **`src/types`**: contains extra typescript typings including OpenAPI doc typings
|
- **`src/types`**: contains extra typescript typings including OpenAPI doc typings
|
||||||
- **`src/utils`**: utility functions
|
- **`src/utils`**: utility functions
|
||||||
- **`src/styled-components.ts`**: - reexprots styled-components with proper typescript annotations using theme
|
- **`src/styled-components.ts`**: - reexports styled-components with proper typescript annotations using theme
|
||||||
- **`src/theme.ts`**: - default theme (colors, fonts, etc) used by all the components
|
- **`src/theme.ts`**: - default theme (colors, fonts, etc) used by all the components
|
||||||
|
|
21
.github/workflows/unit-tests.yml
vendored
Normal file
21
.github/workflows/unit-tests.yml
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
name: Unit Tests
|
||||||
|
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10.x
|
||||||
|
- name: yarn install, build, and test
|
||||||
|
run: |
|
||||||
|
npm install -g yarn
|
||||||
|
yarn install
|
||||||
|
yarn bundle
|
||||||
|
yarn test
|
16
.travis.yml
16
.travis.yml
|
@ -1,10 +1,14 @@
|
||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- '8'
|
- '10'
|
||||||
cache: yarn
|
cache:
|
||||||
|
yarn: true
|
||||||
|
directories:
|
||||||
|
# we also need to cache folder with Cypress binary
|
||||||
|
- ~/.cache
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
- GH_REF: github.com/Rebilly/ReDoc.git
|
- GH_REF: github.com/Redocly/redoc.git
|
||||||
- GIT_AUTHOR_EMAIL: redoc-bot@users.noreply.github.com
|
- GIT_AUTHOR_EMAIL: redoc-bot@users.noreply.github.com
|
||||||
- GIT_AUTHOR_NAME: RedocBot
|
- GIT_AUTHOR_NAME: RedocBot
|
||||||
- secure: H2GClDJ7TEQaWgnk8d2fIVDpLwG3rTmN8TalUzrCqXGoG6ylCVmlwzKLgfPPWrVgSA7QTdfNV0ab7c2KyPoZBinHmeGMSuKNLVbhOXRc2VFxTBntBTuyJhfCgQEpUKvJesJUuv5RuBn//wC7VZcVVNc06TZDEe8+aDVYQuCRMXZJ4X3e6nSZ64QX2veyVcr+TjnRsZPkeBVcK9hngvuaxLb/hbJ85CvjiseZRt47PGIcrEpMn9n2GMw1m0fNnPoN+MBTSCnIklTmdjPG7t4GUSLmD6H0lNLdXuehYqmQAHgYrLec1aiFlV57QaDoDZrq2hSf4vDmCB/FVydGhD5JunI67pujoV2OnD1V80eUZhYNWOYsJ2Nfp4NxgXsPUcE6zWLYsLfktMPZADhOXInQRACt1cnx8zMYKLnch1RY/ZqjSg0nPtRjLzQ0lNsw5leixvBdBnMjxYHVyAWVwg8WiJMaLO9vog2Qnxg1NTacHO2CsOmm2rw6stpg7ndp/+nOleRlfUKggjt0Tn3FjwCIXeGup2P2EBa+WW2YMAaoMFofYviR5vRlKBgdKo9fsAruaO1r6nm2EdAjOlniyw92bEfU/qOey1nVp/oK2S82uT5In8KB7vl6rF3ak7WAsT9Q5vZUhsrG+eE4PVyIyWNBhs4A7pSwZGHDR/MYtp0E2ug=
|
- secure: H2GClDJ7TEQaWgnk8d2fIVDpLwG3rTmN8TalUzrCqXGoG6ylCVmlwzKLgfPPWrVgSA7QTdfNV0ab7c2KyPoZBinHmeGMSuKNLVbhOXRc2VFxTBntBTuyJhfCgQEpUKvJesJUuv5RuBn//wC7VZcVVNc06TZDEe8+aDVYQuCRMXZJ4X3e6nSZ64QX2veyVcr+TjnRsZPkeBVcK9hngvuaxLb/hbJ85CvjiseZRt47PGIcrEpMn9n2GMw1m0fNnPoN+MBTSCnIklTmdjPG7t4GUSLmD6H0lNLdXuehYqmQAHgYrLec1aiFlV57QaDoDZrq2hSf4vDmCB/FVydGhD5JunI67pujoV2OnD1V80eUZhYNWOYsJ2Nfp4NxgXsPUcE6zWLYsLfktMPZADhOXInQRACt1cnx8zMYKLnch1RY/ZqjSg0nPtRjLzQ0lNsw5leixvBdBnMjxYHVyAWVwg8WiJMaLO9vog2Qnxg1NTacHO2CsOmm2rw6stpg7ndp/+nOleRlfUKggjt0Tn3FjwCIXeGup2P2EBa+WW2YMAaoMFofYviR5vRlKBgdKo9fsAruaO1r6nm2EdAjOlniyw92bEfU/qOey1nVp/oK2S82uT5In8KB7vl6rF3ak7WAsT9Q5vZUhsrG+eE4PVyIyWNBhs4A7pSwZGHDR/MYtp0E2ug=
|
||||||
|
@ -14,6 +18,9 @@ env:
|
||||||
- secure: SEqTg6WoGPPpcWzJ03ZfcSBb3nZ2Mdhug0ec2PszuzYO3libCb9usiqi+jils9z6qyXsL6ecz8HYazDGOUepnubhIpI5otLgfn9XiapjMT06Bj//AjbKpH7eu3TJSpJMzoRHZrKIE1y9ZKIBqKwl9Xs7ko+1oa+MLhrLuxXkoi0JqRB5UzkQtJRDoxVNjysnLQn+hsfnm+yuqPHZd2+Loy++q//WHuf9bwJrlkXn2ICYQIX5oQGlxNO6ui+OZklb0YknvyO5GdQeoKaHYru3MMKKCIS6I7AG9wLmPs5Ou3T0Ia0Xx4/7xazs0rH4NCVpIceSYc3v6evR37pp8MsFTC3BzjL1V3slTnmitC1KSNM8ndGRUg1nsCBkJysnR3HpX6SHuCH+UzOuMxEjwiPdSRnzJPEbTHa1HqMfTkTJMbm4zhp7W4/ozX4TtjUB0ql6NoQE2n0Z3aYgR2C78TmzaPQun8EgredWnCID1FedyexaNcw4HyZ2rXlcvG3rBzSwLHH5PePT9skyqy6KtIaL0MlAP556ilgUeyCZfCNdTmzCvPDZuqaeLRezWDdsKnRfTkxIW80QWlmZ6sW0hynJV5JN2Oghk9Tr+QzgV4ZF68FHwoU9YXCTyX4w5iTYq/GjvfTBqB3VSGPOz3PwU7r47tmaYzPj+I44zqktgxyuxDo=
|
- secure: SEqTg6WoGPPpcWzJ03ZfcSBb3nZ2Mdhug0ec2PszuzYO3libCb9usiqi+jils9z6qyXsL6ecz8HYazDGOUepnubhIpI5otLgfn9XiapjMT06Bj//AjbKpH7eu3TJSpJMzoRHZrKIE1y9ZKIBqKwl9Xs7ko+1oa+MLhrLuxXkoi0JqRB5UzkQtJRDoxVNjysnLQn+hsfnm+yuqPHZd2+Loy++q//WHuf9bwJrlkXn2ICYQIX5oQGlxNO6ui+OZklb0YknvyO5GdQeoKaHYru3MMKKCIS6I7AG9wLmPs5Ou3T0Ia0Xx4/7xazs0rH4NCVpIceSYc3v6evR37pp8MsFTC3BzjL1V3slTnmitC1KSNM8ndGRUg1nsCBkJysnR3HpX6SHuCH+UzOuMxEjwiPdSRnzJPEbTHa1HqMfTkTJMbm4zhp7W4/ozX4TtjUB0ql6NoQE2n0Z3aYgR2C78TmzaPQun8EgredWnCID1FedyexaNcw4HyZ2rXlcvG3rBzSwLHH5PePT9skyqy6KtIaL0MlAP556ilgUeyCZfCNdTmzCvPDZuqaeLRezWDdsKnRfTkxIW80QWlmZ6sW0hynJV5JN2Oghk9Tr+QzgV4ZF68FHwoU9YXCTyX4w5iTYq/GjvfTBqB3VSGPOz3PwU7r47tmaYzPj+I44zqktgxyuxDo=
|
||||||
addons:
|
addons:
|
||||||
chrome: stable
|
chrome: stable
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- libgconf-2-4 # for cypress
|
||||||
before_script: npm run bundle
|
before_script: npm run bundle
|
||||||
script: npm test && ([ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run e2e-ci || npm
|
script: npm test && ([ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run e2e-ci || npm
|
||||||
run e2e)
|
run e2e)
|
||||||
|
@ -27,9 +34,6 @@ deploy:
|
||||||
api_key: "$NPM_TOKEN"
|
api_key: "$NPM_TOKEN"
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
- provider: script
|
|
||||||
skip_cleanup: true
|
|
||||||
script: cd cli && yarn install && yarn ci-publish || true
|
|
||||||
- provider: script
|
- provider: script
|
||||||
skip_cleanup: true
|
skip_cleanup: true
|
||||||
script: yarn deploy:demo
|
script: yarn deploy:demo
|
||||||
|
|
208
CHANGELOG.md
208
CHANGELOG.md
|
@ -1,3 +1,211 @@
|
||||||
|
# [2.0.0-rc.18](https://github.com/Redocly/redoc/compare/v2.0.0-rc.17...v2.0.0-rc.18) (2019-10-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add oneOf buttons vertical space when wrapped to new line ([cd9fd61](https://github.com/Redocly/redoc/commit/cd9fd61))
|
||||||
|
* improve mime-type dropdown font ([ce885f8](https://github.com/Redocly/redoc/commit/ce885f8))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.17](https://github.com/Redocly/redoc/compare/v2.0.0-rc.16...v2.0.0-rc.17) (2019-10-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* active menu item scroll into view ([0a01e9a](https://github.com/Redocly/redoc/commit/0a01e9a))
|
||||||
|
* changed several components style font-family to monospace ([#1063](https://github.com/Redocly/redoc/issues/1063)) ([0c20e64](https://github.com/Redocly/redoc/commit/0c20e64)), closes [#909](https://github.com/Redocly/redoc/issues/909)
|
||||||
|
* no quotes for default values in header fields. ([#1059](https://github.com/Redocly/redoc/issues/1059)) ([b5af71d](https://github.com/Redocly/redoc/commit/b5af71d))
|
||||||
|
* types over-pluralization ([#1057](https://github.com/Redocly/redoc/issues/1057)) ([4494f80](https://github.com/Redocly/redoc/commit/4494f80)), closes [#1053](https://github.com/Redocly/redoc/issues/1053)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* added support for file paths as --options cli argument ([#1049](https://github.com/Redocly/redoc/issues/1049)) ([4adb927](https://github.com/Redocly/redoc/commit/4adb927))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.16](https://github.com/Redocly/redoc/compare/v2.0.0-rc.15...v2.0.0-rc.16) (2019-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix scrollYOffset when SSR ([d09c1c1](https://github.com/Redocly/redoc/commit/d09c1c1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.15](https://github.com/Redocly/redoc/compare/v2.0.0-rc.14...v2.0.0-rc.15) (2019-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* auth section appears twice ([5aa7784](https://github.com/Redocly/redoc/commit/5aa7784)), closes [#818](https://github.com/Redocly/redoc/issues/818)
|
||||||
|
* clicking on group title breaks first tag ([4649683](https://github.com/Redocly/redoc/commit/4649683)), closes [#1034](https://github.com/Redocly/redoc/issues/1034)
|
||||||
|
* do not crash on empty scopes ([e787d9e](https://github.com/Redocly/redoc/commit/e787d9e)), closes [#1044](https://github.com/Redocly/redoc/issues/1044)
|
||||||
|
* false-positive recursive detection with allOf at the same level ([faa74d6](https://github.com/Redocly/redoc/commit/faa74d6))
|
||||||
|
* fix scrollYOffset when SSR ([21258a5](https://github.com/Redocly/redoc/commit/21258a5))
|
||||||
|
* left menu item before group is not highligted ([67e2a8f](https://github.com/Redocly/redoc/commit/67e2a8f)), closes [#1033](https://github.com/Redocly/redoc/issues/1033)
|
||||||
|
* remove excessive whitespace between md sections on small screens ([e318fb3](https://github.com/Redocly/redoc/commit/e318fb3)), closes [#874](https://github.com/Redocly/redoc/issues/874)
|
||||||
|
* use url-template dependency ([#1008](https://github.com/Redocly/redoc/issues/1008)) ([32a464a](https://github.com/Redocly/redoc/commit/32a464a)), closes [#1007](https://github.com/Redocly/redoc/issues/1007)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **cli:** added support for JSON string value for --options CLI argument ([#1047](https://github.com/Redocly/redoc/issues/1047)) ([2a28130](https://github.com/Redocly/redoc/commit/2a28130)), closes [#797](https://github.com/Redocly/redoc/issues/797)
|
||||||
|
* **cli:** add `disableGoogleFont` parameter to cli ([#1045](https://github.com/Redocly/redoc/issues/1045)) ([aceb343](https://github.com/Redocly/redoc/commit/aceb343))
|
||||||
|
* new option expandDefaultServerVariables ([#1014](https://github.com/Redocly/redoc/issues/1014)) ([0360dce](https://github.com/Redocly/redoc/commit/0360dce))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.14](https://github.com/Redocly/redoc/compare/v2.0.0-rc.13...v2.0.0-rc.14) (2019-08-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix escaping JSON string values ([58cb20d](https://github.com/Redocly/redoc/commit/58cb20d)), closes [#999](https://github.com/Redocly/redoc/issues/999)
|
||||||
|
* revert expanding default server variables ([7849f7f](https://github.com/Redocly/redoc/commit/7849f7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.13](https://github.com/Redocly/redoc/compare/v2.0.0-rc.12...v2.0.0-rc.13) (2019-08-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* enum list doesn't wrap ([bfbb0c1](https://github.com/Redocly/redoc/commit/bfbb0c1)), closes [#993](https://github.com/Redocly/redoc/issues/993)
|
||||||
|
* incorrect serialization of some parameter samples ([aba45db](https://github.com/Redocly/redoc/commit/aba45db)), closes [#992](https://github.com/Redocly/redoc/issues/992)
|
||||||
|
* support json serialization for parameter examples ([1367380](https://github.com/Redocly/redoc/commit/1367380)), closes [#934](https://github.com/Redocly/redoc/issues/934)
|
||||||
|
* unify accordion icons for responses section ([2afc2e4](https://github.com/Redocly/redoc/commit/2afc2e4)), closes [#975](https://github.com/Redocly/redoc/issues/975)
|
||||||
|
* update to core.js 3 ([9e3375d](https://github.com/Redocly/redoc/commit/9e3375d)), closes [#997](https://github.com/Redocly/redoc/issues/997)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.12](https://github.com/Redocly/redoc/compare/v2.0.0-rc.11...v2.0.0-rc.12) (2019-07-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* rename ObjectDescription to SchemaDefinition as discussed ([4496622](https://github.com/Redocly/redoc/commit/4496622))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.11](https://github.com/Redocly/redoc/compare/v2.0.0-rc.10...v2.0.0-rc.11) (2019-07-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* do not add extra slashes to pattern ([70d1ee9](https://github.com/Redocly/redoc/commit/70d1ee9)), closes [#983](https://github.com/Redocly/redoc/issues/983)
|
||||||
|
* dropdown fixes related to object description ([0504ad4](https://github.com/Redocly/redoc/commit/0504ad4))
|
||||||
|
* incorrect serialization of parameter sample with hypen ([f7dd658](https://github.com/Redocly/redoc/commit/f7dd658))
|
||||||
|
* redoc-cli: Add missing content type header on compressed responses of `/` path
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* menu items from tags + md extension for Schema Definition ([#681](https://github.com/Redocly/redoc/pull/681))
|
||||||
|
* new option `menuToggle` - fold active MenuItem if clicked ([#963](https://github.com/Redocly/redoc/issues/963))
|
||||||
|
* Add option for skipping quotes in enums `enumSkipQuotes` ([#968](https://github.com/Redocly/redoc/issues/968)) ([afc7e36](https://github.com/Redocly/redoc/commit/afc7e36))
|
||||||
|
* add `sampleCollapseLevel` option ([#937](https://github.com/Redocly/redoc/issues/937)) ([d3f1c16](https://github.com/Redocly/redoc/commit/d3f1c16))
|
||||||
|
|
||||||
|
# [2.0.0-rc.10](https://github.com/Redocly/redoc/compare/v2.0.0-rc.9...v2.0.0-rc.10) (2019-07-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* broken headings with single quote ([51d3b9b](https://github.com/Redocly/redoc/commit/51d3b9b)), closes [#955](https://github.com/Redocly/redoc/issues/955)
|
||||||
|
* fix fields table overflow if deeply nested with long title ([12b7057](https://github.com/Redocly/redoc/commit/12b7057))
|
||||||
|
* hide empty example when it is not defined ([4bd499f](https://github.com/Redocly/redoc/commit/4bd499f))
|
||||||
|
* markdown in examples descriptions + minor ui tweaks ([f52d9e8](https://github.com/Redocly/redoc/commit/f52d9e8))
|
||||||
|
* organize response examples in dropdown and display description ([995e557](https://github.com/Redocly/redoc/commit/995e557))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.9](https://github.com/Redocly/redoc/compare/v2.0.0-rc.8-1...v2.0.0-rc.9) (2019-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix regression double slashes added to full URL display ([f29a4fe](https://github.com/Redocly/redoc/commit/f29a4fe))
|
||||||
|
* IE11, add missing Object.assign polyfill ([888f04e](https://github.com/Redocly/redoc/commit/888f04e))
|
||||||
|
* serialize parameter example values according to the spec ([#917](https://github.com/Redocly/redoc/issues/917)) ([3939286](https://github.com/Redocly/redoc/commit/3939286))
|
||||||
|
* styled-component style error in tabs ([#946](https://github.com/Redocly/redoc/issues/946)) ([c488bbf](https://github.com/Redocly/redoc/commit/c488bbf))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add x-additionalPropertiesName ([#622](https://github.com/Redocly/redoc/issues/622)) ([#944](https://github.com/Redocly/redoc/issues/944)) ([0eb1e66](https://github.com/Redocly/redoc/commit/0eb1e66))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.8-1](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.8...v2.0.0-rc.8-1) (2019-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* crash with empty servers with redoc-cli ([3d52b39](https://github.com/Rebilly/ReDoc/commit/3d52b39))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.8](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.7...v2.0.0-rc.8) (2019-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix broken CLI again ([4e12b5d](https://github.com/Rebilly/ReDoc/commit/4e12b5d))
|
||||||
|
* fix logo gutter bg ([81896d3](https://github.com/Rebilly/ReDoc/commit/81896d3))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.7](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.6...v2.0.0-rc.7) (2019-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* crash in node due to broken URL parsing ([8df2b97](https://github.com/Rebilly/ReDoc/commit/8df2b97))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.6](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.5...v2.0.0-rc.6) (2019-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* broken schema talbes with long enums ([3a74b74](https://github.com/Rebilly/ReDoc/commit/3a74b74))
|
||||||
|
* deep linking sometimes not working when sent over messengers ([2491d97](https://github.com/Rebilly/ReDoc/commit/2491d97))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [2.0.0-rc.5](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.4...v2.0.0-rc.5) (2019-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* change fontFamily for EndpointInfo ([#866](https://github.com/Rebilly/ReDoc/issues/866)) ([851b133](https://github.com/Rebilly/ReDoc/commit/851b133))
|
||||||
|
* clean up field values display ([#855](https://github.com/Rebilly/ReDoc/issues/855)) ([5c91590](https://github.com/Rebilly/ReDoc/commit/5c91590))
|
||||||
|
* discriminator and oneOf title fix ([a3d7d7a](https://github.com/Rebilly/ReDoc/commit/a3d7d7a))
|
||||||
|
* encode x-www-form-urlencoded examples correctly ([65930ad](https://github.com/Rebilly/ReDoc/commit/65930ad)), closes [#870](https://github.com/Rebilly/ReDoc/issues/870)
|
||||||
|
* fix redoc-cli broken dependencies ([81a7568](https://github.com/Rebilly/ReDoc/commit/81a7568))
|
||||||
|
* IE11 add missing fetch and URL polyfills ([d2ce1bd](https://github.com/Rebilly/ReDoc/commit/d2ce1bd)), closes [#875](https://github.com/Rebilly/ReDoc/issues/875)
|
||||||
|
* ignore empty x-tagGroups array ([#869](https://github.com/Rebilly/ReDoc/issues/869)) ([4366a0d](https://github.com/Rebilly/ReDoc/commit/4366a0d))
|
||||||
|
* incorrect detected schema title for deeply inherited schemas ([7d7b4e3](https://github.com/Rebilly/ReDoc/commit/7d7b4e3))
|
||||||
|
* pluralize arrray of types ([fdcac30](https://github.com/Rebilly/ReDoc/commit/fdcac30))
|
||||||
|
* remove huge space after Authentication section ([548fae3](https://github.com/Rebilly/ReDoc/commit/548fae3)), closes [#872](https://github.com/Rebilly/ReDoc/issues/872)
|
||||||
|
* remove query string from server URL ([#895](https://github.com/Rebilly/ReDoc/issues/895)) ([64453ff](https://github.com/Rebilly/ReDoc/commit/64453ff))
|
||||||
|
* remove tabs top margin ([5c187f3](https://github.com/Rebilly/ReDoc/commit/5c187f3))
|
||||||
|
* right panel code samples bg color ([de2aed2](https://github.com/Rebilly/ReDoc/commit/de2aed2))
|
||||||
|
* tidy up non-redoc vendor extension presentation ([#847](https://github.com/Rebilly/ReDoc/issues/847)) ([b21cd3d](https://github.com/Rebilly/ReDoc/commit/b21cd3d))
|
||||||
|
* update apiKey in to be titleize ([#902](https://github.com/Rebilly/ReDoc/issues/902)) ([35df477](https://github.com/Rebilly/ReDoc/commit/35df477))
|
||||||
|
* **cli:** add node-libs-browser to the deps ([6c79901](https://github.com/Rebilly/ReDoc/commit/6c79901)), closes [#850](https://github.com/Rebilly/ReDoc/issues/850)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add hideSingleRequestSampleTab option ([4550e4d](https://github.com/Rebilly/ReDoc/commit/4550e4d))
|
||||||
|
* add lineHeight config for headings ([#894](https://github.com/Rebilly/ReDoc/issues/894)) ([5dd5d6d](https://github.com/Rebilly/ReDoc/commit/5dd5d6d))
|
||||||
|
* basic UI labels configuration ([b0e660e](https://github.com/Rebilly/ReDoc/commit/b0e660e)). Can be used for translations later.
|
||||||
|
* add logo gutter to the theme ([82c0cb1a](https://github.com/Rebilly/ReDoc/commit/82c0cb1a)).
|
||||||
|
|
||||||
# [2.0.0-rc.4](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.3...v2.0.0-rc.4) (2019-03-15)
|
# [2.0.0-rc.4](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.3...v2.0.0-rc.4) (2019-03-15)
|
||||||
|
|
||||||
|
|
||||||
|
|
65
README.md
65
README.md
|
@ -1,27 +1,27 @@
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img alt="ReDoc logo" src="https://raw.githubusercontent.com/Rebilly/ReDoc/master/docs/images/redoc-logo.png" width="400px" />
|
<img alt="ReDoc logo" src="https://raw.githubusercontent.com/Redocly/redoc/master/docs/images/redoc-logo.png" width="400px" />
|
||||||
|
|
||||||
**OpenAPI/Swagger-generated API Reference Documentation**
|
**OpenAPI/Swagger-generated API Reference Documentation**
|
||||||
|
|
||||||
[](https://travis-ci.org/Rebilly/ReDoc) [](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [](https://david-dm.org/Rebilly/ReDoc) [](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [](https://www.npmjs.com/package/redoc) [](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
|
[](https://travis-ci.org/Redocly/redoc) [](https://coveralls.io/github/Redocly/redoc?branch=master) [](https://david-dm.org/Redocly/redoc) [](https://david-dm.org/Redocly/redoc#info=devDependencies) [](https://www.npmjs.com/package/redoc) [](https://github.com/Redocly/redoc/blob/master/LICENSE)
|
||||||
|
|
||||||
[](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc) [](https://hub.docker.com/r/redocly/redoc/)
|
[](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc) [](https://hub.docker.com/r/redocly/redoc/)
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
**This is README for `2.0` version of ReDoc (React based). README for `1.x` version is on the branch [v1.x](https://github.com/Rebilly/ReDoc/tree/v1.x)**
|
**This is README for `2.0` version of ReDoc (React based). README for `1.x` version is on the branch [v1.x](https://github.com/Redocly/redoc/tree/v1.x)**
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## [Live demo](http://rebilly.github.io/ReDoc/)
|
## [Live demo](http://redocly.github.io/redoc/)
|
||||||
|
|
||||||
[<img alt="Deploy to Github" src="http://i.imgur.com/YZmaqk3.png" height="60px">](https://github.com/Rebilly/generator-openapi-repo#generator-openapi-repo--) [<img alt="ReDoc as a service" src="http://i.imgur.com/edqdCv6.png" height="60px">](https://redoc.ly) [<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
|
[<img alt="Deploy to Github" src="http://i.imgur.com/YZmaqk3.png" height="60px">](https://github.com/Rebilly/generator-openapi-repo#generator-openapi-repo--) [<img alt="ReDoc as a service" src="http://i.imgur.com/edqdCv6.png" height="60px">](https://redoc.ly) [<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Extremely easy deployment
|
- Extremely easy deployment
|
||||||
- [redoc-cli](https://github.com/Rebilly/ReDoc/blob/master/cli/README.md) with ability to bundle your docs into **zero-dependency** HTML file
|
- [redoc-cli](https://github.com/Redocly/redoc/blob/master/cli/README.md) with ability to bundle your docs into **zero-dependency** HTML file
|
||||||
- Server Side Rendering ready
|
- Server Side Rendering ready
|
||||||
- The widest OpenAPI v2.0 features support (yes, it supports even `discriminator`) <br>
|
- The widest OpenAPI v2.0 features support (yes, it supports even `discriminator`) <br>
|
||||||

|

|
||||||
|
@ -37,7 +37,7 @@
|
||||||
- Branding/customizations via [`theme` option](#redoc-options-object)
|
- Branding/customizations via [`theme` option](#redoc-options-object)
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
- [x] ~~[OpenAPI v3.0 support](https://github.com/Rebilly/ReDoc/issues/312)~~
|
- [x] ~~[OpenAPI v3.0 support](https://github.com/Redocly/redoc/issues/312)~~
|
||||||
- [x] ~~performance optimizations~~
|
- [x] ~~performance optimizations~~
|
||||||
- [x] ~~better navigation (menu improvements + search)~~
|
- [x] ~~better navigation (menu improvements + search)~~
|
||||||
- [x] ~~React rewrite~~
|
- [x] ~~React rewrite~~
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
- particular release, e.g. `v2.0.0-alpha.15`: https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.17/bundles/redoc.standalone.js
|
- particular release, e.g. `v2.0.0-alpha.15`: https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.17/bundles/redoc.standalone.js
|
||||||
- `next` release: https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js
|
- `next` release: https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js
|
||||||
|
|
||||||
Additionally, all the 1.x releases are hosted on our GitHub Pages-based **CDN**:
|
Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(deprecated)**:
|
||||||
- particular release, e.g. `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
|
- particular release, e.g. `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
|
||||||
- `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js
|
- `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js
|
||||||
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js - it will point to latest 1.x.x release since 2.x releases are not hosted on this CDN but on unpkg.
|
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js - it will point to latest 1.x.x release since 2.x releases are not hosted on this CDN but on unpkg.
|
||||||
|
@ -138,7 +138,7 @@ For npm:
|
||||||
|
|
||||||
Install peer dependencies required by ReDoc if you don't have them installed already:
|
Install peer dependencies required by ReDoc if you don't have them installed already:
|
||||||
|
|
||||||
npm i react react-dom mobx@^4.2.0 styled-components
|
npm i react react-dom mobx@^4.2.0 styled-components core-js
|
||||||
|
|
||||||
Import `RedocStandalone` component from 'redoc' module:
|
Import `RedocStandalone` component from 'redoc' module:
|
||||||
|
|
||||||
|
@ -165,7 +165,7 @@ Also you can pass options:
|
||||||
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
|
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
|
||||||
options={{
|
options={{
|
||||||
nativeScrollbars: true,
|
nativeScrollbars: true,
|
||||||
theme: { colors: { primary { main: '#dd5522' } } },
|
theme: { colors: { primary: { main: '#dd5522' } } },
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
@ -185,6 +185,8 @@ You can also specify `onLoaded` callback which will be called each time Redoc ha
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[**IE11 Support Notes**](docs/usage-with-ie11.md)
|
||||||
|
|
||||||
## The Docker way
|
## The Docker way
|
||||||
|
|
||||||
ReDoc is available as pre-built Docker image in official [Docker Hub repository](https://hub.docker.com/r/redocly/redoc/). You may simply pull & run it:
|
ReDoc is available as pre-built Docker image in official [Docker Hub repository](https://hub.docker.com/r/redocly/redoc/). You may simply pull & run it:
|
||||||
|
@ -198,7 +200,7 @@ Also you may rewrite some predefined environment variables defined in [Dockerfil
|
||||||
|
|
||||||
## ReDoc CLI
|
## ReDoc CLI
|
||||||
|
|
||||||
[See here](https://github.com/Rebilly/ReDoc/blob/master/cli/README.md)
|
[See here](https://github.com/Redocly/redoc/blob/master/cli/README.md)
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
@ -216,31 +218,36 @@ ReDoc makes use of the following [vendor extensions](https://swagger.io/specific
|
||||||
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
|
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
|
||||||
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
||||||
* [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore
|
* [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore
|
||||||
|
* [`x-additionalPropertiesName`](docs/redoc-vendor-extensions.md#x-additionalPropertiesName) - ability to supply a descriptive name for the additional property keys
|
||||||
|
|
||||||
### `<redoc>` options object
|
### `<redoc>` options object
|
||||||
You can use all of the following options with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`.
|
You can use all of the following options with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`.
|
||||||
|
|
||||||
* `untrustedSpec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!**
|
* `disableSearch` - disable search indexing and search box.
|
||||||
|
* `expandDefaultServerVariables` - enable expanding default server variables, default `false`.
|
||||||
|
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
||||||
|
* `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
|
||||||
|
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
||||||
|
* `hideLoading` - do not show loading animation. Useful for small docs.
|
||||||
|
* `hideSingleRequestSampleTab` - do not show the request sample tab for requests with only one sample.
|
||||||
|
* `jsonSampleExpandLevel` - set the default expand level for JSON payload samples (responses and request body). Special value 'all' expands all levels. The default value is `2`.
|
||||||
|
* `lazyRendering` - _Not implemented yet_ ~~if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\redocly.github.io/redoc) for the example.~~
|
||||||
|
* `menuToggle` - if true clicking second time on expanded menu item will collapse it, default `false`.
|
||||||
|
* `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs).
|
||||||
|
* `noAutoAuth` - do not inject Authentication section automatically.
|
||||||
|
* `onlyRequiredInSamples` - shows only required fields in request samples.
|
||||||
|
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one.
|
||||||
|
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
|
||||||
* `scrollYOffset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc;
|
* `scrollYOffset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc;
|
||||||
`scrollYOffset` can be specified in various ways:
|
`scrollYOffset` can be specified in various ways:
|
||||||
* **number**: A fixed number of pixels to be used as offset;
|
* **number**: A fixed number of pixels to be used as offset.
|
||||||
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset;
|
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset.
|
||||||
* **function**: A getter function. Must return a number representing the offset (in pixels);
|
* **function**: A getter function. Must return a number representing the offset (in pixels).
|
||||||
|
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display.
|
||||||
|
* `sortPropsAlphabetically` - sort properties alphabetically.
|
||||||
* `suppressWarnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console).
|
* `suppressWarnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console).
|
||||||
* `lazyRendering` - _Not implemented yet_ ~~if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\rebilly.github.io/ReDoc) for the example.~~
|
* `theme` - ReDoc theme. Not documented yet. For details check source code: [theme.ts](https://github.com/Redocly/redoc/blob/master/src/theme.ts).
|
||||||
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
* `untrustedSpec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!**
|
||||||
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
|
|
||||||
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
|
|
||||||
* `sortPropsAlphabetically` - sort properties alphabetically
|
|
||||||
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display
|
|
||||||
* `noAutoAuth` - do not inject Authentication section automatically
|
|
||||||
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one
|
|
||||||
* `hideLoading` - do not show loading animation. Useful for small docs
|
|
||||||
* `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs)
|
|
||||||
* `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
|
|
||||||
* `disableSearch` - disable search indexing and search box
|
|
||||||
* `onlyRequiredInSamples` - shows only required fields in request samples.
|
|
||||||
* `theme` - ReDoc theme. Not documented yet. For details check source code: [theme.ts](https://github.com/Rebilly/ReDoc/blob/master/src/theme.ts)
|
|
||||||
|
|
||||||
## Advanced usage of standalone version
|
## Advanced usage of standalone version
|
||||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
||||||
|
|
1
bundles/1ff7c6b6cdc98f12b62b.worker.js.map
Normal file
1
bundles/1ff7c6b6cdc98f12b62b.worker.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
bundles/da34347ef16856d28753.worker.js.map
Normal file
1
bundles/da34347ef16856d28753.worker.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4634
bundles/redoc.lib.js
4634
bundles/redoc.lib.js
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,6 +1,6 @@
|
||||||
# redoc-cli
|
# redoc-cli
|
||||||
|
|
||||||
**[ReDoc](https://github.com/Rebilly/ReDoc)'s Command Line Interface**
|
**[ReDoc](https://github.com/Redocly/redoc)'s Command Line Interface**
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
You can use redoc cli by installing `redoc-cli` globally or using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b).
|
You can use redoc cli by installing `redoc-cli` globally or using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b).
|
||||||
|
@ -16,6 +16,7 @@ Some examples:
|
||||||
|
|
||||||
- Bundle with main color changed to `orange`: <br> `$ redoc-cli bundle [spec] --options.theme.colors.primary.main=orange`
|
- Bundle with main color changed to `orange`: <br> `$ redoc-cli bundle [spec] --options.theme.colors.primary.main=orange`
|
||||||
- Serve with `nativeScrollbars` option set to true: <br> `$ redoc-cli serve [spec] --options.nativeScrollbars`
|
- Serve with `nativeScrollbars` option set to true: <br> `$ redoc-cli serve [spec] --options.nativeScrollbars`
|
||||||
- Bundle using custom template (check [default template](https://github.com/Rebilly/ReDoc/blob/master/cli/template.hbs) for reference): <br> `$ redoc-cli bundle [spec] -t custom.hbs`
|
- Bundle using custom template (check [default template](https://github.com/Redocly/redoc/blob/master/cli/template.hbs) for reference): <br> `$ redoc-cli bundle [spec] -t custom.hbs`
|
||||||
|
- Bundle using custom template and add custom `templateOptions`: <br> `$ redoc-cli bundle [spec] -t custom.hbs --templateOptions.metaDescription "Page meta description"`
|
||||||
|
|
||||||
For more details run `redoc-cli --help`.
|
For more details run `redoc-cli --help`.
|
||||||
|
|
111
cli/index.ts
111
cli/index.ts
|
@ -14,7 +14,7 @@ import * as zlib from 'zlib';
|
||||||
import { createStore, loadAndBundleSpec, Redoc } from 'redoc';
|
import { createStore, loadAndBundleSpec, Redoc } from 'redoc';
|
||||||
|
|
||||||
import { watch } from 'chokidar';
|
import { watch } from 'chokidar';
|
||||||
import { createReadStream, existsSync, readFileSync, ReadStream, writeFileSync } from 'fs';
|
import { createReadStream, existsSync, readFileSync, ReadStream, writeFileSync, lstatSync } from 'fs';
|
||||||
import * as mkdirp from 'mkdirp';
|
import * as mkdirp from 'mkdirp';
|
||||||
|
|
||||||
import * as YargsParser from 'yargs';
|
import * as YargsParser from 'yargs';
|
||||||
|
@ -25,7 +25,10 @@ interface Options {
|
||||||
cdn?: boolean;
|
cdn?: boolean;
|
||||||
output?: string;
|
output?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
disableGoogleFont?: boolean;
|
||||||
|
port?: number;
|
||||||
templateFileName?: string;
|
templateFileName?: string;
|
||||||
|
templateOptions?: any;
|
||||||
redocOptions?: any;
|
redocOptions?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,13 +64,16 @@ YargsParser.command(
|
||||||
return yargs;
|
return yargs;
|
||||||
},
|
},
|
||||||
async argv => {
|
async argv => {
|
||||||
const config = {
|
const config: Options = {
|
||||||
ssr: argv.ssr,
|
ssr: argv.ssr as boolean,
|
||||||
watch: argv.watch,
|
watch: argv.watch as boolean,
|
||||||
templateFileName: argv.template,
|
templateFileName: argv.template as string,
|
||||||
redocOptions: argv.options || {},
|
templateOptions: argv.templateOptions || {},
|
||||||
|
redocOptions: getObjectOrJSON(argv.options),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(config);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await serve(argv.port, argv.spec, config);
|
await serve(argv.port, argv.spec, config);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -96,6 +102,12 @@ YargsParser.command(
|
||||||
default: 'ReDoc documentation',
|
default: 'ReDoc documentation',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
yargs.options('disableGoogleFont', {
|
||||||
|
describe: 'Disable Google Font',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|
||||||
yargs.option('cdn', {
|
yargs.option('cdn', {
|
||||||
describe: 'Do not include ReDoc source code into html page, use link to CDN instead',
|
describe: 'Do not include ReDoc source code into html page, use link to CDN instead',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
@ -105,14 +117,16 @@ YargsParser.command(
|
||||||
yargs.demandOption('spec');
|
yargs.demandOption('spec');
|
||||||
return yargs;
|
return yargs;
|
||||||
},
|
},
|
||||||
async argv => {
|
async (argv: any) => {
|
||||||
const config = {
|
const config = {
|
||||||
ssr: true,
|
ssr: true,
|
||||||
output: argv.o,
|
output: argv.o as string,
|
||||||
cdn: argv.cdn,
|
cdn: argv.cdn as boolean,
|
||||||
title: argv.title,
|
title: argv.title as string,
|
||||||
templateFileName: argv.template,
|
disableGoogleFont: argv.disableGoogleFont as boolean,
|
||||||
redocOptions: argv.options || {},
|
templateFileName: argv.template as string,
|
||||||
|
templateOptions: argv.templateOptions || {},
|
||||||
|
redocOptions: getObjectOrJSON(argv.options),
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -148,7 +162,9 @@ async function serve(port: any, pathToSpec: any, options: any = {}) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else if (request.url === '/') {
|
} else if (request.url === '/') {
|
||||||
respondWithGzip(pageHTML, request, response);
|
respondWithGzip(pageHTML, request, response, {
|
||||||
|
'Content-Type': 'text/html',
|
||||||
|
});
|
||||||
} else if (request.url === '/spec.json') {
|
} else if (request.url === '/spec.json') {
|
||||||
const specStr = JSON.stringify(spec, null, 2);
|
const specStr = JSON.stringify(spec, null, 2);
|
||||||
respondWithGzip(specStr, request, response, {
|
respondWithGzip(specStr, request, response, {
|
||||||
|
@ -170,21 +186,34 @@ async function serve(port: any, pathToSpec: any, options: any = {}) {
|
||||||
if (options.watch && existsSync(pathToSpec)) {
|
if (options.watch && existsSync(pathToSpec)) {
|
||||||
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
||||||
const watchOptions = {
|
const watchOptions = {
|
||||||
ignored: /(^|[\/\\])\../,
|
ignored: [/(^|[\/\\])\../, /___jb_[a-z]+___$/],
|
||||||
|
ignoreInitial: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const watcher = watch(pathToSpecDirectory, watchOptions);
|
const watcher = watch(pathToSpecDirectory, watchOptions);
|
||||||
const log = console.log.bind(console);
|
const log = console.log.bind(console);
|
||||||
|
|
||||||
|
const handlePath = async path => {
|
||||||
|
try {
|
||||||
|
spec = await loadAndBundleSpec(pathToSpec);
|
||||||
|
pageHTML = await getPageHTML(spec, pathToSpec, options);
|
||||||
|
log('Updated successfully');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error while updating: ', e.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
watcher
|
watcher
|
||||||
.on('change', async path => {
|
.on('change', async path => {
|
||||||
log(`${path} changed, updating docs`);
|
log(`${path} changed, updating docs`);
|
||||||
try {
|
handlePath(path);
|
||||||
spec = await loadAndBundleSpec(pathToSpec);
|
})
|
||||||
pageHTML = await getPageHTML(spec, pathToSpec, options);
|
.on('add', async path => {
|
||||||
log('Updated successfully');
|
log(`File ${path} added, updating docs`);
|
||||||
} catch (e) {
|
handlePath(path);
|
||||||
console.error('Error while updating: ', e.message);
|
})
|
||||||
}
|
.on('addDir', path => {
|
||||||
|
log(`↗ Directory ${path} added. Files in here will trigger reload.`);
|
||||||
})
|
})
|
||||||
.on('error', error => console.error(`Watcher error: ${error}`))
|
.on('error', error => console.error(`Watcher error: ${error}`))
|
||||||
.on('ready', () => log(`👀 Watching ${pathToSpecDirectory} for changes...`));
|
.on('ready', () => log(`👀 Watching ${pathToSpecDirectory} for changes...`));
|
||||||
|
@ -208,7 +237,15 @@ async function bundle(pathToSpec, options: any = {}) {
|
||||||
async function getPageHTML(
|
async function getPageHTML(
|
||||||
spec: any,
|
spec: any,
|
||||||
pathToSpec: string,
|
pathToSpec: string,
|
||||||
{ ssr, cdn, title, templateFileName, redocOptions = {} }: Options,
|
{
|
||||||
|
ssr,
|
||||||
|
cdn,
|
||||||
|
title,
|
||||||
|
disableGoogleFont,
|
||||||
|
templateFileName,
|
||||||
|
templateOptions,
|
||||||
|
redocOptions = {},
|
||||||
|
}: Options,
|
||||||
) {
|
) {
|
||||||
let html;
|
let html;
|
||||||
let css;
|
let css;
|
||||||
|
@ -242,15 +279,17 @@ async function getPageHTML(
|
||||||
ssr
|
ssr
|
||||||
? 'hydrate(__redoc_state, container);'
|
? 'hydrate(__redoc_state, container);'
|
||||||
: `init("spec.json", ${JSON.stringify(redocOptions)}, container)`
|
: `init("spec.json", ${JSON.stringify(redocOptions)}, container)`
|
||||||
};
|
};
|
||||||
|
|
||||||
</script>`,
|
</script>`,
|
||||||
redocHead: ssr
|
redocHead: ssr
|
||||||
? (cdn
|
? (cdn
|
||||||
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
|
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
|
||||||
: `<script>${redocStandaloneSrc}</script>`) + css
|
: `<script>${redocStandaloneSrc}</script>`) + css
|
||||||
: '<script src="redoc.standalone.js"></script>',
|
: '<script src="redoc.standalone.js"></script>',
|
||||||
title,
|
title,
|
||||||
|
disableGoogleFont,
|
||||||
|
templateOptions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,3 +351,25 @@ function handleError(error: Error) {
|
||||||
console.error(error.stack);
|
console.error(error.stack);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getObjectOrJSON(options) {
|
||||||
|
switch (typeof options) {
|
||||||
|
case 'object':
|
||||||
|
return options;
|
||||||
|
case 'string':
|
||||||
|
try {
|
||||||
|
if (existsSync(options) && lstatSync(options).isFile()) {
|
||||||
|
return JSON.parse(readFileSync(options, 'utf-8'));
|
||||||
|
} else {
|
||||||
|
return JSON.parse(options);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(
|
||||||
|
`Encountered error:\n\n${options}\n\nis neither a file with a valid JSON object neither a stringified JSON object.`
|
||||||
|
);
|
||||||
|
handleError(e);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,38 +1,35 @@
|
||||||
{
|
{
|
||||||
"name": "redoc-cli",
|
"name": "redoc-cli",
|
||||||
"version": "0.7.0",
|
"version": "0.9.2",
|
||||||
"description": "ReDoc's Command Line Interface",
|
"description": "ReDoc's Command Line Interface",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"bin": "index.js",
|
"bin": "index.js",
|
||||||
"repository": "https://github.com/Rebilly/ReDoc",
|
"repository": "https://github.com/Redocly/redoc",
|
||||||
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^2.0.4",
|
"chokidar": "^3.0.2",
|
||||||
"handlebars": "^4.0.11",
|
"handlebars": "^4.1.2",
|
||||||
"isarray": "^2.0.4",
|
"isarray": "^2.0.5",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"mobx": "^4.2.0",
|
"mobx": "^4.2.0",
|
||||||
"react": "^16.6.3",
|
"node-libs-browser": "^2.2.1",
|
||||||
"react-dom": "^16.6.3",
|
"react": "^16.8.6",
|
||||||
|
"react-dom": "^16.8.6",
|
||||||
"redoc": "github:BusinessDuck/ReDoc#master",
|
"redoc": "github:BusinessDuck/ReDoc#master",
|
||||||
"styled-components": "^4.1.1",
|
"styled-components": "^4.3.2",
|
||||||
"tslib": "^1.9.3",
|
"tslib": "^1.10.0",
|
||||||
"yargs": "^12.0.5",
|
"yargs": "^13.3.0"
|
||||||
"node-fetch-h2": "^2.3.1-0",
|
|
||||||
"builtin-status-codes": "^3.0.0",
|
|
||||||
"to-arraybuffer": "^1.0.1"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"ci-publish": "ci-publish"
|
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chokidar": "^1.7.5",
|
"@types/chokidar": "^2.1.3",
|
||||||
"@types/handlebars": "^4.0.39",
|
"@types/handlebars": "^4.0.39",
|
||||||
"@types/mkdirp": "^0.5.2",
|
"@types/mkdirp": "^0.5.2"
|
||||||
"ci-publish": "^1.3.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{{{redocHead}}}
|
{{{redocHead}}}
|
||||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
{{#unless disableGoogleFont}}<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">{{/unless}}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
3695
cli/yarn.lock
3695
cli/yarn.lock
File diff suppressed because it is too large
Load Diff
|
@ -19,7 +19,7 @@ Serve local file:
|
||||||
- `PAGE_FAVICON` (default `"favicon.png"`) - URL to page favicon
|
- `PAGE_FAVICON` (default `"favicon.png"`) - URL to page favicon
|
||||||
- `SPEC_URL` (default `"http://petstore.swagger.io/v2/swagger.json"`) - URL to spec
|
- `SPEC_URL` (default `"http://petstore.swagger.io/v2/swagger.json"`) - URL to spec
|
||||||
- `PORT` (default `80`) - nginx port
|
- `PORT` (default `80`) - nginx port
|
||||||
- `REDOC_OPTIONS` - [`<redoc>` tag attributes](https://github.com/Rebilly/ReDoc#redoc-tag-attributes)
|
- `REDOC_OPTIONS` - [`<redoc>` tag attributes](https://github.com/Redocly/redoc#redoc-tag-attributes)
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ class DemoApp extends React.Component<
|
||||||
<>
|
<>
|
||||||
<Heading>
|
<Heading>
|
||||||
<a href=".">
|
<a href=".">
|
||||||
<Logo src="https://github.com/Rebilly/ReDoc/raw/master/docs/images/redoc-logo.png" />
|
<Logo src="https://github.com/Redocly/redoc/raw/master/docs/images/redoc-logo.png" />
|
||||||
</a>
|
</a>
|
||||||
<ControlsContainer>
|
<ControlsContainer>
|
||||||
<ComboBox
|
<ComboBox
|
||||||
|
@ -97,7 +97,7 @@ class DemoApp extends React.Component<
|
||||||
</CorsCheckbox>
|
</CorsCheckbox>
|
||||||
</ControlsContainer>
|
</ControlsContainer>
|
||||||
<iframe
|
<iframe
|
||||||
src="https://ghbtns.com/github-btn.html?user=Rebilly&repo=ReDoc&type=star&count=true&size=large"
|
src="https://ghbtns.com/github-btn.html?user=Redocly&repo=redoc&type=star&count=true&size=large"
|
||||||
frameBorder="0"
|
frameBorder="0"
|
||||||
scrolling="0"
|
scrolling="0"
|
||||||
width="150px"
|
width="150px"
|
||||||
|
|
|
@ -15,16 +15,16 @@ info:
|
||||||
This API is documented in **OpenAPI format** and is based on
|
This API is documented in **OpenAPI format** and is based on
|
||||||
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
||||||
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
|
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
|
||||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||||
|
|
||||||
# OpenAPI Specification
|
# OpenAPI Specification
|
||||||
This API is documented in **OpenAPI format** and is based on
|
This API is documented in **OpenAPI format** and is based on
|
||||||
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
||||||
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
|
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
|
||||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||||
|
|
||||||
# Cross-Origin Resource Sharing
|
# Cross-Origin Resource Sharing
|
||||||
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
||||||
And that allows cross-domain communication from the browser.
|
And that allows cross-domain communication from the browser.
|
||||||
|
@ -38,7 +38,7 @@ info:
|
||||||
OAuth2 - an open protocol to allow secure authorization in a simple
|
OAuth2 - an open protocol to allow secure authorization in a simple
|
||||||
and standard method from web, mobile and desktop applications.
|
and standard method from web, mobile and desktop applications.
|
||||||
|
|
||||||
<security-definitions />
|
<SecurityDefinitions />
|
||||||
|
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
title: Swagger Petstore
|
title: Swagger Petstore
|
||||||
|
@ -46,9 +46,9 @@ info:
|
||||||
contact:
|
contact:
|
||||||
name: API Support
|
name: API Support
|
||||||
email: apiteam@swagger.io
|
email: apiteam@swagger.io
|
||||||
url: https://github.com/Rebilly/ReDoc
|
url: https://github.com/Redocly/redoc
|
||||||
x-logo:
|
x-logo:
|
||||||
url: 'https://rebilly.github.io/ReDoc/petstore-logo.png'
|
url: 'https://redocly.github.io/redoc/petstore-logo.png'
|
||||||
altText: Petstore logo
|
altText: Petstore logo
|
||||||
license:
|
license:
|
||||||
name: Apache 2.0
|
name: Apache 2.0
|
||||||
|
@ -63,6 +63,14 @@ tags:
|
||||||
description: Access to Petstore orders
|
description: Access to Petstore orders
|
||||||
- name: user
|
- name: user
|
||||||
description: Operations about user
|
description: Operations about user
|
||||||
|
- name: pet_model
|
||||||
|
x-displayName: The Pet Model
|
||||||
|
description: |
|
||||||
|
<SchemaDefinition schemaRef="#/components/schemas/Pet" />
|
||||||
|
- name: store_model
|
||||||
|
x-displayName: The Order Model
|
||||||
|
description: |
|
||||||
|
<SchemaDefinition schemaRef="#/components/schemas/Order" exampleRef="#/components/examples/Order" showReadOnly={true} showWriteOnly={true} />
|
||||||
x-tagGroups:
|
x-tagGroups:
|
||||||
- name: General
|
- name: General
|
||||||
tags:
|
tags:
|
||||||
|
@ -71,9 +79,21 @@ x-tagGroups:
|
||||||
- name: User Management
|
- name: User Management
|
||||||
tags:
|
tags:
|
||||||
- user
|
- user
|
||||||
|
- name: Models
|
||||||
|
tags:
|
||||||
|
- pet_model
|
||||||
|
- store_model
|
||||||
paths:
|
paths:
|
||||||
/pet:
|
/pet:
|
||||||
parameters:
|
parameters:
|
||||||
|
- name: Accept-Language
|
||||||
|
in: header
|
||||||
|
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
|
||||||
|
example: en-US
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
default: en-AU
|
||||||
- name: cookieParam
|
- name: cookieParam
|
||||||
in: cookie
|
in: cookie
|
||||||
description: Some cookie
|
description: Some cookie
|
||||||
|
@ -666,6 +686,7 @@ components:
|
||||||
type: string
|
type: string
|
||||||
description: The measured skill for hunting
|
description: The measured skill for hunting
|
||||||
default: lazy
|
default: lazy
|
||||||
|
example: adventurous
|
||||||
enum:
|
enum:
|
||||||
- clueless
|
- clueless
|
||||||
- lazy
|
- lazy
|
||||||
|
@ -754,6 +775,11 @@ components:
|
||||||
description: Indicates whenever order was completed or not
|
description: Indicates whenever order was completed or not
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
readOnly: true
|
||||||
|
rqeuestId:
|
||||||
|
description: Unique Request Id
|
||||||
|
type: string
|
||||||
|
writeOnly: true
|
||||||
xml:
|
xml:
|
||||||
name: Order
|
name: Order
|
||||||
Pet:
|
Pet:
|
||||||
|
@ -866,14 +892,13 @@ components:
|
||||||
as well as digits
|
as well as digits
|
||||||
format: password
|
format: password
|
||||||
minLength: 8
|
minLength: 8
|
||||||
pattern: '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'
|
pattern: '/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/'
|
||||||
example: drowssaP123
|
example: drowssaP123
|
||||||
phone:
|
phone:
|
||||||
description: User phone number in international format
|
description: User phone number in international format
|
||||||
type: string
|
type: string
|
||||||
pattern: '^\+(?:[0-9]-?){6,14}[0-9]$'
|
pattern: '/^\+(?:[0-9]-?){6,14}[0-9]$/'
|
||||||
example: +1-202-555-0192
|
example: +1-202-555-0192
|
||||||
nullable: true
|
|
||||||
userStatus:
|
userStatus:
|
||||||
description: User status
|
description: User status
|
||||||
type: integer
|
type: integer
|
||||||
|
@ -926,3 +951,10 @@ components:
|
||||||
type: apiKey
|
type: apiKey
|
||||||
name: api_key
|
name: api_key
|
||||||
in: header
|
in: header
|
||||||
|
examples:
|
||||||
|
Order:
|
||||||
|
value:
|
||||||
|
quantity: 1,
|
||||||
|
shipDate: 2018-10-19T16:46:45Z,
|
||||||
|
status: placed,
|
||||||
|
complete: false
|
||||||
|
|
|
@ -14,14 +14,14 @@ info:
|
||||||
This API is documented in **OpenAPI format** and is based on
|
This API is documented in **OpenAPI format** and is based on
|
||||||
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
||||||
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
|
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
|
||||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||||
# OpenAPI Specification
|
# OpenAPI Specification
|
||||||
This API is documented in **OpenAPI format** and is based on
|
This API is documented in **OpenAPI format** and is based on
|
||||||
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
||||||
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
|
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
|
||||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||||
# Cross-Origin Resource Sharing
|
# Cross-Origin Resource Sharing
|
||||||
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
||||||
And that allows cross-domain communication from the browser.
|
And that allows cross-domain communication from the browser.
|
||||||
|
@ -39,9 +39,9 @@ info:
|
||||||
termsOfService: 'http://swagger.io/terms/'
|
termsOfService: 'http://swagger.io/terms/'
|
||||||
contact:
|
contact:
|
||||||
email: apiteam@swagger.io
|
email: apiteam@swagger.io
|
||||||
url: https://github.com/Rebilly/ReDoc
|
url: https://github.com/Redocly/redoc
|
||||||
x-logo:
|
x-logo:
|
||||||
url: 'https://rebilly.github.io/ReDoc/petstore-logo.png'
|
url: 'https://redocly.github.io/redoc/petstore-logo.png'
|
||||||
altText: Petstore logo
|
altText: Petstore logo
|
||||||
license:
|
license:
|
||||||
name: Apache 2.0
|
name: Apache 2.0
|
||||||
|
|
|
@ -38,7 +38,6 @@ const babelLoader = mode => ({
|
||||||
['@babel/plugin-syntax-decorators', { legacy: true }],
|
['@babel/plugin-syntax-decorators', { legacy: true }],
|
||||||
'@babel/plugin-syntax-dynamic-import',
|
'@babel/plugin-syntax-dynamic-import',
|
||||||
'@babel/plugin-syntax-jsx',
|
'@babel/plugin-syntax-jsx',
|
||||||
mode !== 'production' ? 'react-hot-loader/babel' : undefined,
|
|
||||||
[
|
[
|
||||||
'babel-plugin-styled-components',
|
'babel-plugin-styled-components',
|
||||||
{
|
{
|
||||||
|
@ -50,6 +49,13 @@ const babelLoader = mode => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const babelHotLoader = {
|
||||||
|
loader: 'babel-loader',
|
||||||
|
options: {
|
||||||
|
plugins: ['react-hot-loader/babel'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
|
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
|
||||||
entry: [
|
entry: [
|
||||||
root('../src/polyfills.ts'),
|
root('../src/polyfills.ts'),
|
||||||
|
@ -94,6 +100,9 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
externals: {
|
externals: {
|
||||||
esprima: 'esprima',
|
esprima: 'esprima',
|
||||||
'node-fetch': 'null',
|
'node-fetch': 'null',
|
||||||
|
'node-fetch-h2': 'null',
|
||||||
|
yaml: 'null',
|
||||||
|
'safe-json-stringify': 'null',
|
||||||
},
|
},
|
||||||
|
|
||||||
module: {
|
module: {
|
||||||
|
@ -102,7 +111,11 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/,
|
test: /\.tsx?$/,
|
||||||
use: [tsLoader(env), babelLoader(mode)],
|
use: compact([
|
||||||
|
mode !== 'production' ? babelHotLoader : undefined,
|
||||||
|
tsLoader(env),
|
||||||
|
babelLoader(mode),
|
||||||
|
]),
|
||||||
exclude: [/node_modules/],
|
exclude: [/node_modules/],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -107,7 +107,7 @@ json
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"title": "Swagger Petstore",
|
"title": "Swagger Petstore",
|
||||||
"x-logo": {
|
"x-logo": {
|
||||||
"url": "https://rebilly.github.io/ReDoc/petstore-logo.png",
|
"url": "https://redocly.github.io/redoc/petstore-logo.png",
|
||||||
"backgroundColor": "#FFFFFF",
|
"backgroundColor": "#FFFFFF",
|
||||||
"altText": "Petstore logo"
|
"altText": "Petstore logo"
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ info:
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
title: "Swagger Petstore"
|
title: "Swagger Petstore"
|
||||||
x-logo:
|
x-logo:
|
||||||
url: "https://rebilly.github.io/ReDoc/petstore-logo.png"
|
url: "https://redocly.github.io/redoc/petstore-logo.png"
|
||||||
backgroundColor: "#FFFFFF"
|
backgroundColor: "#FFFFFF"
|
||||||
altText: "Petstore logo"
|
altText: "Petstore logo"
|
||||||
```
|
```
|
||||||
|
@ -278,3 +278,31 @@ PayPalPayment:
|
||||||
|
|
||||||
In the example above the names of definitions (`PayPalPayment`) are named differently than
|
In the example above the names of definitions (`PayPalPayment`) are named differently than
|
||||||
names in the payload (`paypal`) which is not supported by default `discriminator`.
|
names in the payload (`paypal`) which is not supported by default `discriminator`.
|
||||||
|
|
||||||
|
#### x-additionalPropertiesName
|
||||||
|
**ATTENTION**: This is ReDoc-specific vendor extension. It won't be supported by other tools.
|
||||||
|
|
||||||
|
Extends the `additionalProperties` property of the schema object.
|
||||||
|
|
||||||
|
| Field Name | Type | Description |
|
||||||
|
| :------------- | :------: | :---------- |
|
||||||
|
| x-additionalPropertiesName | string | descriptive name of additional properties keys |
|
||||||
|
|
||||||
|
###### Usage in ReDoc
|
||||||
|
ReDoc uses this extension to display a more descriptive property name in objects with `additionalProperties` when viewing the property list with an `object`.
|
||||||
|
|
||||||
|
###### x-additionalPropertiesName example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
Player:
|
||||||
|
required:
|
||||||
|
- name
|
||||||
|
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
additionalProperties:
|
||||||
|
x-additionalPropertiesName: attribute-name
|
||||||
|
type: string
|
||||||
|
```
|
||||||
|
|
24
docs/usage-with-ie11.md
Normal file
24
docs/usage-with-ie11.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Usage With IE11
|
||||||
|
|
||||||
|
|
||||||
|
## Standalone package
|
||||||
|
|
||||||
|
IE11 is supported by default if you use ReDoc as a standalone package.
|
||||||
|
|
||||||
|
## Usage as a React component
|
||||||
|
|
||||||
|
If you use ReDoc as a React component you should include the following polyfills in your project:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import 'core-js/es6/promise';
|
||||||
|
import 'core-js/fn/array/find';
|
||||||
|
import 'core-js/fn/object/assign';
|
||||||
|
import 'core-js/fn/string/ends-with';
|
||||||
|
import 'core-js/fn/string/starts-with';
|
||||||
|
|
||||||
|
import 'core-js/es6/map';
|
||||||
|
import 'core-js/es6/symbol';
|
||||||
|
|
||||||
|
import 'unfetch/polyfill/index'; // or any other fetch polyfill
|
||||||
|
import 'url-polyfill';
|
||||||
|
```
|
6
e2e/e2e.html
Normal file
6
e2e/e2e.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script src="../bundles/redoc.standalone.js">{}</script>
|
||||||
|
<div id="redoc" />
|
||||||
|
</body>
|
||||||
|
</html>;
|
6
e2e/index.html
Normal file
6
e2e/index.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script src="../bundles/redoc.standalone.js">{}</script>
|
||||||
|
<div id="redoc" />
|
||||||
|
</body>
|
||||||
|
</html>;
|
|
@ -6,7 +6,7 @@ describe('Menu', () => {
|
||||||
it('should have valid items count', () => {
|
it('should have valid items count', () => {
|
||||||
cy.get('.menu-content')
|
cy.get('.menu-content')
|
||||||
.find('li')
|
.find('li')
|
||||||
.should('have.length', 6 + (2 + 8 + 4) + (1 + 8));
|
.should('have.length', 6 + (2 + 8 + 1 + 4 + 2) + (1 + 8));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync active menu items while scroll', () => {
|
it('should sync active menu items while scroll', () => {
|
||||||
|
|
64
e2e/integration/misc.e2e.ts
Normal file
64
e2e/integration/misc.e2e.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// tslint:disable:no-implicit-dependencies
|
||||||
|
import * as yaml from 'yaml-js';
|
||||||
|
|
||||||
|
async function loadSpec(url: string): Promise<any> {
|
||||||
|
const spec = await (await fetch(url)).text();
|
||||||
|
return yaml.load(spec);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initReDoc(win, spec, options = {}) {
|
||||||
|
(win as any).Redoc.init(spec, options, win.document.getElementById('redoc'));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Servers', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit('e2e/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have valid server', () => {
|
||||||
|
cy.window().then(async win => {
|
||||||
|
const spec = await loadSpec('/demo/openapi.yaml');
|
||||||
|
initReDoc(win, spec, {});
|
||||||
|
|
||||||
|
// TODO add cy-data attributes
|
||||||
|
cy.get('[data-section-id="operation/addPet"]').should(
|
||||||
|
'contain',
|
||||||
|
'http://petstore.swagger.io/v2/pet',
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.get('[data-section-id="operation/addPet"]').should(
|
||||||
|
'contain',
|
||||||
|
'http://petstore.swagger.io/sandbox/pet',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have valid server for when servers not provided', () => {
|
||||||
|
cy.window().then(async win => {
|
||||||
|
const spec = await loadSpec('/demo/openapi.yaml');
|
||||||
|
delete spec.servers;
|
||||||
|
initReDoc(win, spec, {});
|
||||||
|
|
||||||
|
// TODO add cy-data attributes
|
||||||
|
cy.get('[data-section-id="operation/addPet"]').should(
|
||||||
|
'contain',
|
||||||
|
'http://localhost:' + win.location.port + '/e2e/pet',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have valid server for when servers not provided at .html pages', () => {
|
||||||
|
cy.visit('e2e/e2e.html');
|
||||||
|
cy.window().then(async win => {
|
||||||
|
const spec = await loadSpec('/demo/openapi.yaml');
|
||||||
|
delete spec.servers;
|
||||||
|
initReDoc(win, spec, {});
|
||||||
|
|
||||||
|
// TODO add cy-data attributes
|
||||||
|
cy.get('[data-section-id="operation/addPet"]').should(
|
||||||
|
'contain',
|
||||||
|
'http://localhost:' + win.location.port + '/e2e/pet',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
139
package.json
139
package.json
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"version": "2.0.0-rc.4",
|
"version": "2.0.0-rc.18",
|
||||||
"description": "ReDoc",
|
"description": "ReDoc",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/Rebilly/ReDoc"
|
"url": "git://github.com/Redocly/redoc"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9",
|
"node": ">=6.9",
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
"start": "webpack-dev-server --mode=development --env.playground --hot --config demo/webpack.config.ts",
|
"start": "webpack-dev-server --mode=development --env.playground --hot --config demo/webpack.config.ts",
|
||||||
"start:prod": "webpack-dev-server --env.playground --mode=production --config demo/webpack.config.ts",
|
"start:prod": "webpack-dev-server --env.playground --mode=production --config demo/webpack.config.ts",
|
||||||
"start:benchmark": "webpack-dev-server --mode=production --env.bench --config demo/webpack.config.ts",
|
"start:benchmark": "webpack-dev-server --mode=production --env.bench --config demo/webpack.config.ts",
|
||||||
"test": "npm run lint && npm run unit && npm run bundlesize && npm run license-check",
|
"test": "npm run lint && npm run unit && npm run license-check",
|
||||||
"unit": "jest --coverage",
|
"unit": "jest --coverage",
|
||||||
"e2e": "cypress run",
|
"e2e": "cypress run",
|
||||||
"e2e-ci": "cypress run --record",
|
"e2e-ci": "cypress run --record",
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"bundle:clean": "rimraf bundles",
|
"bundle:clean": "rimraf bundles",
|
||||||
"bundle:standalone": "webpack --env.standalone --mode=production",
|
"bundle:standalone": "webpack --env.standalone --mode=production",
|
||||||
"bundle:lib": "webpack --mode=production",
|
"bundle:lib": "webpack --mode=production && npm run declarations",
|
||||||
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone",
|
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone",
|
||||||
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
|
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
|
||||||
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json",
|
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json",
|
||||||
|
@ -52,79 +52,81 @@
|
||||||
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ."
|
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.3.4",
|
"@babel/core": "7.6.2",
|
||||||
"@babel/plugin-syntax-decorators": "7.2.0",
|
"@babel/plugin-syntax-decorators": "7.2.0",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/plugin-syntax-jsx": "7.2.0",
|
"@babel/plugin-syntax-jsx": "7.2.0",
|
||||||
"@babel/plugin-syntax-typescript": "7.3.3",
|
"@babel/plugin-syntax-typescript": "7.3.3",
|
||||||
"@cypress/webpack-preprocessor": "4.0.3",
|
"@cypress/webpack-preprocessor": "4.1.0",
|
||||||
"@hot-loader/react-dom": "^16.8.4",
|
"@hot-loader/react-dom": "^16.9.0",
|
||||||
"@types/chai": "4.1.7",
|
"@types/chai": "4.2.3",
|
||||||
"@types/dompurify": "^0.0.32",
|
"@types/dompurify": "^0.0.33",
|
||||||
"@types/enzyme": "^3.9.0",
|
"@types/enzyme": "^3.10.3",
|
||||||
"@types/enzyme-to-json": "^1.5.3",
|
"@types/enzyme-to-json": "^1.5.3",
|
||||||
"@types/jest": "^24.0.11",
|
"@types/jest": "^24.0.18",
|
||||||
"@types/json-pointer": "^1.0.30",
|
"@types/json-pointer": "^1.0.30",
|
||||||
"@types/lodash": "^4.14.122",
|
"@types/lodash": "^4.14.141",
|
||||||
"@types/lunr": "^2.3.2",
|
"@types/lunr": "^2.3.2",
|
||||||
"@types/mark.js": "^8.11.3",
|
"@types/mark.js": "^8.11.4",
|
||||||
"@types/marked": "^0.6.3",
|
"@types/marked": "^0.6.5",
|
||||||
"@types/prismjs": "^1.9.1",
|
"@types/prismjs": "^1.16.0",
|
||||||
"@types/prop-types": "^15.7.0",
|
"@types/prop-types": "^15.7.3",
|
||||||
"@types/react": "^16.8.7",
|
"@types/react": "^16.9.3",
|
||||||
"@types/react-dom": "^16.8.2",
|
"@types/react-dom": "^16.9.1",
|
||||||
"@types/react-hot-loader": "^4.1.0",
|
"@types/react-hot-loader": "^4.1.0",
|
||||||
"@types/react-tabs": "^2.3.1",
|
"@types/react-tabs": "^2.3.1",
|
||||||
"@types/styled-components": "^4.1.12",
|
"@types/styled-components": "^4.1.19",
|
||||||
"@types/tapable": "1.0.4",
|
"@types/tapable": "1.0.4",
|
||||||
"@types/webpack": "^4.4.25",
|
"@types/webpack": "^4.39.2",
|
||||||
"@types/webpack-env": "^1.13.9",
|
"@types/webpack-env": "^1.14.0",
|
||||||
"@types/yargs": "^12.0.9",
|
"@types/yargs": "^13.0.3",
|
||||||
"babel-loader": "8.0.5",
|
"babel-loader": "8.0.6",
|
||||||
"babel-plugin-styled-components": "^1.10.0",
|
"babel-plugin-styled-components": "^1.10.6",
|
||||||
"beautify-benchmark": "^0.2.4",
|
"beautify-benchmark": "^0.2.4",
|
||||||
"bundlesize": "^0.17.1",
|
"bundlesize": "^0.18.0",
|
||||||
"conventional-changelog-cli": "^2.0.12",
|
"conventional-changelog-cli": "^2.0.23",
|
||||||
"copy-webpack-plugin": "^5.0.0",
|
"copy-webpack-plugin": "^5.0.4",
|
||||||
"core-js": "^2.6.5",
|
"core-js": "^3.2.1",
|
||||||
"coveralls": "^3.0.3",
|
"coveralls": "^3.0.6",
|
||||||
"css-loader": "^2.1.1",
|
"css-loader": "^3.2.0",
|
||||||
"cypress": "~3.1.5",
|
"cypress": "~3.4.1",
|
||||||
"deploy-to-gh-pages": "^1.3.6",
|
"deploy-to-gh-pages": "^1.3.7",
|
||||||
"enzyme": "^3.9.0",
|
"enzyme": "^3.10.0",
|
||||||
"enzyme-adapter-react-16": "^1.10.0",
|
"enzyme-adapter-react-16": "^1.14.0",
|
||||||
"enzyme-to-json": "^3.3.5",
|
"enzyme-to-json": "^3.4.0",
|
||||||
"fork-ts-checker-webpack-plugin": "1.0.0",
|
"fork-ts-checker-webpack-plugin": "1.5.0",
|
||||||
"html-webpack-plugin": "^3.1.0",
|
"html-webpack-plugin": "^3.1.0",
|
||||||
"jest": "^24.3.1",
|
"jest": "^24.9.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.15",
|
||||||
"mobx": "^4.3.1",
|
"mobx": "^4.3.1",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.18.2",
|
||||||
"prettier-eslint": "^8.8.2",
|
"prettier-eslint": "^9.0.0",
|
||||||
"puppeteer": "^1.13.0",
|
|
||||||
"raf": "^3.4.1",
|
"raf": "^3.4.1",
|
||||||
"react": "^16.8.4",
|
"react": "^16.10.1",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.10.1",
|
||||||
"rimraf": "^2.6.3",
|
"rimraf": "^3.0.0",
|
||||||
"shelljs": "^0.8.3",
|
"shelljs": "^0.8.3",
|
||||||
"source-map-loader": "^0.2.4",
|
"source-map-loader": "^0.2.4",
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^1.0.0",
|
||||||
"styled-components": "^4.1.3",
|
"styled-components": "^4.4.0",
|
||||||
"ts-jest": "24.0.0",
|
"ts-jest": "24.1.0",
|
||||||
"ts-loader": "5.3.3",
|
"ts-loader": "6.2.0",
|
||||||
"ts-node": "^8.0.3",
|
"ts-node": "^8.4.1",
|
||||||
"tslint": "^5.13.1",
|
"tslint": "^5.20.0",
|
||||||
"tslint-react": "^3.4.0",
|
"tslint-react": "^4.1.0",
|
||||||
"typescript": "^3.3.3333",
|
"typescript": "^3.6.3",
|
||||||
"webpack": "^4.29.6",
|
"unfetch": "^4.1.0",
|
||||||
"webpack-cli": "^3.2.3",
|
"url-polyfill": "^1.1.7",
|
||||||
"webpack-dev-server": "^3.2.1",
|
"webpack": "^4.41.0",
|
||||||
|
"webpack-cli": "^3.3.9",
|
||||||
|
"webpack-dev-server": "^3.8.1",
|
||||||
"webpack-node-externals": "^1.6.0",
|
"webpack-node-externals": "^1.6.0",
|
||||||
"workerize-loader": "^1.0.4",
|
"workerize-loader": "^1.1.0",
|
||||||
"yaml-js": "^0.2.3"
|
"yaml-js": "^0.2.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"core-js": "^3.1.4",
|
||||||
"mobx": "^4.2.0 || ^5.0.0",
|
"mobx": "^4.2.0 || ^5.0.0",
|
||||||
"react": "^16.8.4",
|
"react": "^16.8.4",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.8.4",
|
||||||
|
@ -133,27 +135,28 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"decko": "^1.2.0",
|
"decko": "^1.2.0",
|
||||||
"dompurify": "^1.0.10",
|
"dompurify": "^2.0.3",
|
||||||
"eventemitter3": "^3.0.0",
|
"eventemitter3": "^4.0.0",
|
||||||
"json-pointer": "^0.6.0",
|
"json-pointer": "^0.6.0",
|
||||||
"json-schema-ref-parser": "^6.1.0",
|
"json-schema-ref-parser": "^6.1.0",
|
||||||
"lunr": "2.3.6",
|
"lunr": "2.3.6",
|
||||||
"mark.js": "^8.11.1",
|
"mark.js": "^8.11.1",
|
||||||
"marked": "^0.6.1",
|
"marked": "^0.7.0",
|
||||||
"memoize-one": "^5.0.0",
|
"memoize-one": "~5.0.5",
|
||||||
"mobx-react": "^5.4.3",
|
"mobx-react": "^6.1.3",
|
||||||
"openapi-sampler": "1.0.0-beta.14",
|
"openapi-sampler": "1.0.0-beta.15",
|
||||||
"perfect-scrollbar": "^1.4.0",
|
"perfect-scrollbar": "^1.4.0",
|
||||||
"polished": "^3.0.3",
|
"polished": "^3.4.1",
|
||||||
"prismjs": "^1.15.0",
|
"prismjs": "^1.17.1",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react-dropdown": "^1.6.4",
|
"react-dropdown": "^1.6.4",
|
||||||
"react-hot-loader": "^4.8.0",
|
"react-hot-loader": "^4.12.14",
|
||||||
"react-tabs": "^3.0.0",
|
"react-tabs": "^3.0.0",
|
||||||
"slugify": "^1.3.4",
|
"slugify": "^1.3.5",
|
||||||
"stickyfill": "^1.1.1",
|
"stickyfill": "^1.1.1",
|
||||||
"swagger2openapi": "^5.2.3",
|
"swagger2openapi": "^5.3.1",
|
||||||
"tslib": "^1.9.3"
|
"tslib": "^1.10.0",
|
||||||
|
"url-template": "^2.0.8"
|
||||||
},
|
},
|
||||||
"bundlesize": [
|
"bundlesize": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,11 +5,7 @@ import { ClipboardService } from '../services/ClipboardService';
|
||||||
|
|
||||||
export interface CopyButtonWrapperProps {
|
export interface CopyButtonWrapperProps {
|
||||||
data: any;
|
data: any;
|
||||||
children: (
|
children: (props: { renderCopyButton: () => React.ReactNode }) => React.ReactNode;
|
||||||
props: {
|
|
||||||
renderCopyButton: (() => React.ReactNode);
|
|
||||||
},
|
|
||||||
) => React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CopyButtonWrapper extends React.PureComponent<
|
export class CopyButtonWrapper extends React.PureComponent<
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const StyledDropdown = styled(Dropdown)`
|
||||||
display: block;
|
display: block;
|
||||||
height: 0;
|
height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0.35em;
|
right: 0.3em;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
margin-top: -0.125em;
|
margin-top: -0.125em;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
@ -97,7 +97,7 @@ export const StyledDropdown = styled(Dropdown)`
|
||||||
export const SimpleDropdown = styled(StyledDropdown)`
|
export const SimpleDropdown = styled(StyledDropdown)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-size: 0.929em;
|
font-size: 0.969em;
|
||||||
|
|
||||||
.Dropdown-control {
|
.Dropdown-control {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
|
|
@ -75,7 +75,7 @@ export const PropertyNameCell = styled(PropertyCell)`
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
font-family: ${props => props.theme.typography.headings.fontFamily};
|
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||||
|
|
||||||
&.deprecated {
|
&.deprecated {
|
||||||
${deprecatedCss};
|
${deprecatedCss};
|
||||||
|
|
|
@ -32,6 +32,7 @@ export const TypeName = styled(FieldLabel)`
|
||||||
|
|
||||||
export const TypeTitle = styled(FieldLabel)`
|
export const TypeTitle = styled(FieldLabel)`
|
||||||
color: ${props => props.theme.schema.typeTitleColor};
|
color: ${props => props.theme.schema.typeTitleColor};
|
||||||
|
word-break: break-word;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TypeFormat = TypeName;
|
export const TypeFormat = TypeName;
|
||||||
|
@ -60,14 +61,6 @@ export const PatternLabel = styled(FieldLabel)`
|
||||||
&::after {
|
&::after {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: ' /';
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '/ ';
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ExampleValue = styled(FieldLabel)`
|
export const ExampleValue = styled(FieldLabel)`
|
||||||
|
@ -76,11 +69,9 @@ export const ExampleValue = styled(FieldLabel)`
|
||||||
background-color: ${transparentize(0.95, theme.colors.text.primary)};
|
background-color: ${transparentize(0.95, theme.colors.text.primary)};
|
||||||
color: ${transparentize(0.1, theme.colors.text.primary)};
|
color: ${transparentize(0.1, theme.colors.text.primary)};
|
||||||
|
|
||||||
margin: ${theme.spacing.unit}px;
|
|
||||||
padding: 0 ${theme.spacing.unit}px;
|
padding: 0 ${theme.spacing.unit}px;
|
||||||
border: 1px solid ${transparentize(0.9, theme.colors.text.primary)};
|
border: 1px solid ${transparentize(0.9, theme.colors.text.primary)};
|
||||||
font-family: ${theme.typography.code.fontFamily};
|
font-family: ${theme.typography.code.fontFamily};
|
||||||
color: ${theme.typography.code.color};
|
|
||||||
}`};
|
}`};
|
||||||
& + & {
|
& + & {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
@ -99,6 +90,7 @@ export const ConstraintItem = styled(FieldLabel)`
|
||||||
margin: 0 ${theme.spacing.unit}px;
|
margin: 0 ${theme.spacing.unit}px;
|
||||||
padding: 0 ${theme.spacing.unit}px;
|
padding: 0 ${theme.spacing.unit}px;
|
||||||
border: 1px solid ${transparentize(0.9, theme.colors.primary.main)};
|
border: 1px solid ${transparentize(0.9, theme.colors.primary.main)};
|
||||||
|
font-family: ${theme.typography.code.fontFamily};
|
||||||
}`};
|
}`};
|
||||||
& + & {
|
& + & {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
import { SECTION_ATTR } from '../services/MenuStore';
|
import { SECTION_ATTR } from '../services/MenuStore';
|
||||||
import styled, { media } from '../styled-components';
|
import styled, { media } from '../styled-components';
|
||||||
|
|
||||||
export const MiddlePanel = styled.div`
|
export const MiddlePanel = styled.div<{ compact?: boolean }>`
|
||||||
width: 100%;
|
width: calc(100% - ${props => props.theme.rightPanel.width});
|
||||||
padding: ${props =>
|
padding: 0 ${props => props.theme.spacing.sectionHorizontal}px;
|
||||||
`${props.theme.spacing.sectionVertical}px ${props.theme.spacing.sectionHorizontal}px`};
|
${({ compact, theme }) =>
|
||||||
|
media.lessThan('medium', true)`
|
||||||
|
width: 100%;
|
||||||
|
padding: ${`${compact ? 0 : theme.spacing.sectionVertical}px ${
|
||||||
|
theme.spacing.sectionHorizontal
|
||||||
|
}px`};
|
||||||
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const Section = styled.div.attrs(props => ({
|
export const Section = styled.div.attrs(props => ({
|
||||||
[SECTION_ATTR]: props.id,
|
[SECTION_ATTR]: props.id,
|
||||||
}))<{ underlined?: boolean }>`
|
})) <{ underlined?: boolean }>`
|
||||||
padding: ${props => props.theme.spacing.sectionVertical}px 0;
|
padding: ${props => props.theme.spacing.sectionVertical}px 0;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
|
|
@ -18,6 +18,7 @@ export const OneOfLabel = styled.span`
|
||||||
export const OneOfButton = styled.li<{ active: boolean }>`
|
export const OneOfButton = styled.li<{ active: boolean }>`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
margin-bottom: 5px;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid ${props => props.theme.colors.primary.main};
|
border: 1px solid ${props => props.theme.colors.primary.main};
|
||||||
|
|
|
@ -98,7 +98,7 @@ export const SmallTabs = styled(Tabs)`
|
||||||
> .react-tabs__tab-panel {
|
> .react-tabs__tab-panel {
|
||||||
& > div,
|
& > div,
|
||||||
& > pre {
|
& > pre {
|
||||||
padding: ${props => props.theme.spacing.unit * 2} 0;
|
padding: ${props => props.theme.spacing.unit * 2}px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -44,22 +44,20 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const website =
|
const website =
|
||||||
(info.contact &&
|
(info.contact && info.contact.url && (
|
||||||
info.contact.url && (
|
<InfoSpan>
|
||||||
<InfoSpan>
|
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
||||||
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
</InfoSpan>
|
||||||
</InfoSpan>
|
)) ||
|
||||||
)) ||
|
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const email =
|
const email =
|
||||||
(info.contact &&
|
(info.contact && info.contact.email && (
|
||||||
info.contact.email && (
|
<InfoSpan>
|
||||||
<InfoSpan>
|
{info.contact.name || 'E-mail'}:{' '}
|
||||||
{info.contact.name || 'E-mail'}:{' '}
|
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
||||||
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
</InfoSpan>
|
||||||
</InfoSpan>
|
)) ||
|
||||||
)) ||
|
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const terms =
|
const terms =
|
||||||
|
@ -70,11 +68,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
)) ||
|
)) ||
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const version =
|
const version = (info.version && <span>({info.version})</span>) || null;
|
||||||
(info.version && (
|
|
||||||
<span>({info.version})</span>
|
|
||||||
)) ||
|
|
||||||
null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Section>
|
||||||
|
|
|
@ -17,13 +17,11 @@ export class ApiLogo extends React.Component<{ info: OpenAPIInfo }> {
|
||||||
// Use the english word logo if no alt text is provided
|
// Use the english word logo if no alt text is provided
|
||||||
const altText = logoInfo.altText ? logoInfo.altText : 'logo';
|
const altText = logoInfo.altText ? logoInfo.altText : 'logo';
|
||||||
|
|
||||||
const logo = (
|
const logo = <LogoImgEl src={logoInfo.url} alt={altText} />;
|
||||||
<LogoImgEl
|
return (
|
||||||
src={logoInfo.url}
|
<LogoWrap style={{ backgroundColor: logoInfo.backgroundColor }}>
|
||||||
style={{ backgroundColor: logoInfo.backgroundColor }}
|
{logoHref ? LinkWrap(logoHref)(logo) : logo}
|
||||||
alt={altText}
|
</LogoWrap>
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
return <LogoWrap>{logoHref ? LinkWrap(logoHref)(logo) : logo}</LogoWrap>;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,13 @@ import styled from '../../styled-components';
|
||||||
export const LogoImgEl = styled.img`
|
export const LogoImgEl = styled.img`
|
||||||
max-height: ${props => props.theme.logo.maxHeight};
|
max-height: ${props => props.theme.logo.maxHeight};
|
||||||
max-width: ${props => props.theme.logo.maxWidth};
|
max-width: ${props => props.theme.logo.maxWidth};
|
||||||
|
padding: ${props => props.theme.logo.gutter};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const LogoWrap = styled.div`
|
export const LogoWrap = styled.div`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: ${props => props.theme.logo.gutter};
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Link = styled.a`
|
const Link = styled.a`
|
||||||
|
|
|
@ -60,7 +60,7 @@ export class ContentItem extends React.Component<ContentItemProps> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const middlePanelWrap = component => <MiddlePanel>{component}</MiddlePanel>;
|
const middlePanelWrap = component => <MiddlePanel compact={true}>{component}</MiddlePanel>;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class SectionItem extends React.Component<ContentItemProps> {
|
export class SectionItem extends React.Component<ContentItemProps> {
|
||||||
|
@ -71,7 +71,7 @@ export class SectionItem extends React.Component<ContentItemProps> {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row>
|
<Row>
|
||||||
<MiddlePanel>
|
<MiddlePanel compact={level !== 1}>
|
||||||
<Header>
|
<Header>
|
||||||
<ShareLink to={this.props.item.id} />
|
<ShareLink to={this.props.item.id} />
|
||||||
{name}
|
{name}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Markdown } from '../Markdown/Markdown';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
|
import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
|
||||||
|
|
||||||
import { getBasePath } from '../../utils';
|
import { expandDefaultServerVariables, getBasePath } from '../../utils';
|
||||||
import {
|
import {
|
||||||
EndpointInfo,
|
EndpointInfo,
|
||||||
HttpVerb,
|
HttpVerb,
|
||||||
|
@ -60,21 +60,26 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
|
||||||
/>
|
/>
|
||||||
</EndpointInfo>
|
</EndpointInfo>
|
||||||
<ServersOverlay expanded={expanded}>
|
<ServersOverlay expanded={expanded}>
|
||||||
{operation.servers.map(server => (
|
{operation.servers.map(server => {
|
||||||
<ServerItem key={server.url}>
|
const normalizedUrl = options.expandDefaultServerVariables
|
||||||
<Markdown source={server.description || ''} compact={true} />
|
? expandDefaultServerVariables(server.url, server.variables)
|
||||||
<SelectOnClick>
|
: server.url;
|
||||||
<ServerUrl>
|
return (
|
||||||
<span>
|
<ServerItem key={normalizedUrl}>
|
||||||
{hideHostname || options.hideHostname
|
<Markdown source={server.description || ''} compact={true} />
|
||||||
? getBasePath(server.url)
|
<SelectOnClick>
|
||||||
: server.url}
|
<ServerUrl>
|
||||||
</span>
|
<span>
|
||||||
{operation.path}
|
{hideHostname || options.hideHostname
|
||||||
</ServerUrl>
|
? getBasePath(normalizedUrl)
|
||||||
</SelectOnClick>
|
: normalizedUrl}
|
||||||
</ServerItem>
|
</span>
|
||||||
))}
|
{operation.path}
|
||||||
|
</ServerUrl>
|
||||||
|
</SelectOnClick>
|
||||||
|
</ServerItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</ServersOverlay>
|
</ServersOverlay>
|
||||||
</OperationEndpointWrap>
|
</OperationEndpointWrap>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
||||||
|
|
||||||
|
import { l } from '../../services/Labels';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
|
||||||
export interface EnumValuesProps {
|
export interface EnumValuesProps {
|
||||||
values: string[];
|
values: string[];
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
render() {
|
render() {
|
||||||
const { values, type } = this.props;
|
const { values, type } = this.props;
|
||||||
|
const { enumSkipQuotes } = this.context;
|
||||||
if (!values.length) {
|
if (!values.length) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -16,11 +21,17 @@ export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel>
|
<FieldLabel>
|
||||||
{type === 'array' ? 'Items' : ''} {values.length === 1 ? 'Value' : 'Enum'}:
|
{type === 'array' ? l('enumArray') : ''}{' '}
|
||||||
</FieldLabel>
|
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||||
{values.map((value, idx) => (
|
</FieldLabel>{' '}
|
||||||
<ExampleValue key={idx}>{JSON.stringify(value)} </ExampleValue>
|
{values.map((value, idx) => {
|
||||||
))}
|
const exampleValue = enumSkipQuotes ? value : JSON.stringify(value);
|
||||||
|
return (
|
||||||
|
<React.Fragment key={idx}>
|
||||||
|
<ExampleValue>{exampleValue}</ExampleValue>{' '}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
||||||
export interface FieldDetailProps {
|
export interface FieldDetailProps {
|
||||||
value?: any;
|
value?: any;
|
||||||
label: string;
|
label: string;
|
||||||
|
raw?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
||||||
|
@ -11,10 +12,12 @@ export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
||||||
if (this.props.value === undefined) {
|
if (this.props.value === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const value = this.props.raw ? this.props.value : JSON.stringify(this.props.value);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FieldLabel> {this.props.label} </FieldLabel>{' '}
|
<FieldLabel> {this.props.label} </FieldLabel> <ExampleValue>{value}</ExampleValue>
|
||||||
<ExampleValue> {JSON.stringify(this.props.value)} </ExampleValue>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
TypePrefix,
|
TypePrefix,
|
||||||
TypeTitle,
|
TypeTitle,
|
||||||
} from '../../common-elements/fields';
|
} from '../../common-elements/fields';
|
||||||
|
import { serializeParameterValue } from '../../utils/openapi';
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { EnumValues } from './EnumValues';
|
import { EnumValues } from './EnumValues';
|
||||||
|
@ -19,12 +20,31 @@ import { FieldDetail } from './FieldDetail';
|
||||||
|
|
||||||
import { Badge } from '../../common-elements/';
|
import { Badge } from '../../common-elements/';
|
||||||
|
|
||||||
|
import { l } from '../../services/Labels';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
|
|
||||||
export class FieldDetails extends React.PureComponent<FieldProps> {
|
export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
render() {
|
render() {
|
||||||
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
||||||
|
const { enumSkipQuotes } = this.context;
|
||||||
|
|
||||||
const { schema, description, example, deprecated } = field;
|
const { schema, description, example, deprecated } = field;
|
||||||
|
|
||||||
|
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
|
||||||
|
|
||||||
|
let exampleField: JSX.Element | null = null;
|
||||||
|
|
||||||
|
if (showExamples && example !== undefined) {
|
||||||
|
const label = l('example') + ':';
|
||||||
|
if (field.in && (field.style || field.serializationMime)) {
|
||||||
|
const serializedValue = serializeParameterValue(field, example);
|
||||||
|
exampleField = <FieldDetail label={label} value={serializedValue} raw={true} />;
|
||||||
|
} else {
|
||||||
|
exampleField = <FieldDetail label={label} value={example} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -40,18 +60,18 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||||
)}
|
)}
|
||||||
{schema.title && <TypeTitle> ({schema.title}) </TypeTitle>}
|
{schema.title && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||||
<ConstraintsView constraints={schema.constraints} />
|
<ConstraintsView constraints={schema.constraints} />
|
||||||
{schema.nullable && <NullableLabel> Nullable </NullableLabel>}
|
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>}
|
||||||
{schema.pattern && <PatternLabel>{schema.pattern}</PatternLabel>}
|
{schema.pattern && <PatternLabel> {schema.pattern} </PatternLabel>}
|
||||||
{schema.isCircular && <RecursiveLabel> Recursive </RecursiveLabel>}
|
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
||||||
</div>
|
</div>
|
||||||
{deprecated && (
|
{deprecated && (
|
||||||
<div>
|
<div>
|
||||||
<Badge type="warning"> Deprecated </Badge>
|
<Badge type="warning"> {l('deprecated')} </Badge>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<FieldDetail label={'Default:'} value={schema.default} />
|
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
||||||
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
||||||
{showExamples && <FieldDetail label={'Example:'} value={example} />}
|
{exampleField}
|
||||||
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
||||||
<div>
|
<div>
|
||||||
<Markdown compact={true} source={description} />
|
<Markdown compact={true} source={description} />
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { SampleControls } from '../../common-elements';
|
||||||
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
|
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
|
||||||
import { PrismDiv } from '../../common-elements/PrismDiv';
|
import { PrismDiv } from '../../common-elements/PrismDiv';
|
||||||
import { jsonToHTML } from '../../utils/jsonToHtml';
|
import { jsonToHTML } from '../../utils/jsonToHtml';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { jsonStyles } from './style';
|
import { jsonStyles } from './style';
|
||||||
|
|
||||||
export interface JsonProps {
|
export interface JsonProps {
|
||||||
|
@ -32,12 +33,18 @@ class Json extends React.PureComponent<JsonProps> {
|
||||||
<span onClick={this.expandAll}> Expand all </span>
|
<span onClick={this.expandAll}> Expand all </span>
|
||||||
<span onClick={this.collapseAll}> Collapse all </span>
|
<span onClick={this.collapseAll}> Collapse all </span>
|
||||||
</SampleControls>
|
</SampleControls>
|
||||||
<PrismDiv
|
<OptionsContext.Consumer>
|
||||||
className={this.props.className}
|
{options => (
|
||||||
// tslint:disable-next-line
|
<PrismDiv
|
||||||
ref={node => (this.node = node!)}
|
className={this.props.className}
|
||||||
dangerouslySetInnerHTML={{ __html: jsonToHTML(this.props.data) }}
|
// tslint:disable-next-line
|
||||||
/>
|
ref={node => (this.node = node!)}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</OptionsContext.Consumer>
|
||||||
</JsonViewerWrap>
|
</JsonViewerWrap>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { DropdownProps } from '../../common-elements/dropdown';
|
import { DropdownProps } from '../../common-elements/dropdown';
|
||||||
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
|
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
|
||||||
|
import { DropdownLabel, DropdownWrapper } from '../PayloadSamples/styled.elements';
|
||||||
|
|
||||||
export interface MediaTypeChildProps {
|
export interface MediaTypeChildProps {
|
||||||
schema: SchemaModel;
|
schema: SchemaModel;
|
||||||
|
@ -11,6 +12,8 @@ export interface MediaTypeChildProps {
|
||||||
|
|
||||||
export interface MediaTypesSwitchProps {
|
export interface MediaTypesSwitchProps {
|
||||||
content?: MediaContentModel;
|
content?: MediaContentModel;
|
||||||
|
withLabel?: boolean;
|
||||||
|
|
||||||
renderDropdown: (props: DropdownProps) => JSX.Element;
|
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||||
children: (activeMime: MediaTypeModel) => JSX.Element;
|
children: (activeMime: MediaTypeModel) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
@ -37,13 +40,25 @@ export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Wrapper = ({ children }) =>
|
||||||
|
this.props.withLabel ? (
|
||||||
|
<DropdownWrapper>
|
||||||
|
<DropdownLabel>Content type</DropdownLabel>
|
||||||
|
{children}
|
||||||
|
</DropdownWrapper>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{this.props.renderDropdown({
|
<Wrapper>
|
||||||
value: options[activeMimeIdx],
|
{this.props.renderDropdown({
|
||||||
options,
|
value: options[activeMimeIdx],
|
||||||
onChange: this.switchMedia,
|
options,
|
||||||
})}
|
onChange: this.switchMedia,
|
||||||
|
})}
|
||||||
|
</Wrapper>
|
||||||
{this.props.children(content.active)}
|
{this.props.children(content.active)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,9 +2,6 @@ import * as React from 'react';
|
||||||
|
|
||||||
import { StyledPre } from '../../common-elements/samples';
|
import { StyledPre } from '../../common-elements/samples';
|
||||||
import { ExampleModel } from '../../services/models';
|
import { ExampleModel } from '../../services/models';
|
||||||
import { isJsonLike, langFromMime } from '../../utils';
|
|
||||||
import { JsonViewer } from '../JsonViewer/JsonViewer';
|
|
||||||
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
|
|
||||||
import { ExampleValue } from './ExampleValue';
|
import { ExampleValue } from './ExampleValue';
|
||||||
import { useExternalExample } from './exernalExampleHook';
|
import { useExternalExample } from './exernalExampleHook';
|
||||||
|
|
||||||
|
@ -22,14 +19,13 @@ export function Example({ example, mimeType }: ExampleProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExternalExample({ example, mimeType }: ExampleProps) {
|
export function ExternalExample({ example, mimeType }: ExampleProps) {
|
||||||
let value = useExternalExample(example, mimeType);
|
const value = useExternalExample(example, mimeType);
|
||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return <span>Loading...</span>;
|
return <span>Loading...</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value instanceof Error) {
|
if (value instanceof Error) {
|
||||||
console.log(value);
|
|
||||||
return (
|
return (
|
||||||
<StyledPre>
|
<StyledPre>
|
||||||
Error loading external example: <br />
|
Error loading external example: <br />
|
||||||
|
@ -40,13 +36,5 @@ export function ExternalExample({ example, mimeType }: ExampleProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isJsonLike(mimeType)) {
|
return <ExampleValue value={value} mimeType={mimeType} />;
|
||||||
return <JsonViewer data={value} />;
|
|
||||||
} else {
|
|
||||||
if (typeof value === 'object') {
|
|
||||||
// just in case example was cached as json but used as non-json
|
|
||||||
value = JSON.stringify(value, null, 2);
|
|
||||||
}
|
|
||||||
return <SourceCodeWithCopy lang={langFromMime(mimeType)} source={value} />;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,10 @@ export function ExampleValue({ value, mimeType }: ExampleValueProps) {
|
||||||
if (isJsonLike(mimeType)) {
|
if (isJsonLike(mimeType)) {
|
||||||
return <JsonViewer data={value} />;
|
return <JsonViewer data={value} />;
|
||||||
} else {
|
} else {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
// just in case example was cached as json but used as non-json
|
||||||
|
value = JSON.stringify(value, null, 2);
|
||||||
|
}
|
||||||
return <SourceCodeWithCopy lang={langFromMime(mimeType)} source={value} />;
|
return <SourceCodeWithCopy lang={langFromMime(mimeType)} source={value} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,33 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { SmallTabs, Tab, TabList, TabPanel } from '../../common-elements';
|
import styled from '../../styled-components';
|
||||||
import { MediaTypeModel } from '../../services/models';
|
|
||||||
|
|
||||||
|
import { DropdownProps } from '../../common-elements';
|
||||||
|
import { MediaTypeModel } from '../../services/models';
|
||||||
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { Example } from './Example';
|
import { Example } from './Example';
|
||||||
import { NoSampleLabel } from './styled.elements';
|
import { DropdownLabel, DropdownWrapper, NoSampleLabel } from './styled.elements';
|
||||||
|
|
||||||
export interface PayloadSamplesProps {
|
export interface PayloadSamplesProps {
|
||||||
mediaType: MediaTypeModel;
|
mediaType: MediaTypeModel;
|
||||||
|
renderDropdown: (props: DropdownProps) => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
|
interface MediaTypeSamplesState {
|
||||||
|
activeIdx: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MediaTypeSamples extends React.Component<PayloadSamplesProps, MediaTypeSamplesState> {
|
||||||
|
state = {
|
||||||
|
activeIdx: 0,
|
||||||
|
};
|
||||||
|
switchMedia = ({ value }) => {
|
||||||
|
this.setState({
|
||||||
|
activeIdx: parseInt(value, 10),
|
||||||
|
});
|
||||||
|
};
|
||||||
render() {
|
render() {
|
||||||
|
const { activeIdx } = this.state;
|
||||||
const examples = this.props.mediaType.examples || {};
|
const examples = this.props.mediaType.examples || {};
|
||||||
const mimeType = this.props.mediaType.name;
|
const mimeType = this.props.mediaType.name;
|
||||||
|
|
||||||
|
@ -21,28 +37,46 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
|
||||||
if (examplesNames.length === 0) {
|
if (examplesNames.length === 0) {
|
||||||
return noSample;
|
return noSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (examplesNames.length > 1) {
|
if (examplesNames.length > 1) {
|
||||||
|
const options = examplesNames.map((name, idx) => {
|
||||||
|
return {
|
||||||
|
label: examples[name].summary || name,
|
||||||
|
value: idx.toString(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const example = examples[examplesNames[activeIdx]];
|
||||||
|
const description = example.description;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SmallTabs defaultIndex={0}>
|
<SamplesWrapper>
|
||||||
<TabList>
|
<DropdownWrapper>
|
||||||
{examplesNames.map(name => (
|
<DropdownLabel>Example</DropdownLabel>
|
||||||
<Tab key={name}> {examples[name].summary || name} </Tab>
|
{this.props.renderDropdown({
|
||||||
))}
|
value: options[activeIdx],
|
||||||
</TabList>
|
options,
|
||||||
{examplesNames.map(name => (
|
onChange: this.switchMedia,
|
||||||
<TabPanel key={name}>
|
})}
|
||||||
<Example example={examples[name]} mimeType={mimeType} />
|
</DropdownWrapper>
|
||||||
</TabPanel>
|
<div>
|
||||||
))}
|
{description && <Markdown source={description} />}
|
||||||
</SmallTabs>
|
<Example example={example} mimeType={mimeType} />
|
||||||
|
</div>
|
||||||
|
</SamplesWrapper>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const name = examplesNames[0];
|
const example = examples[examplesNames[0]];
|
||||||
return (
|
return (
|
||||||
<div>
|
<SamplesWrapper>
|
||||||
<Example example={examples[name]} mimeType={mimeType} />
|
{example.description && <Markdown source={example.description} />}
|
||||||
</div>
|
<Example example={example} mimeType={mimeType} />
|
||||||
|
</SamplesWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SamplesWrapper = styled.div`
|
||||||
|
margin-top: 15px;
|
||||||
|
`;
|
||||||
|
|
|
@ -2,10 +2,9 @@ import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { MediaTypeSamples } from './MediaTypeSamples';
|
import { MediaTypeSamples } from './MediaTypeSamples';
|
||||||
|
|
||||||
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
|
||||||
|
|
||||||
import { MediaContentModel } from '../../services/models';
|
import { MediaContentModel } from '../../services/models';
|
||||||
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||||
|
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
||||||
import { InvertedSimpleDropdown, MimeLabel } from './styled.elements';
|
import { InvertedSimpleDropdown, MimeLabel } from './styled.elements';
|
||||||
|
|
||||||
export interface PayloadSamplesProps {
|
export interface PayloadSamplesProps {
|
||||||
|
@ -21,8 +20,14 @@ export class PayloadSamples extends React.Component<PayloadSamplesProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown}>
|
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown} withLabel={true}>
|
||||||
{mediaType => <MediaTypeSamples key="samples" mediaType={mediaType} />}
|
{mediaType => (
|
||||||
|
<MediaTypeSamples
|
||||||
|
key="samples"
|
||||||
|
mediaType={mediaType}
|
||||||
|
renderDropdown={this.renderDropdown}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</MediaTypesSwitch>
|
</MediaTypesSwitch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,22 +13,19 @@ export function useExternalExample(example: ExampleModel, mimeType: string) {
|
||||||
|
|
||||||
prevRef.current = example;
|
prevRef.current = example;
|
||||||
|
|
||||||
useEffect(
|
useEffect(() => {
|
||||||
() => {
|
const load = async () => {
|
||||||
const load = async () => {
|
setIsLoading(true);
|
||||||
setIsLoading(true);
|
try {
|
||||||
try {
|
value.current = await example.getExternalValue(mimeType);
|
||||||
value.current = await example.getExternalValue(mimeType);
|
} catch (e) {
|
||||||
} catch (e) {
|
value.current = e;
|
||||||
value.current = e;
|
}
|
||||||
}
|
setIsLoading(false);
|
||||||
setIsLoading(false);
|
};
|
||||||
};
|
|
||||||
|
|
||||||
load();
|
load();
|
||||||
},
|
}, [example, mimeType]);
|
||||||
[example, mimeType],
|
|
||||||
);
|
|
||||||
|
|
||||||
return value.current;
|
return value.current;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,58 @@
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Dropdown from 'react-dropdown';
|
import Dropdown from 'react-dropdown';
|
||||||
|
|
||||||
|
import { transparentize } from 'polished';
|
||||||
import styled from '../../styled-components';
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
import { StyledDropdown } from '../../common-elements';
|
import { StyledDropdown } from '../../common-elements';
|
||||||
|
|
||||||
export const MimeLabel = styled.div`
|
export const MimeLabel = styled.div`
|
||||||
position: relative;
|
padding: 12px;
|
||||||
top: -35px;
|
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||||
left: 132px;
|
margin: 0 0 10px 0;
|
||||||
margin: 0;
|
|
||||||
font-size: 0.929em;
|
|
||||||
color: #000;
|
|
||||||
display: block;
|
display: block;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const DropdownLabel = styled.span`
|
||||||
|
font-family: ${({ theme }) => theme.typography.headings.fontFamily};
|
||||||
|
font-size: 12px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
top: -11px;
|
||||||
|
left: 12px;
|
||||||
|
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
|
||||||
|
color: ${({ theme }) => transparentize(0.6, theme.rightPanel.textColor)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DropdownWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
export const InvertedSimpleDropdown = styled(StyledDropdown)`
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.rightPanel.textColor};
|
|
||||||
margin: 0 0 10px 0;
|
margin: 0 0 10px 0;
|
||||||
display: block;
|
display: block;
|
||||||
|
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||||
|
.Dropdown-control {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
.Dropdown-control,
|
.Dropdown-control,
|
||||||
.Dropdown-control:hover {
|
.Dropdown-control:hover {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0 1.2em 0 0;
|
padding: 0.9em 1.6em 0.9em 0.9em;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: ${({ theme }) => theme.rightPanel.textColor};
|
color: ${({ theme }) => theme.rightPanel.textColor};
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
.Dropdown-arrow {
|
.Dropdown-arrow {
|
||||||
border-top-color: ${({ theme }) => theme.rightPanel.textColor};
|
border-top-color: ${({ theme }) => theme.rightPanel.textColor};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.Dropdown-menu {
|
.Dropdown-menu {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Tab, TabList, TabPanel, Tabs, UnderlinedHeader } from '../../common-elements';
|
import { Tab, TabList, TabPanel, Tabs, UnderlinedHeader } from '../../common-elements';
|
||||||
import { OperationModel } from '../../services/models';
|
import { OperationModel, RedocNormalizedOptions } from '../../services';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
|
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
|
||||||
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
|
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
|
||||||
|
|
||||||
|
@ -11,6 +12,8 @@ export interface RequestSamplesProps {
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class RequestSamples extends React.Component<RequestSamplesProps> {
|
export class RequestSamples extends React.Component<RequestSamplesProps> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
|
context: RedocNormalizedOptions;
|
||||||
operation: OperationModel;
|
operation: OperationModel;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -20,6 +23,7 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
|
||||||
const samples = operation.codeSamples;
|
const samples = operation.codeSamples;
|
||||||
|
|
||||||
const hasSamples = hasBodySample || samples.length > 0;
|
const hasSamples = hasBodySample || samples.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(hasSamples && (
|
(hasSamples && (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -22,7 +22,7 @@ export class ResponseTitle extends React.PureComponent<ResponseTitleProps> {
|
||||||
<ShelfIcon
|
<ShelfIcon
|
||||||
size={'1.5em'}
|
size={'1.5em'}
|
||||||
color={type}
|
color={type}
|
||||||
direction={opened ? 'up' : 'down'}
|
direction={opened ? 'down' : 'right'}
|
||||||
float={'left'}
|
float={'left'}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -34,9 +34,9 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
||||||
|
|
||||||
const filteredFields = needFilter
|
const filteredFields = needFilter
|
||||||
? fields.filter(item => {
|
? fields.filter(item => {
|
||||||
return (
|
return !(
|
||||||
(this.props.skipReadOnly && !item.schema.readOnly) ||
|
(this.props.skipReadOnly && item.schema.readOnly) ||
|
||||||
(this.props.skipWriteOnly && !item.schema.writeOnly)
|
(this.props.skipWriteOnly && item.schema.writeOnly)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
: fields;
|
: fields;
|
||||||
|
|
|
@ -10,6 +10,8 @@ import { ArraySchema } from './ArraySchema';
|
||||||
import { ObjectSchema } from './ObjectSchema';
|
import { ObjectSchema } from './ObjectSchema';
|
||||||
import { OneOfSchema } from './OneOfSchema';
|
import { OneOfSchema } from './OneOfSchema';
|
||||||
|
|
||||||
|
import { l } from '../../services/Labels';
|
||||||
|
|
||||||
export interface SchemaOptions {
|
export interface SchemaOptions {
|
||||||
showTitle?: boolean;
|
showTitle?: boolean;
|
||||||
skipReadOnly?: boolean;
|
skipReadOnly?: boolean;
|
||||||
|
@ -34,7 +36,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
<div>
|
<div>
|
||||||
<TypeName>{schema.displayType}</TypeName>
|
<TypeName>{schema.displayType}</TypeName>
|
||||||
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
|
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
|
||||||
<RecursiveLabel> Recursive </RecursiveLabel>
|
<RecursiveLabel> {l('recursive')} </RecursiveLabel>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -42,9 +44,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
if (discriminatorProp !== undefined) {
|
if (discriminatorProp !== undefined) {
|
||||||
if (!oneOf || !oneOf.length) {
|
if (!oneOf || !oneOf.length) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Looks like you are using discriminator wrong: you don't have any definition inherited from the ${
|
`Looks like you are using discriminator wrong: you don't have any definition inherited from the ${schema.title}`,
|
||||||
schema.title
|
|
||||||
}`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -64,9 +64,9 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'object':
|
case 'object':
|
||||||
return <ObjectSchema {...this.props as any} />;
|
return <ObjectSchema {...(this.props as any)} />;
|
||||||
case 'array':
|
case 'array':
|
||||||
return <ArraySchema {...this.props as any} />;
|
return <ArraySchema {...(this.props as any)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybe adjust FieldDetails to accept schema
|
// TODO: maybe adjust FieldDetails to accept schema
|
||||||
|
|
93
src/components/SchemaDefinition/SchemaDefinition.tsx
Normal file
93
src/components/SchemaDefinition/SchemaDefinition.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { DarkRightPanel, MiddlePanel, MimeLabel, Row, Section } from '../../common-elements';
|
||||||
|
import { MediaTypeModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
import { OpenAPIMediaType } from '../../types';
|
||||||
|
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||||
|
import { MediaTypeSamples } from '../PayloadSamples/MediaTypeSamples';
|
||||||
|
import { InvertedSimpleDropdown } from '../PayloadSamples/styled.elements';
|
||||||
|
import { Schema } from '../Schema';
|
||||||
|
|
||||||
|
export interface ObjectDescriptionProps {
|
||||||
|
schemaRef: string;
|
||||||
|
exampleRef?: string;
|
||||||
|
showReadOnly?: boolean;
|
||||||
|
showWriteOnly?: boolean;
|
||||||
|
parser: OpenAPIParser;
|
||||||
|
options: RedocNormalizedOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps> {
|
||||||
|
private static getMediaType(schemaRef: string, exampleRef?: string): OpenAPIMediaType {
|
||||||
|
if (!schemaRef) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const info: OpenAPIMediaType = {
|
||||||
|
schema: { $ref: schemaRef },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (exampleRef) {
|
||||||
|
info.examples = { example: { $ref: exampleRef } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _mediaModel: MediaTypeModel;
|
||||||
|
|
||||||
|
private get mediaModel() {
|
||||||
|
const { parser, schemaRef, exampleRef, options } = this.props;
|
||||||
|
if (!this._mediaModel) {
|
||||||
|
this._mediaModel = new MediaTypeModel(
|
||||||
|
parser,
|
||||||
|
'json',
|
||||||
|
false,
|
||||||
|
SchemaDefinition.getMediaType(schemaRef, exampleRef),
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._mediaModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { showReadOnly = true, showWriteOnly = false } = this.props;
|
||||||
|
return (
|
||||||
|
<Section>
|
||||||
|
<Row>
|
||||||
|
<MiddlePanel>
|
||||||
|
<Schema
|
||||||
|
skipWriteOnly={!showWriteOnly}
|
||||||
|
skipReadOnly={!showReadOnly}
|
||||||
|
schema={this.mediaModel.schema}
|
||||||
|
/>
|
||||||
|
</MiddlePanel>
|
||||||
|
<DarkRightPanel>
|
||||||
|
<MediaSamplesWrap>
|
||||||
|
<MediaTypeSamples renderDropdown={this.renderDropdown} mediaType={this.mediaModel} />
|
||||||
|
</MediaSamplesWrap>
|
||||||
|
</DarkRightPanel>
|
||||||
|
</Row>
|
||||||
|
</Section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderDropdown = props => {
|
||||||
|
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const MediaSamplesWrap = styled.div`
|
||||||
|
background: ${({ theme }) => theme.codeSample.backgroundColor};
|
||||||
|
& > div,
|
||||||
|
& > pre {
|
||||||
|
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div > pre {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
`;
|
|
@ -4,6 +4,7 @@ import { SecuritySchemesModel } from '../../services/models';
|
||||||
|
|
||||||
import { H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
import { H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||||
import { OpenAPISecurityScheme } from '../../types';
|
import { OpenAPISecurityScheme } from '../../types';
|
||||||
|
import { titleize } from '../../utils/helpers';
|
||||||
import { Markdown } from '../Markdown/Markdown';
|
import { Markdown } from '../Markdown/Markdown';
|
||||||
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ export class OAuthFlow extends React.PureComponent<OAuthFlowProps> {
|
||||||
<strong> Scopes: </strong>
|
<strong> Scopes: </strong>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
{Object.keys(flow!.scopes).map(scope => (
|
{Object.keys(flow!.scopes || {}).map(scope => (
|
||||||
<li key={scope}>
|
<li key={scope}>
|
||||||
<code>{scope}</code> - <Markdown inline={true} source={flow!.scopes[scope] || ''} />
|
<code>{scope}</code> - <Markdown inline={true} source={flow!.scopes[scope] || ''} />
|
||||||
</li>
|
</li>
|
||||||
|
@ -84,7 +85,7 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
</tr>
|
</tr>
|
||||||
{scheme.apiKey ? (
|
{scheme.apiKey ? (
|
||||||
<tr>
|
<tr>
|
||||||
<th> {scheme.apiKey.in} parameter name:</th>
|
<th> {titleize(scheme.apiKey.in || '')} parameter name:</th>
|
||||||
<td> {scheme.apiKey.name} </td>
|
<td> {scheme.apiKey.name} </td>
|
||||||
</tr>
|
</tr>
|
||||||
) : scheme.http ? (
|
) : scheme.http ? (
|
||||||
|
@ -93,13 +94,12 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
<th> HTTP Authorization Scheme </th>
|
<th> HTTP Authorization Scheme </th>
|
||||||
<td> {scheme.http.scheme} </td>
|
<td> {scheme.http.scheme} </td>
|
||||||
</tr>,
|
</tr>,
|
||||||
scheme.http.scheme === 'bearer' &&
|
scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
|
||||||
scheme.http.bearerFormat && (
|
<tr key="bearer">
|
||||||
<tr key="bearer">
|
<th> Bearer format </th>
|
||||||
<th> Bearer format </th>
|
<td> "{scheme.http.bearerFormat}" </td>
|
||||||
<td> "{scheme.http.bearerFormat}" </td>
|
</tr>
|
||||||
</tr>
|
),
|
||||||
),
|
|
||||||
]
|
]
|
||||||
) : scheme.openId ? (
|
) : scheme.openId ? (
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// import { observe } from 'mobx';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ export interface MenuItemProps {
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class MenuItem extends React.Component<MenuItemProps> {
|
export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
ref: Element | null;
|
ref = React.createRef<HTMLLabelElement>();
|
||||||
|
|
||||||
activate = (evt: React.MouseEvent<HTMLElement>) => {
|
activate = (evt: React.MouseEvent<HTMLElement>) => {
|
||||||
this.props.onActivate!(this.props.item);
|
this.props.onActivate!(this.props.item);
|
||||||
|
@ -31,24 +32,15 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollIntoViewIfActive() {
|
scrollIntoViewIfActive() {
|
||||||
if (this.props.item.active && this.ref) {
|
if (this.props.item.active && this.ref.current) {
|
||||||
this.ref.scrollIntoViewIfNeeded();
|
this.ref.current.scrollIntoViewIfNeeded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveRef = ref => {
|
|
||||||
this.ref = ref;
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item, withoutChildren } = this.props;
|
const { item, withoutChildren } = this.props;
|
||||||
return (
|
return (
|
||||||
<MenuItemLi
|
<MenuItemLi onClick={this.activate} depth={item.depth} data-item-id={item.id}>
|
||||||
onClick={this.activate}
|
|
||||||
depth={item.depth}
|
|
||||||
ref={this.saveRef}
|
|
||||||
data-item-id={item.id}
|
|
||||||
>
|
|
||||||
{item.type === 'operation' ? (
|
{item.type === 'operation' ? (
|
||||||
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -83,11 +75,24 @@ export interface OperationMenuItemContentProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
|
export class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
|
||||||
|
ref = React.createRef<HTMLLabelElement>();
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (this.props.item.active && this.ref.current) {
|
||||||
|
this.ref.current.scrollIntoViewIfNeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { item } = this.props;
|
const { item } = this.props;
|
||||||
return (
|
return (
|
||||||
<MenuItemLabel depth={item.depth} active={item.active} deprecated={item.deprecated}>
|
<MenuItemLabel
|
||||||
|
depth={item.depth}
|
||||||
|
active={item.active}
|
||||||
|
deprecated={item.deprecated}
|
||||||
|
ref={this.ref}
|
||||||
|
>
|
||||||
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
||||||
<MenuItemTitle width="calc(100% - 38px)">
|
<MenuItemTitle width="calc(100% - 38px)">
|
||||||
{item.name}
|
{item.name}
|
||||||
|
|
|
@ -2,12 +2,14 @@ import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
||||||
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { MenuItems } from './MenuItems';
|
import { MenuItems } from './MenuItems';
|
||||||
|
|
||||||
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
|
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
private _updateScroll?: () => void;
|
private _updateScroll?: () => void;
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -26,6 +28,10 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
||||||
}
|
}
|
||||||
|
|
||||||
activate = (item: IMenuItem) => {
|
activate = (item: IMenuItem) => {
|
||||||
|
if (item && item.active && this.context.menuToggle) {
|
||||||
|
return item.expanded ? item.collapse() : item.expand();
|
||||||
|
}
|
||||||
|
|
||||||
this.props.menu.activateAndScroll(item, true);
|
this.props.menu.activateAndScroll(item, true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this._updateScroll) {
|
if (this._updateScroll) {
|
||||||
|
|
|
@ -19,6 +19,10 @@ export interface StickySidebarProps {
|
||||||
menu: MenuStore;
|
menu: MenuStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StickySidebarState {
|
||||||
|
offsetTop?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const stickyfill = Stickyfill && Stickyfill();
|
const stickyfill = Stickyfill && Stickyfill();
|
||||||
|
|
||||||
const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
||||||
|
@ -77,13 +81,26 @@ const FloatingButton = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class StickyResponsiveSidebar extends React.Component<StickySidebarProps> {
|
export class StickyResponsiveSidebar extends React.Component<
|
||||||
|
StickySidebarProps,
|
||||||
|
StickySidebarState
|
||||||
|
> {
|
||||||
|
static contextType = OptionsContext;
|
||||||
|
context!: React.ContextType<typeof OptionsContext>;
|
||||||
|
state: StickySidebarState = { offsetTop: '0px' };
|
||||||
|
|
||||||
stickyElement: Element;
|
stickyElement: Element;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (stickyfill) {
|
if (stickyfill) {
|
||||||
stickyfill.add(this.stickyElement);
|
stickyfill.add(this.stickyElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rerender when hydrating from SSR
|
||||||
|
// see https://github.com/facebook/react/issues/8017#issuecomment-256351955
|
||||||
|
this.setState({
|
||||||
|
offsetTop: this.getScrollYOffset(this.context),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
@ -92,7 +109,7 @@ export class StickyResponsiveSidebar extends React.Component<StickySidebarProps>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getScrollYOffset(options) {
|
getScrollYOffset(options: RedocNormalizedOptions) {
|
||||||
let top;
|
let top;
|
||||||
if (this.props.scrollYOffset !== undefined) {
|
if (this.props.scrollYOffset !== undefined) {
|
||||||
top = RedocNormalizedOptions.normalizeScrollYOffset(this.props.scrollYOffset)();
|
top = RedocNormalizedOptions.normalizeScrollYOffset(this.props.scrollYOffset)();
|
||||||
|
@ -105,43 +122,32 @@ export class StickyResponsiveSidebar extends React.Component<StickySidebarProps>
|
||||||
render() {
|
render() {
|
||||||
const open = this.props.menu.sideBarOpened;
|
const open = this.props.menu.sideBarOpened;
|
||||||
|
|
||||||
const style = options => {
|
const top = this.state.offsetTop;
|
||||||
const top = this.getScrollYOffset(options);
|
|
||||||
return {
|
|
||||||
top,
|
|
||||||
height: `calc(100vh - ${top})`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsContext.Consumer>
|
<>
|
||||||
{options => (
|
<StyledStickySidebar
|
||||||
<>
|
open={open}
|
||||||
<StyledStickySidebar
|
className={this.props.className}
|
||||||
open={open}
|
style={{
|
||||||
className={this.props.className}
|
top,
|
||||||
style={style(options)}
|
height: `calc(100vh - ${top})`,
|
||||||
// tslint:disable-next-line
|
}}
|
||||||
ref={el => {
|
// tslint:disable-next-line
|
||||||
this.stickyElement = el as any;
|
ref={el => {
|
||||||
}}
|
this.stickyElement = el as any;
|
||||||
>
|
}}
|
||||||
{this.props.children}
|
>
|
||||||
</StyledStickySidebar>
|
{this.props.children}
|
||||||
<FloatingButton onClick={this.toggleNavMenu}>
|
</StyledStickySidebar>
|
||||||
<AnimatedChevronButton open={open} />
|
<FloatingButton onClick={this.toggleNavMenu}>
|
||||||
</FloatingButton>
|
<AnimatedChevronButton open={open} />
|
||||||
</>
|
</FloatingButton>
|
||||||
)}
|
</>
|
||||||
</OptionsContext.Consumer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private toggleNavMenu = () => {
|
private toggleNavMenu = () => {
|
||||||
this.props.menu.toggleSidebar();
|
this.props.menu.toggleSidebar();
|
||||||
};
|
};
|
||||||
|
|
||||||
// private closeNavMenu = () => {
|
|
||||||
// this.setState({ open: false });
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"description": "",
|
"description": "",
|
||||||
"example": undefined,
|
"example": undefined,
|
||||||
"expanded": false,
|
"expanded": false,
|
||||||
|
"explode": false,
|
||||||
"in": undefined,
|
"in": undefined,
|
||||||
"kind": "field",
|
"kind": "field",
|
||||||
"name": "packSize",
|
"name": "packSize",
|
||||||
|
@ -59,6 +60,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
||||||
"description": "",
|
"description": "",
|
||||||
"example": undefined,
|
"example": undefined,
|
||||||
"expanded": false,
|
"expanded": false,
|
||||||
|
"explode": false,
|
||||||
"in": undefined,
|
"in": undefined,
|
||||||
"kind": "field",
|
"kind": "field",
|
||||||
"name": "type",
|
"name": "type",
|
||||||
|
|
|
@ -28,3 +28,5 @@ export * from './OptionsProvider';
|
||||||
export * from './SideMenu/';
|
export * from './SideMenu/';
|
||||||
export * from './StickySidebar/StickyResponsiveSidebar';
|
export * from './StickySidebar/StickyResponsiveSidebar';
|
||||||
export * from './SearchBox/SearchBox';
|
export * from './SearchBox/SearchBox';
|
||||||
|
export * from './SchemaDefinition/SchemaDefinition';
|
||||||
|
export * from './SourceCode/SourceCode';
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import 'core-js/es6/promise';
|
import 'core-js/es/promise';
|
||||||
import 'core-js/fn/array/find';
|
|
||||||
import 'core-js/fn/object/assign';
|
|
||||||
import 'core-js/fn/string/ends-with';
|
|
||||||
import 'core-js/fn/string/starts-with';
|
|
||||||
|
|
||||||
import 'core-js/es6/map';
|
import 'core-js/es/array/find';
|
||||||
import 'core-js/es6/symbol';
|
import 'core-js/es/object/assign';
|
||||||
|
import 'core-js/es/string/ends-with';
|
||||||
|
import 'core-js/es/string/starts-with';
|
||||||
|
|
||||||
|
import 'core-js/es/map';
|
||||||
|
import 'core-js/es/symbol';
|
||||||
|
|
||||||
|
import 'unfetch/polyfill/index';
|
||||||
|
import 'url-polyfill';
|
||||||
|
|
|
@ -10,8 +10,13 @@ import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOption
|
||||||
import { ScrollService } from './ScrollService';
|
import { ScrollService } from './ScrollService';
|
||||||
import { SearchStore } from './SearchStore';
|
import { SearchStore } from './SearchStore';
|
||||||
|
|
||||||
|
import { SchemaDefinition } from '../components/SchemaDefinition/SchemaDefinition';
|
||||||
import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
|
import { SecurityDefs } from '../components/SecuritySchemes/SecuritySchemes';
|
||||||
import { SECURITY_DEFINITIONS_COMPONENT_NAME } from '../utils/openapi';
|
import {
|
||||||
|
SCHEMA_DEFINITION_JSX_NAME,
|
||||||
|
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||||
|
SECURITY_DEFINITIONS_JSX_NAME,
|
||||||
|
} from '../utils/openapi';
|
||||||
|
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
menu: {
|
menu: {
|
||||||
|
@ -151,5 +156,18 @@ const DEFAULT_OPTIONS: RedocRawOptions = {
|
||||||
securitySchemes: store.spec.securitySchemes,
|
securitySchemes: store.spec.securitySchemes,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
[SECURITY_DEFINITIONS_JSX_NAME]: {
|
||||||
|
component: SecurityDefs,
|
||||||
|
propsSelector: (store: AppStore) => ({
|
||||||
|
securitySchemes: store.spec.securitySchemes,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
[SCHEMA_DEFINITION_JSX_NAME]: {
|
||||||
|
component: SchemaDefinition,
|
||||||
|
propsSelector: (store: AppStore) => ({
|
||||||
|
parser: store.spec.parser,
|
||||||
|
options: store.options,
|
||||||
|
}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,10 @@ export class ClipboardService {
|
||||||
if ((document as any).selection) {
|
if ((document as any).selection) {
|
||||||
(document as any).selection.empty();
|
(document as any).selection.empty();
|
||||||
} else if (window.getSelection) {
|
} else if (window.getSelection) {
|
||||||
window.getSelection()!.removeAllRanges();
|
const selection = window.getSelection();
|
||||||
|
if (selection) {
|
||||||
|
selection.removeAllRanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ export class HistoryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentId(): string {
|
get currentId(): string {
|
||||||
return IS_BROWSER ? window.location.hash.substring(1) : '';
|
return IS_BROWSER ? decodeURIComponent(window.location.hash.substring(1)) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
linkForId(id: string) {
|
linkForId(id: string) {
|
||||||
|
|
37
src/services/Labels.ts
Normal file
37
src/services/Labels.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
export interface LabelsConfig {
|
||||||
|
enum: string;
|
||||||
|
enumSingleValue: string;
|
||||||
|
enumArray: string;
|
||||||
|
default: string;
|
||||||
|
deprecated: string;
|
||||||
|
example: string;
|
||||||
|
nullable: string;
|
||||||
|
recursive: string;
|
||||||
|
arrayOf: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LabelsConfigRaw = Partial<LabelsConfig>;
|
||||||
|
|
||||||
|
const labels: LabelsConfig = {
|
||||||
|
enum: 'Enum',
|
||||||
|
enumSingleValue: 'Value',
|
||||||
|
enumArray: 'Items',
|
||||||
|
default: 'Default',
|
||||||
|
deprecated: 'Deprecated',
|
||||||
|
example: 'Example',
|
||||||
|
nullable: 'Nullable',
|
||||||
|
recursive: 'Recursive',
|
||||||
|
arrayOf: 'Array of ',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function setRedocLabels(_labels?: LabelsConfigRaw) {
|
||||||
|
Object.assign(labels, _labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function l(key: keyof LabelsConfig, idx?: number): string {
|
||||||
|
const label = labels[key];
|
||||||
|
if (idx !== undefined) {
|
||||||
|
return label[idx];
|
||||||
|
}
|
||||||
|
return label;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import * as marked from 'marked';
|
import * as marked from 'marked';
|
||||||
|
|
||||||
import { highlight, safeSlugify } from '../utils';
|
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
||||||
import { AppStore } from './AppStore';
|
import { AppStore } from './AppStore';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ export class MarkdownRenderer {
|
||||||
container: MarkdownHeading[] = this.headings,
|
container: MarkdownHeading[] = this.headings,
|
||||||
parentId?: string,
|
parentId?: string,
|
||||||
): MarkdownHeading {
|
): MarkdownHeading {
|
||||||
|
name = unescapeHTMLChars(name);
|
||||||
const item = {
|
const item = {
|
||||||
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
|
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
|
||||||
name,
|
name,
|
||||||
|
@ -88,7 +89,7 @@ export class MarkdownRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
attachHeadingsDescriptions(rawText: string) {
|
attachHeadingsDescriptions(rawText: string) {
|
||||||
const buildRegexp = heading => {
|
const buildRegexp = (heading: MarkdownHeading) => {
|
||||||
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class MenuBuilder {
|
||||||
|
|
||||||
const items: ContentItemModel[] = [];
|
const items: ContentItemModel[] = [];
|
||||||
const tagsMap = MenuBuilder.getTagsWithOperations(spec);
|
const tagsMap = MenuBuilder.getTagsWithOperations(spec);
|
||||||
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', options));
|
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', undefined, 1, options));
|
||||||
if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) {
|
if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) {
|
||||||
items.push(
|
items.push(
|
||||||
...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap, options),
|
...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap, options),
|
||||||
|
@ -59,14 +59,16 @@ export class MenuBuilder {
|
||||||
*/
|
*/
|
||||||
static addMarkdownItems(
|
static addMarkdownItems(
|
||||||
description: string,
|
description: string,
|
||||||
|
parent: GroupModel | undefined,
|
||||||
|
initialDepth: number,
|
||||||
options: RedocNormalizedOptions,
|
options: RedocNormalizedOptions,
|
||||||
): ContentItemModel[] {
|
): ContentItemModel[] {
|
||||||
const renderer = new MarkdownRenderer(options);
|
const renderer = new MarkdownRenderer(options);
|
||||||
const headings = renderer.extractHeadings(description || '');
|
const headings = renderer.extractHeadings(description || '');
|
||||||
|
|
||||||
const mapHeadingsDeep = (parent, items, depth = 1) =>
|
const mapHeadingsDeep = (_parent, items, depth = 1) =>
|
||||||
items.map(heading => {
|
items.map(heading => {
|
||||||
const group = new GroupModel('section', heading, parent);
|
const group = new GroupModel('section', heading, _parent);
|
||||||
group.depth = depth;
|
group.depth = depth;
|
||||||
if (heading.items) {
|
if (heading.items) {
|
||||||
group.items = mapHeadingsDeep(group, heading.items, depth + 1);
|
group.items = mapHeadingsDeep(group, heading.items, depth + 1);
|
||||||
|
@ -82,7 +84,7 @@ export class MenuBuilder {
|
||||||
return group;
|
return group;
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapHeadingsDeep(undefined, headings);
|
return mapHeadingsDeep(parent, headings, initialDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -144,15 +146,22 @@ export class MenuBuilder {
|
||||||
}
|
}
|
||||||
const item = new GroupModel('tag', tag, parent);
|
const item = new GroupModel('tag', tag, parent);
|
||||||
item.depth = GROUP_DEPTH + 1;
|
item.depth = GROUP_DEPTH + 1;
|
||||||
item.items = this.getOperationsItems(parser, item, tag, item.depth + 1, options);
|
|
||||||
|
|
||||||
// don't put empty tag into content, instead put its operations
|
// don't put empty tag into content, instead put its operations
|
||||||
if (tag.name === '') {
|
if (tag.name === '') {
|
||||||
const items = this.getOperationsItems(parser, undefined, tag, item.depth + 1, options);
|
const items = [
|
||||||
|
...MenuBuilder.addMarkdownItems(tag.description || '', item, item.depth + 1, options),
|
||||||
|
...this.getOperationsItems(parser, undefined, tag, item.depth + 1, options),
|
||||||
|
];
|
||||||
res.push(...items);
|
res.push(...items);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.items = [
|
||||||
|
...MenuBuilder.addMarkdownItems(tag.description || '', item, item.depth + 1, options),
|
||||||
|
...this.getOperationsItems(parser, item, tag, item.depth + 1, options),
|
||||||
|
];
|
||||||
|
|
||||||
res.push(item);
|
res.push(item);
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -116,7 +116,7 @@ export class MenuStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isScrolledDown) {
|
if (isScrolledDown) {
|
||||||
const el = this.getElementAt(itemIdx + 1);
|
const el = this.getElementAtOrFirstChild(itemIdx + 1);
|
||||||
if (this.scroll.isElementBellow(el)) {
|
if (this.scroll.isElementBellow(el)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,18 @@ export class MenuStore {
|
||||||
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
|
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get section/operation DOM Node related to the item or if it is group item, returns first item of the group
|
||||||
|
* @param idx item absolute index
|
||||||
|
*/
|
||||||
|
getElementAtOrFirstChild(idx: number): Element | null {
|
||||||
|
let item = this.flatItems[idx];
|
||||||
|
if (item && item.type === 'group') {
|
||||||
|
item = item.items[0];
|
||||||
|
}
|
||||||
|
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* current active item
|
* current active item
|
||||||
*/
|
*/
|
||||||
|
@ -189,6 +201,11 @@ export class MenuStore {
|
||||||
if ((this.activeItem && this.activeItem.id) === (item && item.id)) {
|
if ((this.activeItem && this.activeItem.id) === (item && item.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item && item.type === 'group') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.deactivate(this.activeItem);
|
this.deactivate(this.activeItem);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
this.history.replace('', rewriteHistory);
|
this.history.replace('', rewriteHistory);
|
||||||
|
|
|
@ -4,7 +4,11 @@ import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
|
||||||
|
|
||||||
import { appendToMdHeading, IS_BROWSER } from '../utils/';
|
import { appendToMdHeading, IS_BROWSER } from '../utils/';
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer } from '../utils/JsonPointer';
|
||||||
import { isNamedDefinition, SECURITY_DEFINITIONS_COMPONENT_NAME } from '../utils/openapi';
|
import {
|
||||||
|
isNamedDefinition,
|
||||||
|
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||||
|
SECURITY_DEFINITIONS_JSX_NAME,
|
||||||
|
} from '../utils/openapi';
|
||||||
import { buildComponentComment, MarkdownRenderer } from './MarkdownRenderer';
|
import { buildComponentComment, MarkdownRenderer } from './MarkdownRenderer';
|
||||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||||
|
|
||||||
|
@ -40,6 +44,7 @@ class RefCounter {
|
||||||
export class OpenAPIParser {
|
export class OpenAPIParser {
|
||||||
specUrl?: string;
|
specUrl?: string;
|
||||||
spec: OpenAPISpec;
|
spec: OpenAPISpec;
|
||||||
|
mergeRefs: Set<string>;
|
||||||
|
|
||||||
private _refCounter: RefCounter = new RefCounter();
|
private _refCounter: RefCounter = new RefCounter();
|
||||||
|
|
||||||
|
@ -53,6 +58,8 @@ export class OpenAPIParser {
|
||||||
|
|
||||||
this.spec = spec;
|
this.spec = spec;
|
||||||
|
|
||||||
|
this.mergeRefs = new Set();
|
||||||
|
|
||||||
const href = IS_BROWSER ? window.location.href : '';
|
const href = IS_BROWSER ? window.location.href : '';
|
||||||
if (typeof specUrl === 'string') {
|
if (typeof specUrl === 'string') {
|
||||||
this.specUrl = urlResolve(href, specUrl);
|
this.specUrl = urlResolve(href, specUrl);
|
||||||
|
@ -74,7 +81,10 @@ export class OpenAPIParser {
|
||||||
) {
|
) {
|
||||||
// Automatically inject Authentication section with SecurityDefinitions component
|
// Automatically inject Authentication section with SecurityDefinitions component
|
||||||
const description = spec.info.description || '';
|
const description = spec.info.description || '';
|
||||||
if (!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_COMPONENT_NAME)) {
|
if (
|
||||||
|
!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_COMPONENT_NAME) &&
|
||||||
|
!MarkdownRenderer.containsComponent(description, SECURITY_DEFINITIONS_JSX_NAME)
|
||||||
|
) {
|
||||||
const comment = buildComponentComment(SECURITY_DEFINITIONS_COMPONENT_NAME);
|
const comment = buildComponentComment(SECURITY_DEFINITIONS_COMPONENT_NAME);
|
||||||
spec.info.description = appendToMdHeading(description, 'Authentication', comment);
|
spec.info.description = appendToMdHeading(description, 'Authentication', comment);
|
||||||
}
|
}
|
||||||
|
@ -176,7 +186,12 @@ export class OpenAPIParser {
|
||||||
schema: OpenAPISchema,
|
schema: OpenAPISchema,
|
||||||
$ref?: string,
|
$ref?: string,
|
||||||
forceCircular: boolean = false,
|
forceCircular: boolean = false,
|
||||||
|
used$Refs = new Set<string>(),
|
||||||
): MergedOpenAPISchema {
|
): MergedOpenAPISchema {
|
||||||
|
if ($ref) {
|
||||||
|
used$Refs.add($ref);
|
||||||
|
}
|
||||||
|
|
||||||
schema = this.hoistOneOfs(schema);
|
schema = this.hoistOneOfs(schema);
|
||||||
|
|
||||||
if (schema.allOf === undefined) {
|
if (schema.allOf === undefined) {
|
||||||
|
@ -187,6 +202,7 @@ export class OpenAPIParser {
|
||||||
...schema,
|
...schema,
|
||||||
allOf: undefined,
|
allOf: undefined,
|
||||||
parentRefs: [],
|
parentRefs: [],
|
||||||
|
title: schema.title || (isNamedDefinition($ref) ? JsonPointer.baseName($ref) : undefined),
|
||||||
};
|
};
|
||||||
|
|
||||||
// avoid mutating inner objects
|
// avoid mutating inner objects
|
||||||
|
@ -197,16 +213,25 @@ export class OpenAPIParser {
|
||||||
receiver.items = { ...receiver.items };
|
receiver.items = { ...receiver.items };
|
||||||
}
|
}
|
||||||
|
|
||||||
const allOfSchemas = schema.allOf.map(subSchema => {
|
const allOfSchemas = schema.allOf
|
||||||
const resolved = this.deref(subSchema, forceCircular);
|
.map(subSchema => {
|
||||||
const subRef = subSchema.$ref || undefined;
|
if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
|
||||||
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular);
|
return undefined;
|
||||||
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
}
|
||||||
return {
|
|
||||||
$ref: subRef,
|
const resolved = this.deref(subSchema, forceCircular);
|
||||||
schema: subMerged,
|
const subRef = subSchema.$ref || undefined;
|
||||||
};
|
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular, used$Refs);
|
||||||
});
|
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
||||||
|
return {
|
||||||
|
$ref: subRef,
|
||||||
|
schema: subMerged,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(child => child !== undefined) as Array<{
|
||||||
|
$ref: string | undefined;
|
||||||
|
schema: MergedOpenAPISchema;
|
||||||
|
}>;
|
||||||
|
|
||||||
for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
|
for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
|
||||||
if (
|
if (
|
||||||
|
@ -257,17 +282,12 @@ export class OpenAPIParser {
|
||||||
receiver.parentRefs!.push(subSchemaRef);
|
receiver.parentRefs!.push(subSchemaRef);
|
||||||
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
||||||
// this is not so correct behaviour. comented out for now
|
// this is not so correct behaviour. comented out for now
|
||||||
// ref: https://github.com/Rebilly/ReDoc/issues/601
|
// ref: https://github.com/Redocly/redoc/issues/601
|
||||||
// receiver.title = JsonPointer.baseName(subSchemaRef);
|
// receiver.title = JsonPointer.baseName(subSchemaRef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// name of definition or title on top level
|
|
||||||
if (schema.title === undefined && isNamedDefinition($ref)) {
|
|
||||||
receiver.title = JsonPointer.baseName($ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
return receiver;
|
return receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import defaultTheme, { ResolvedThemeInterface, resolveTheme, ThemeInterface } fr
|
||||||
import { querySelector } from '../utils/dom';
|
import { querySelector } from '../utils/dom';
|
||||||
import { isNumeric, mergeObjects } from '../utils/helpers';
|
import { isNumeric, mergeObjects } from '../utils/helpers';
|
||||||
|
|
||||||
|
import { LabelsConfigRaw, setRedocLabels } from './Labels';
|
||||||
import { MDXComponentMeta } from './MarkdownRenderer';
|
import { MDXComponentMeta } from './MarkdownRenderer';
|
||||||
|
|
||||||
export interface RedocRawOptions {
|
export interface RedocRawOptions {
|
||||||
|
@ -20,10 +21,19 @@ export interface RedocRawOptions {
|
||||||
disableSearch?: boolean | string;
|
disableSearch?: boolean | string;
|
||||||
onlyRequiredInSamples?: boolean | string;
|
onlyRequiredInSamples?: boolean | string;
|
||||||
showExtensions?: boolean | string | string[];
|
showExtensions?: boolean | string | string[];
|
||||||
|
hideSingleRequestSampleTab?: boolean | string;
|
||||||
|
menuToggle?: boolean | string;
|
||||||
|
jsonSampleExpandLevel?: number | string | 'all';
|
||||||
|
|
||||||
unstable_ignoreMimeParameters?: boolean;
|
unstable_ignoreMimeParameters?: boolean;
|
||||||
|
|
||||||
allowedMdComponents?: Dict<MDXComponentMeta>;
|
allowedMdComponents?: Dict<MDXComponentMeta>;
|
||||||
|
|
||||||
|
labels?: LabelsConfigRaw;
|
||||||
|
|
||||||
|
enumSkipQuotes?: boolean | string;
|
||||||
|
|
||||||
|
expandDefaultServerVariables?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function argValueToBoolean(val?: string | boolean): boolean {
|
function argValueToBoolean(val?: string | boolean): boolean {
|
||||||
|
@ -106,6 +116,16 @@ export class RedocNormalizedOptions {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static normalizeJsonSampleExpandLevel(level?: number | string | 'all'): number {
|
||||||
|
if (level === 'all') {
|
||||||
|
return +Infinity;
|
||||||
|
}
|
||||||
|
if (!isNaN(Number(level))) {
|
||||||
|
return Math.ceil(Number(level));
|
||||||
|
}
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
theme: ResolvedThemeInterface;
|
theme: ResolvedThemeInterface;
|
||||||
scrollYOffset: () => number;
|
scrollYOffset: () => number;
|
||||||
hideHostname: boolean;
|
hideHostname: boolean;
|
||||||
|
@ -120,11 +140,17 @@ export class RedocNormalizedOptions {
|
||||||
disableSearch: boolean;
|
disableSearch: boolean;
|
||||||
onlyRequiredInSamples: boolean;
|
onlyRequiredInSamples: boolean;
|
||||||
showExtensions: boolean | string[];
|
showExtensions: boolean | string[];
|
||||||
|
hideSingleRequestSampleTab: boolean;
|
||||||
|
menuToggle: boolean;
|
||||||
|
jsonSampleExpandLevel: number;
|
||||||
|
enumSkipQuotes: boolean;
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
unstable_ignoreMimeParameters: boolean;
|
unstable_ignoreMimeParameters: boolean;
|
||||||
allowedMdComponents: Dict<MDXComponentMeta>;
|
allowedMdComponents: Dict<MDXComponentMeta>;
|
||||||
|
|
||||||
|
expandDefaultServerVariables: boolean;
|
||||||
|
|
||||||
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
||||||
raw = { ...defaults, ...raw };
|
raw = { ...defaults, ...raw };
|
||||||
const hook = raw.theme && raw.theme.extensionsHook;
|
const hook = raw.theme && raw.theme.extensionsHook;
|
||||||
|
@ -134,6 +160,9 @@ export class RedocNormalizedOptions {
|
||||||
|
|
||||||
this.theme.extensionsHook = hook as any;
|
this.theme.extensionsHook = hook as any;
|
||||||
|
|
||||||
|
// do not support dynamic labels changes. Labels should be configured before
|
||||||
|
setRedocLabels(raw.labels);
|
||||||
|
|
||||||
this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset);
|
this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset);
|
||||||
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
|
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
|
||||||
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
|
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
|
||||||
|
@ -147,9 +176,17 @@ export class RedocNormalizedOptions {
|
||||||
this.disableSearch = true; //argValueToBoolean(raw.disableSearch);
|
this.disableSearch = true; //argValueToBoolean(raw.disableSearch);
|
||||||
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
||||||
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
||||||
|
this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab);
|
||||||
|
this.menuToggle = argValueToBoolean(raw.menuToggle);
|
||||||
|
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
||||||
|
raw.jsonSampleExpandLevel,
|
||||||
|
);
|
||||||
|
this.enumSkipQuotes = argValueToBoolean(raw.enumSkipQuotes);
|
||||||
|
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
||||||
this.allowedMdComponents = raw.allowedMdComponents || {};
|
this.allowedMdComponents = raw.allowedMdComponents || {};
|
||||||
|
|
||||||
|
this.expandDefaultServerVariables = argValueToBoolean(raw.expandDefaultServerVariables);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as lunr from 'lunr';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
require('core-js/es6/promise'); // bundle into worker
|
require('core-js/es/promise'); // bundle into worker
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// nope
|
// nope
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"title": undefined,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
@ -38,6 +39,7 @@ Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"title": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -59,6 +61,7 @@ Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"title": undefined,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"allOf": undefined,
|
"allOf": undefined,
|
||||||
|
@ -76,6 +79,7 @@ Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"title": undefined,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,13 @@
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"name": "test_name",
|
"name": "test_name",
|
||||||
"schema": { "type": "string" }
|
"schema": { "type": "string" }
|
||||||
|
},
|
||||||
|
"serializationParam": {
|
||||||
|
"in": "query",
|
||||||
|
"name": "serialization_test_name",
|
||||||
|
"schema": { "type": "array" },
|
||||||
|
"style": "form",
|
||||||
|
"explode": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"headers": {
|
"headers": {
|
||||||
|
@ -21,4 +28,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,23 @@ describe('Models', () => {
|
||||||
expect(field.schema.type).toEqual('string');
|
expect(field.schema.type).toEqual('string');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('field details relevant for parameter serialization', () => {
|
||||||
|
const field = new FieldModel(
|
||||||
|
parser,
|
||||||
|
{
|
||||||
|
$ref: '#/components/parameters/serializationParam',
|
||||||
|
},
|
||||||
|
'#/components/parameters/serializationParam',
|
||||||
|
opts,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(field.name).toEqual('serialization_test_name');
|
||||||
|
expect(field.in).toEqual('query');
|
||||||
|
expect(field.schema.type).toEqual('array');
|
||||||
|
expect(field.style).toEqual('form');
|
||||||
|
expect(field.explode).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
test('field name should populated from name even if $ref (headers)', () => {
|
test('field name should populated from name even if $ref (headers)', () => {
|
||||||
const field = new FieldModel(
|
const field = new FieldModel(
|
||||||
parser,
|
parser,
|
||||||
|
|
|
@ -37,7 +37,7 @@ describe('Models', () => {
|
||||||
parser = new OpenAPIParser(spec, undefined, opts);
|
parser = new OpenAPIParser(spec, undefined, opts);
|
||||||
const schema = new SchemaModel(parser, spec.components.schemas.WithArray, '', opts);
|
const schema = new SchemaModel(parser, spec.components.schemas.WithArray, '', opts);
|
||||||
expect(schema.oneOf).toHaveLength(2);
|
expect(schema.oneOf).toHaveLength(2);
|
||||||
expect(schema.displayType).toBe('(Array of string or number) or string');
|
expect(schema.displayType).toBe('(Array of strings or numbers) or string');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@ export class ExampleModel {
|
||||||
constructor(
|
constructor(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
infoOrRef: Referenced<OpenAPIExample>,
|
infoOrRef: Referenced<OpenAPIExample>,
|
||||||
mime: string,
|
public mime: string,
|
||||||
encoding?: { [field: string]: OpenAPIEncoding },
|
encoding?: { [field: string]: OpenAPIEncoding },
|
||||||
) {
|
) {
|
||||||
const example = parser.deref(infoOrRef);
|
const example = parser.deref(infoOrRef);
|
||||||
|
|
|
@ -1,12 +1,30 @@
|
||||||
import { action, observable } from 'mobx';
|
import { action, observable } from 'mobx';
|
||||||
|
|
||||||
import { OpenAPIParameter, Referenced } from '../../types';
|
import {
|
||||||
|
OpenAPIParameter,
|
||||||
|
OpenAPIParameterLocation,
|
||||||
|
OpenAPIParameterStyle,
|
||||||
|
Referenced,
|
||||||
|
} from '../../types';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
|
||||||
import { extractExtensions } from '../../utils/openapi';
|
import { extractExtensions } from '../../utils/openapi';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { SchemaModel } from './Schema';
|
import { SchemaModel } from './Schema';
|
||||||
|
|
||||||
|
function getDefaultStyleValue(parameterLocation: OpenAPIParameterLocation): OpenAPIParameterStyle {
|
||||||
|
switch (parameterLocation) {
|
||||||
|
case 'header':
|
||||||
|
return 'simple';
|
||||||
|
case 'query':
|
||||||
|
return 'form';
|
||||||
|
case 'path':
|
||||||
|
return 'simple';
|
||||||
|
default:
|
||||||
|
return 'form';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Field or Parameter model ready to be used by components
|
* Field or Parameter model ready to be used by components
|
||||||
*/
|
*/
|
||||||
|
@ -20,9 +38,13 @@ export class FieldModel {
|
||||||
description: string;
|
description: string;
|
||||||
example?: string;
|
example?: string;
|
||||||
deprecated: boolean;
|
deprecated: boolean;
|
||||||
in?: string;
|
in?: OpenAPIParameterLocation;
|
||||||
kind: string;
|
kind: string;
|
||||||
extensions?: Dict<any>;
|
extensions?: Dict<any>;
|
||||||
|
explode: boolean;
|
||||||
|
style?: OpenAPIParameterStyle;
|
||||||
|
|
||||||
|
serializationMime?: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
|
@ -35,11 +57,29 @@ export class FieldModel {
|
||||||
this.name = infoOrRef.name || info.name;
|
this.name = infoOrRef.name || info.name;
|
||||||
this.in = info.in;
|
this.in = info.in;
|
||||||
this.required = !!info.required;
|
this.required = !!info.required;
|
||||||
this.schema = new SchemaModel(parser, info.schema || {}, pointer, options);
|
|
||||||
|
let fieldSchema = info.schema;
|
||||||
|
let serializationMime = '';
|
||||||
|
if (!fieldSchema && info.in && info.content) {
|
||||||
|
serializationMime = Object.keys(info.content)[0];
|
||||||
|
fieldSchema = info.content[serializationMime] && info.content[serializationMime].schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.schema = new SchemaModel(parser, fieldSchema || {}, pointer, options);
|
||||||
this.description =
|
this.description =
|
||||||
info.description === undefined ? this.schema.description || '' : info.description;
|
info.description === undefined ? this.schema.description || '' : info.description;
|
||||||
this.example = info.example || this.schema.example;
|
this.example = info.example || this.schema.example;
|
||||||
|
|
||||||
|
if (serializationMime) {
|
||||||
|
this.serializationMime = serializationMime;
|
||||||
|
} else if (info.style) {
|
||||||
|
this.style = info.style;
|
||||||
|
} else if (this.in) {
|
||||||
|
this.style = getDefaultStyleValue(this.in);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.explode = !!info.explode;
|
||||||
|
|
||||||
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
|
this.deprecated = info.deprecated === undefined ? !!this.schema.deprecated : info.deprecated;
|
||||||
parser.exitRef(infoOrRef);
|
parser.exitRef(infoOrRef);
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,14 @@ export class GroupModel implements IMenuItem {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.name = tagOrGroup['x-displayName'] || tagOrGroup.name;
|
this.name = tagOrGroup['x-displayName'] || tagOrGroup.name;
|
||||||
this.level = (tagOrGroup as MarkdownHeading).level || 1;
|
this.level = (tagOrGroup as MarkdownHeading).level || 1;
|
||||||
|
|
||||||
|
// remove sections from markdown, same as in ApiInfo
|
||||||
this.description = tagOrGroup.description || '';
|
this.description = tagOrGroup.description || '';
|
||||||
|
const firstHeadingLinePos = this.description.search(/^##?\s+/m);
|
||||||
|
if (firstHeadingLinePos > -1) {
|
||||||
|
this.description = this.description.substring(0, firstHeadingLinePos);
|
||||||
|
}
|
||||||
|
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.externalDocs = (tagOrGroup as OpenAPITag).externalDocs;
|
this.externalDocs = (tagOrGroup as OpenAPITag).externalDocs;
|
||||||
|
|
||||||
|
|
|
@ -77,8 +77,8 @@ export class OperationModel implements IMenuItem {
|
||||||
operationSpec.operationId !== undefined
|
operationSpec.operationId !== undefined
|
||||||
? 'operation/' + operationSpec.operationId
|
? 'operation/' + operationSpec.operationId
|
||||||
: parent !== undefined
|
: parent !== undefined
|
||||||
? parent.id + this.pointer
|
? parent.id + this.pointer
|
||||||
: this.pointer;
|
: this.pointer;
|
||||||
|
|
||||||
this.name = getOperationSummary(operationSpec);
|
this.name = getOperationSummary(operationSpec);
|
||||||
this.description = operationSpec.description;
|
this.description = operationSpec.description;
|
||||||
|
|
|
@ -14,10 +14,13 @@ import {
|
||||||
isNamedDefinition,
|
isNamedDefinition,
|
||||||
isPrimitiveType,
|
isPrimitiveType,
|
||||||
JsonPointer,
|
JsonPointer,
|
||||||
|
pluralizeType,
|
||||||
sortByField,
|
sortByField,
|
||||||
sortByRequired,
|
sortByRequired,
|
||||||
} from '../../utils/';
|
} from '../../utils/';
|
||||||
|
|
||||||
|
import { l } from '../Labels';
|
||||||
|
|
||||||
// TODO: refactor this model, maybe use getters instead of copying all the values
|
// TODO: refactor this model, maybe use getters instead of copying all the values
|
||||||
export class SchemaModel {
|
export class SchemaModel {
|
||||||
pointer: string;
|
pointer: string;
|
||||||
|
@ -145,9 +148,9 @@ export class SchemaModel {
|
||||||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
||||||
} else if (this.type === 'array' && schema.items) {
|
} else if (this.type === 'array' && schema.items) {
|
||||||
this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options);
|
this.items = new SchemaModel(parser, schema.items, this.pointer + '/items', this.options);
|
||||||
this.displayType = this.items.displayType;
|
this.displayType = pluralizeType(this.items.displayType);
|
||||||
this.displayFormat = this.items.format;
|
this.displayFormat = this.items.format;
|
||||||
this.typePrefix = this.items.typePrefix + 'Array of ';
|
this.typePrefix = this.items.typePrefix + l('arrayOf');
|
||||||
this.title = this.title || this.items.title;
|
this.title = this.title || this.items.title;
|
||||||
this.isPrimitive = this.items.isPrimitive;
|
this.isPrimitive = this.items.isPrimitive;
|
||||||
if (this.example === undefined && this.items.example !== undefined) {
|
if (this.example === undefined && this.items.example !== undefined) {
|
||||||
|
@ -220,7 +223,7 @@ export class SchemaModel {
|
||||||
if (variant.$ref === undefined) {
|
if (variant.$ref === undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const name = JsonPointer.dirName(variant.$ref);
|
const name = JsonPointer.baseName(variant.$ref);
|
||||||
derived[variant.$ref] = name;
|
derived[variant.$ref] = name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -289,7 +292,10 @@ function buildFields(
|
||||||
new FieldModel(
|
new FieldModel(
|
||||||
parser,
|
parser,
|
||||||
{
|
{
|
||||||
name: 'property name *',
|
name: (typeof additionalProps === 'object'
|
||||||
|
? additionalProps['x-additionalPropertiesName'] || 'property name'
|
||||||
|
: 'property name'
|
||||||
|
).concat('*'),
|
||||||
required: false,
|
required: false,
|
||||||
schema: additionalProps === true ? {} : additionalProps,
|
schema: additionalProps === true ? {} : additionalProps,
|
||||||
kind: 'additionalProperties',
|
kind: 'additionalProperties',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import slugify from 'slugify';
|
import slugify from 'slugify';
|
||||||
import { mapWithLast, appendToMdHeading, mergeObjects, safeSlugify } from '../helpers';
|
import { appendToMdHeading, mapWithLast, mergeObjects, safeSlugify, titleize } from '../helpers';
|
||||||
|
|
||||||
describe('Utils', () => {
|
describe('Utils', () => {
|
||||||
describe('helpers', () => {
|
describe('helpers', () => {
|
||||||
|
@ -60,7 +60,7 @@ describe('Utils', () => {
|
||||||
test('should behave like Object.assign on the top level', () => {
|
test('should behave like Object.assign on the top level', () => {
|
||||||
const obj1 = { a: { a1: 'A1' }, c: 'C' };
|
const obj1 = { a: { a1: 'A1' }, c: 'C' };
|
||||||
const obj2 = { a: undefined, b: { b1: 'B1' } };
|
const obj2 = { a: undefined, b: { b1: 'B1' } };
|
||||||
expect(mergeObjects({}, obj1, obj2)).toEqual(Object.assign({}, obj1, obj2));
|
expect(mergeObjects({}, obj1, obj2)).toEqual({ ...obj1, ...obj2 });
|
||||||
});
|
});
|
||||||
test('should not merge array values, just override', () => {
|
test('should not merge array values, just override', () => {
|
||||||
const obj1 = { a: ['A', 'B'] };
|
const obj1 = { a: ['A', 'B'] };
|
||||||
|
@ -68,5 +68,11 @@ describe('Utils', () => {
|
||||||
expect(mergeObjects({}, obj1, obj2)).toEqual({ a: ['C'], b: ['D'] });
|
expect(mergeObjects({}, obj1, obj2)).toEqual({ a: ['C'], b: ['D'] });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('titleize', () => {
|
||||||
|
test('should return the string with the first letter capitalized', () => {
|
||||||
|
expect(titleize('my title')).toEqual('My title');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,10 +7,13 @@ import {
|
||||||
isPrimitiveType,
|
isPrimitiveType,
|
||||||
mergeParams,
|
mergeParams,
|
||||||
normalizeServers,
|
normalizeServers,
|
||||||
|
pluralizeType,
|
||||||
|
serializeParameterValue,
|
||||||
} from '../';
|
} from '../';
|
||||||
|
|
||||||
import { OpenAPIParser } from '../../services';
|
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||||
import { OpenAPIParameter } from '../../types';
|
import { OpenAPIParameter, OpenAPIParameterLocation, OpenAPIParameterStyle } from '../../types';
|
||||||
|
import { expandDefaultServerVariables } from '../openapi';
|
||||||
|
|
||||||
describe('Utils', () => {
|
describe('Utils', () => {
|
||||||
describe('openapi getStatusCode', () => {
|
describe('openapi getStatusCode', () => {
|
||||||
|
@ -295,11 +298,8 @@ describe('Utils', () => {
|
||||||
it('should expand variables', () => {
|
it('should expand variables', () => {
|
||||||
const servers = normalizeServers('', [
|
const servers = normalizeServers('', [
|
||||||
{
|
{
|
||||||
url: '{protocol}{host}{basePath}',
|
url: 'http://{host}{basePath}',
|
||||||
variables: {
|
variables: {
|
||||||
protocol: {
|
|
||||||
default: 'http://',
|
|
||||||
},
|
|
||||||
host: {
|
host: {
|
||||||
default: '127.0.0.1',
|
default: '127.0.0.1',
|
||||||
},
|
},
|
||||||
|
@ -317,9 +317,15 @@ describe('Utils', () => {
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(servers[0].url).toEqual('http://127.0.0.1/path/to/endpoint');
|
expect(expandDefaultServerVariables(servers[0].url, servers[0].variables)).toEqual(
|
||||||
expect(servers[1].url).toEqual('http://127.0.0.2:{port}');
|
'http://127.0.0.1/path/to/endpoint',
|
||||||
expect(servers[2].url).toEqual('http://127.0.0.3');
|
);
|
||||||
|
expect(expandDefaultServerVariables(servers[1].url, servers[1].variables)).toEqual(
|
||||||
|
'http://127.0.0.2:{port}',
|
||||||
|
);
|
||||||
|
expect(expandDefaultServerVariables(servers[2].url, servers[2].variables)).toEqual(
|
||||||
|
'http://127.0.0.3',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -353,4 +359,266 @@ describe('Utils', () => {
|
||||||
expect(humanizeConstraints(itemConstraintSchema(1))).toContain('non-empty');
|
expect(humanizeConstraints(itemConstraintSchema(1))).toContain('non-empty');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('OpenAPI pluralizeType', () => {
|
||||||
|
it('should pluralize all simple types', () => {
|
||||||
|
expect(pluralizeType('string')).toEqual('strings');
|
||||||
|
expect(pluralizeType('number')).toEqual('numbers');
|
||||||
|
expect(pluralizeType('object')).toEqual('objects');
|
||||||
|
expect(pluralizeType('integer')).toEqual('integers');
|
||||||
|
expect(pluralizeType('boolean')).toEqual('booleans');
|
||||||
|
expect(pluralizeType('array')).toEqual('arrays');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pluralize complex dislay types', () => {
|
||||||
|
expect(pluralizeType('object (Pet)')).toEqual('objects (Pet)');
|
||||||
|
expect(pluralizeType('string <email>')).toEqual('strings <email>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pluralize oneOf-ed dislay types', () => {
|
||||||
|
expect(pluralizeType('object or string')).toEqual('objects or strings');
|
||||||
|
expect(pluralizeType('object (Pet) or number <int64>')).toEqual(
|
||||||
|
'objects (Pet) or numbers <int64>',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not pluralize display types that are already pluralized', () => {
|
||||||
|
expect(pluralizeType('strings')).toEqual('strings');
|
||||||
|
expect(pluralizeType('objects (Pet)')).toEqual('objects (Pet)');
|
||||||
|
expect(pluralizeType('strings <email>')).toEqual('strings <email>');
|
||||||
|
expect(pluralizeType('objects or strings')).toEqual('objects or strings');
|
||||||
|
expect(pluralizeType('objects (Pet) or numbers <int64>')).toEqual('objects (Pet) or numbers <int64>');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('openapi serializeParameter', () => {
|
||||||
|
interface TestCase {
|
||||||
|
style: OpenAPIParameterStyle;
|
||||||
|
explode: boolean;
|
||||||
|
expected: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestValueTypeGroup {
|
||||||
|
value: any;
|
||||||
|
description: string;
|
||||||
|
cases: TestCase[];
|
||||||
|
}
|
||||||
|
interface TestLocationGroup {
|
||||||
|
location: OpenAPIParameterLocation;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
cases: TestValueTypeGroup[];
|
||||||
|
}
|
||||||
|
const testCases: TestLocationGroup[] = [
|
||||||
|
{
|
||||||
|
location: 'path',
|
||||||
|
name: 'id',
|
||||||
|
description: 'path parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '5' },
|
||||||
|
{ style: 'label', explode: false, expected: '.5' },
|
||||||
|
{ style: 'label', explode: true, expected: '.5' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=5' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '3,4,5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '3,4,5' },
|
||||||
|
{ style: 'label', explode: false, expected: '.3,4,5' },
|
||||||
|
{ style: 'label', explode: true, expected: '.3.4.5' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=3,4,5' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';id=3;id=4;id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: 'role,admin,firstName,Alex' },
|
||||||
|
{ style: 'simple', explode: true, expected: 'role=admin,firstName=Alex' },
|
||||||
|
{ style: 'label', explode: false, expected: '.role,admin,firstName,Alex' },
|
||||||
|
{ style: 'label', explode: true, expected: '.role=admin.firstName=Alex' },
|
||||||
|
{ style: 'matrix', explode: false, expected: ';id=role,admin,firstName,Alex' },
|
||||||
|
{ style: 'matrix', explode: true, expected: ';role=admin;firstName=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'query',
|
||||||
|
name: 'id',
|
||||||
|
description: 'query parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=3,4,5' },
|
||||||
|
{ style: 'spaceDelimited', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'spaceDelimited', explode: false, expected: 'id=3%204%205' },
|
||||||
|
{ style: 'pipeDelimited', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'pipeDelimited', explode: false, expected: 'id=3|4|5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'role=admin&firstName=Alex' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=role,admin,firstName,Alex' },
|
||||||
|
{ style: 'deepObject', explode: true, expected: 'id[role]=admin&id[firstName]=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'cookie',
|
||||||
|
name: 'id',
|
||||||
|
description: 'cookie parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'id=3&id=4&id=5' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=3,4,5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'form', explode: true, expected: 'role=admin&firstName=Alex' },
|
||||||
|
{ style: 'form', explode: false, expected: 'id=role,admin,firstName,Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
location: 'header',
|
||||||
|
name: 'x-id',
|
||||||
|
description: 'header parameters',
|
||||||
|
cases: [
|
||||||
|
{
|
||||||
|
value: 5,
|
||||||
|
description: 'primitive values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: [3, 4, 5],
|
||||||
|
description: 'array values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: '3,4,5' },
|
||||||
|
{ style: 'simple', explode: true, expected: '3,4,5' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: { role: 'admin', firstName: 'Alex' },
|
||||||
|
description: 'object values',
|
||||||
|
cases: [
|
||||||
|
{ style: 'simple', explode: false, expected: 'role,admin,firstName,Alex' },
|
||||||
|
{ style: 'simple', explode: true, expected: 'role=admin,firstName=Alex' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
testCases.forEach(locationTestGroup => {
|
||||||
|
describe(locationTestGroup.description, () => {
|
||||||
|
locationTestGroup.cases.forEach(valueTypeTestGroup => {
|
||||||
|
describe(valueTypeTestGroup.description, () => {
|
||||||
|
valueTypeTestGroup.cases.forEach(testCase => {
|
||||||
|
it(`should serialize correctly when style is ${testCase.style} and explode is ${testCase.explode}`, () => {
|
||||||
|
const parameter: OpenAPIParameter = {
|
||||||
|
name: locationTestGroup.name,
|
||||||
|
in: locationTestGroup.location,
|
||||||
|
style: testCase.style,
|
||||||
|
explode: testCase.explode,
|
||||||
|
};
|
||||||
|
const serialized = serializeParameterValue(parameter, valueTypeTestGroup.value);
|
||||||
|
|
||||||
|
expect(serialized).toEqual(testCase.expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('advanced serialization', () => {
|
||||||
|
it('should serialize correctly query parameter with content with application/json', () => {
|
||||||
|
const parameter: OpenAPIParameter = {
|
||||||
|
name: 'id',
|
||||||
|
in: 'query',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const parser = new OpenAPIParser({ openapi: '3.0' } as any);
|
||||||
|
const opts = new RedocNormalizedOptions({});
|
||||||
|
|
||||||
|
const field = new FieldModel(parser, parameter, '', opts);
|
||||||
|
expect(serializeParameterValue(field, { name: 'test', age: 23 })).toEqual(
|
||||||
|
'id={"name":"test","age":23}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should serialize correctly header parameter with content with application/json', () => {
|
||||||
|
const parameter: OpenAPIParameter = {
|
||||||
|
name: 'x-header',
|
||||||
|
in: 'header',
|
||||||
|
content: {
|
||||||
|
'application/json': {
|
||||||
|
schema: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const parser = new OpenAPIParser({ openapi: '3.0' } as any);
|
||||||
|
const opts = new RedocNormalizedOptions({});
|
||||||
|
|
||||||
|
const field = new FieldModel(parser, parameter, '', opts);
|
||||||
|
expect(serializeParameterValue(field, { name: 'test', age: 23 })).toEqual(
|
||||||
|
'{"name":"test","age":23}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -83,7 +83,7 @@ export function appendToMdHeading(md: string, heading: string, content: string)
|
||||||
}
|
}
|
||||||
|
|
||||||
// credits https://stackoverflow.com/a/46973278/1749888
|
// credits https://stackoverflow.com/a/46973278/1749888
|
||||||
export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T => {
|
export const mergeObjects = (target: any, ...sources: any[]): any => {
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
@ -147,7 +147,7 @@ export function resolveUrl(url: string, to: string) {
|
||||||
let res;
|
let res;
|
||||||
if (to.startsWith('//')) {
|
if (to.startsWith('//')) {
|
||||||
const { protocol: specProtocol } = parse(url);
|
const { protocol: specProtocol } = parse(url);
|
||||||
res = `${specProtocol}${to}`;
|
res = `${specProtocol || 'https:'}${to}`;
|
||||||
} else if (isAbsoluteUrl(to)) {
|
} else if (isAbsoluteUrl(to)) {
|
||||||
res = to;
|
res = to;
|
||||||
} else if (!to.startsWith('/')) {
|
} else if (!to.startsWith('/')) {
|
||||||
|
@ -163,5 +163,38 @@ export function resolveUrl(url: string, to: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBasePath(serverUrl: string): string {
|
export function getBasePath(serverUrl: string): string {
|
||||||
return new URL(serverUrl).pathname;
|
try {
|
||||||
|
return parseURL(serverUrl).pathname;
|
||||||
|
} catch (e) {
|
||||||
|
// when using with redoc-cli serverUrl can be empty resulting in crash
|
||||||
|
return serverUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function titleize(text: string) {
|
||||||
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeQueryString(serverUrl: string): string {
|
||||||
|
try {
|
||||||
|
const url = parseURL(serverUrl);
|
||||||
|
url.search = '';
|
||||||
|
return url.toString();
|
||||||
|
} catch (e) {
|
||||||
|
// when using with redoc-cli serverUrl can be empty resulting in crash
|
||||||
|
return serverUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseURL(url: string) {
|
||||||
|
if (typeof URL === 'undefined') {
|
||||||
|
// node
|
||||||
|
return new (require('url')).URL(url);
|
||||||
|
} else {
|
||||||
|
return new URL(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unescapeHTMLChars(str: string): string {
|
||||||
|
return str.replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(parseInt(code, 10)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
let level = 1;
|
let level = 1;
|
||||||
const COLLAPSE_LEVEL = 2;
|
|
||||||
|
|
||||||
export function jsonToHTML(json) {
|
export function jsonToHTML(json, maxExpandLevel) {
|
||||||
level = 1;
|
level = 1;
|
||||||
let output = '';
|
let output = '';
|
||||||
output += '<div class="redoc-json">';
|
output += '<div class="redoc-json">';
|
||||||
output += valueToHTML(json);
|
output += '<code>';
|
||||||
|
output += valueToHTML(json, maxExpandLevel);
|
||||||
|
output += '</code>';
|
||||||
output += '</div>';
|
output += '</div>';
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
@ -21,8 +22,8 @@ function htmlEncode(t) {
|
||||||
: '';
|
: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeForStringLiteral(str: string) {
|
function stringifyStringLiteral(str: string) {
|
||||||
return str.replace(/([\\"])/g, '\\$1');
|
return JSON.stringify(str).slice(1, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function decorateWithSpan(value, className) {
|
function decorateWithSpan(value, className) {
|
||||||
|
@ -33,20 +34,20 @@ function punctuation(val) {
|
||||||
return '<span class="token punctuation">' + val + '</span>';
|
return '<span class="token punctuation">' + val + '</span>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function valueToHTML(value) {
|
function valueToHTML(value, maxExpandLevel: number) {
|
||||||
const valueType = typeof value;
|
const valueType = typeof value;
|
||||||
let output = '';
|
let output = '';
|
||||||
if (value === undefined || value === null) {
|
if (value === undefined || value === null) {
|
||||||
output += decorateWithSpan('null', 'token keyword');
|
output += decorateWithSpan('null', 'token keyword');
|
||||||
} else if (value && value.constructor === Array) {
|
} else if (value && value.constructor === Array) {
|
||||||
level++;
|
level++;
|
||||||
output += arrayToHTML(value);
|
output += arrayToHTML(value, maxExpandLevel);
|
||||||
level--;
|
level--;
|
||||||
} else if (value && value.constructor === Date) {
|
} else if (value && value.constructor === Date) {
|
||||||
output += decorateWithSpan('"' + value.toISOString() + '"', 'token string');
|
output += decorateWithSpan('"' + value.toISOString() + '"', 'token string');
|
||||||
} else if (valueType === 'object') {
|
} else if (valueType === 'object') {
|
||||||
level++;
|
level++;
|
||||||
output += objectToHTML(value);
|
output += objectToHTML(value, maxExpandLevel);
|
||||||
level--;
|
level--;
|
||||||
} else if (valueType === 'number') {
|
} else if (valueType === 'number') {
|
||||||
output += decorateWithSpan(value, 'token number');
|
output += decorateWithSpan(value, 'token number');
|
||||||
|
@ -57,11 +58,11 @@ function valueToHTML(value) {
|
||||||
'<a href="' +
|
'<a href="' +
|
||||||
value +
|
value +
|
||||||
'">' +
|
'">' +
|
||||||
htmlEncode(escapeForStringLiteral(value)) +
|
htmlEncode(stringifyStringLiteral(value)) +
|
||||||
'</a>' +
|
'</a>' +
|
||||||
decorateWithSpan('"', 'token string');
|
decorateWithSpan('"', 'token string');
|
||||||
} else {
|
} else {
|
||||||
output += decorateWithSpan('"' + escapeForStringLiteral(value) + '"', 'token string');
|
output += decorateWithSpan('"' + stringifyStringLiteral(value) + '"', 'token string');
|
||||||
}
|
}
|
||||||
} else if (valueType === 'boolean') {
|
} else if (valueType === 'boolean') {
|
||||||
output += decorateWithSpan(value, 'token boolean');
|
output += decorateWithSpan(value, 'token boolean');
|
||||||
|
@ -70,8 +71,8 @@ function valueToHTML(value) {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
function arrayToHTML(json) {
|
function arrayToHTML(json, maxExpandLevel: number) {
|
||||||
const collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : '';
|
const collapsed = level > maxExpandLevel ? 'collapsed' : '';
|
||||||
let output = `<div class="collapser"></div>${punctuation(
|
let output = `<div class="collapser"></div>${punctuation(
|
||||||
'[',
|
'[',
|
||||||
)}<span class="ellipsis"></span><ul class="array collapsible">`;
|
)}<span class="ellipsis"></span><ul class="array collapsible">`;
|
||||||
|
@ -80,7 +81,7 @@ function arrayToHTML(json) {
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
hasContents = true;
|
hasContents = true;
|
||||||
output += '<li><div class="hoverable ' + collapsed + '">';
|
output += '<li><div class="hoverable ' + collapsed + '">';
|
||||||
output += valueToHTML(json[i]);
|
output += valueToHTML(json[i], maxExpandLevel);
|
||||||
if (i < length - 1) {
|
if (i < length - 1) {
|
||||||
output += ',';
|
output += ',';
|
||||||
}
|
}
|
||||||
|
@ -93,8 +94,8 @@ function arrayToHTML(json) {
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
function objectToHTML(json) {
|
function objectToHTML(json, maxExpandLevel: number) {
|
||||||
const collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : '';
|
const collapsed = level > maxExpandLevel ? 'collapsed' : '';
|
||||||
const keys = Object.keys(json);
|
const keys = Object.keys(json);
|
||||||
const length = keys.length;
|
const length = keys.length;
|
||||||
let output = `<div class="collapser"></div>${punctuation(
|
let output = `<div class="collapser"></div>${punctuation(
|
||||||
|
@ -106,7 +107,7 @@ function objectToHTML(json) {
|
||||||
hasContents = true;
|
hasContents = true;
|
||||||
output += '<li><div class="hoverable ' + collapsed + '">';
|
output += '<li><div class="hoverable ' + collapsed + '">';
|
||||||
output += '<span class="property token string">"' + htmlEncode(key) + '"</span>: ';
|
output += '<span class="property token string">"' + htmlEncode(key) + '"</span>: ';
|
||||||
output += valueToHTML(json[key]);
|
output += valueToHTML(json[key], maxExpandLevel);
|
||||||
if (i < length - 1) {
|
if (i < length - 1) {
|
||||||
output += punctuation(',');
|
output += punctuation(',');
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,9 @@ import { OpenAPISpec } from '../types';
|
||||||
|
|
||||||
export async function loadAndBundleSpec(specUrlOrObject: object | string): Promise<OpenAPISpec> {
|
export async function loadAndBundleSpec(specUrlOrObject: object | string): Promise<OpenAPISpec> {
|
||||||
const parser = new JsonSchemaRefParser();
|
const parser = new JsonSchemaRefParser();
|
||||||
const spec = await parser.bundle(specUrlOrObject, {
|
const spec = (await parser.bundle(specUrlOrObject, {
|
||||||
resolve: { http: { withCredentials: false } },
|
resolve: { http: { withCredentials: false } },
|
||||||
} as object);
|
} as object)) as any;
|
||||||
|
|
||||||
if (spec.swagger !== undefined) {
|
if (spec.swagger !== undefined) {
|
||||||
return convertSwagger2OpenAPI(spec);
|
return convertSwagger2OpenAPI(spec);
|
||||||
|
@ -19,7 +19,7 @@ export async function loadAndBundleSpec(specUrlOrObject: object | string): Promi
|
||||||
export function convertSwagger2OpenAPI(spec: any): Promise<OpenAPISpec> {
|
export function convertSwagger2OpenAPI(spec: any): Promise<OpenAPISpec> {
|
||||||
console.warn('[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0');
|
console.warn('[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0');
|
||||||
return new Promise<OpenAPISpec>((resolve, reject) =>
|
return new Promise<OpenAPISpec>((resolve, reject) =>
|
||||||
convertObj(spec, { patch: true, warnOnly: true }, (err, res) => {
|
convertObj(spec, { patch: true, warnOnly: true, text: '{}' }, (err, res) => {
|
||||||
// TODO: log any warnings
|
// TODO: log any warnings
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
|
const URLtemplate = require('url-template');
|
||||||
|
|
||||||
import { OpenAPIParser } from '../services/OpenAPIParser';
|
import { OpenAPIParser } from '../services/OpenAPIParser';
|
||||||
import {
|
import {
|
||||||
|
@ -6,12 +7,13 @@ import {
|
||||||
OpenAPIMediaType,
|
OpenAPIMediaType,
|
||||||
OpenAPIOperation,
|
OpenAPIOperation,
|
||||||
OpenAPIParameter,
|
OpenAPIParameter,
|
||||||
|
OpenAPIParameterStyle,
|
||||||
OpenAPISchema,
|
OpenAPISchema,
|
||||||
OpenAPIServer,
|
OpenAPIServer,
|
||||||
Referenced,
|
Referenced,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { IS_BROWSER } from './dom';
|
import { IS_BROWSER } from './dom';
|
||||||
import { isNumeric, resolveUrl } from './helpers';
|
import { isNumeric, removeQueryString, resolveUrl, stripTrailingSlash } from './helpers';
|
||||||
|
|
||||||
function isWildcardStatusCode(statusCode: string | number): statusCode is string {
|
function isWildcardStatusCode(statusCode: string | number): statusCode is string {
|
||||||
return typeof statusCode === 'string' && /\dxx/i.test(statusCode);
|
return typeof statusCode === 'string' && /\dxx/i.test(statusCode);
|
||||||
|
@ -135,36 +137,6 @@ export function isFormUrlEncoded(contentType: string): boolean {
|
||||||
return contentType === 'application/x-www-form-urlencoded';
|
return contentType === 'application/x-www-form-urlencoded';
|
||||||
}
|
}
|
||||||
|
|
||||||
function formEncodeField(fieldVal: any, fieldName: string, explode: boolean): string {
|
|
||||||
if (!fieldVal || !fieldVal.length) {
|
|
||||||
return fieldName + '=';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(fieldVal)) {
|
|
||||||
if (explode) {
|
|
||||||
return fieldVal.map(val => `${fieldName}=${val}`).join('&');
|
|
||||||
} else {
|
|
||||||
return fieldName + '=' + fieldVal.map(val => val.toString()).join(',');
|
|
||||||
}
|
|
||||||
} else if (typeof fieldVal === 'object') {
|
|
||||||
if (explode) {
|
|
||||||
return Object.keys(fieldVal)
|
|
||||||
.map(k => `${k}=${fieldVal[k]}`)
|
|
||||||
.join('&');
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
fieldName +
|
|
||||||
'=' +
|
|
||||||
Object.keys(fieldVal)
|
|
||||||
.map(k => `${k},${fieldVal[k]}`)
|
|
||||||
.join(',')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fieldName + '=' + fieldVal.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function delimitedEncodeField(fieldVal: any, fieldName: string, delimeter: string): string {
|
function delimitedEncodeField(fieldVal: any, fieldName: string, delimeter: string): string {
|
||||||
if (Array.isArray(fieldVal)) {
|
if (Array.isArray(fieldVal)) {
|
||||||
return fieldVal.map(v => v.toString()).join(delimeter);
|
return fieldVal.map(v => v.toString()).join(delimeter);
|
||||||
|
@ -191,6 +163,18 @@ function deepObjectEncodeField(fieldVal: any, fieldName: string): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serializeFormValue(name: string, explode: boolean, value: any) {
|
||||||
|
// Use RFC6570 safe name ([a-zA-Z0-9_]) and replace with our name later
|
||||||
|
// e.g. URI.template doesn't parse names with hypen (-) which are valid query param names
|
||||||
|
const safeName = '__redoc_param_name__';
|
||||||
|
const suffix = explode ? '*' : '';
|
||||||
|
const template = URLtemplate.parse(`{?${safeName}${suffix}}`);
|
||||||
|
return template
|
||||||
|
.expand({ [safeName]: value })
|
||||||
|
.substring(1)
|
||||||
|
.replace(/__redoc_param_name__/g, name);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Should be used only for url-form-encoded body payloads
|
* Should be used only for url-form-encoded body payloads
|
||||||
* To be used for parmaters should be extended with other style values
|
* To be used for parmaters should be extended with other style values
|
||||||
|
@ -208,8 +192,7 @@ export function urlFormEncodePayload(
|
||||||
const { style = 'form', explode = true } = encoding[fieldName] || {};
|
const { style = 'form', explode = true } = encoding[fieldName] || {};
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case 'form':
|
case 'form':
|
||||||
return formEncodeField(fieldVal, fieldName, explode);
|
return serializeFormValue(fieldName, explode, fieldVal);
|
||||||
break;
|
|
||||||
case 'spaceDelimited':
|
case 'spaceDelimited':
|
||||||
return delimitedEncodeField(fieldVal, fieldName, '%20');
|
return delimitedEncodeField(fieldVal, fieldName, '%20');
|
||||||
case 'pipeDelimited':
|
case 'pipeDelimited':
|
||||||
|
@ -226,6 +209,154 @@ export function urlFormEncodePayload(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function serializePathParameter(
|
||||||
|
name: string,
|
||||||
|
style: OpenAPIParameterStyle,
|
||||||
|
explode: boolean,
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
const suffix = explode ? '*' : '';
|
||||||
|
let prefix = '';
|
||||||
|
|
||||||
|
if (style === 'label') {
|
||||||
|
prefix = '.';
|
||||||
|
} else if (style === 'matrix') {
|
||||||
|
prefix = ';';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use RFC6570 safe name ([a-zA-Z0-9_]) and replace with our name later
|
||||||
|
// e.g. URI.template doesn't parse names with hypen (-) which are valid query param names
|
||||||
|
const safeName = '__redoc_param_name__';
|
||||||
|
const template = URLtemplate.parse(`{${prefix}${safeName}${suffix}}`);
|
||||||
|
|
||||||
|
return template.expand({ [safeName]: value }).replace(/__redoc_param_name__/g, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeQueryParameter(
|
||||||
|
name: string,
|
||||||
|
style: OpenAPIParameterStyle,
|
||||||
|
explode: boolean,
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
switch (style) {
|
||||||
|
case 'form':
|
||||||
|
return serializeFormValue(name, explode, value);
|
||||||
|
case 'spaceDelimited':
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
console.warn('The style spaceDelimited is only applicable to arrays');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (explode) {
|
||||||
|
return serializeFormValue(name, explode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${name}=${value.join('%20')}`;
|
||||||
|
case 'pipeDelimited':
|
||||||
|
if (!Array.isArray(value)) {
|
||||||
|
console.warn('The style pipeDelimited is only applicable to arrays');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (explode) {
|
||||||
|
return serializeFormValue(name, explode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${name}=${value.join('|')}`;
|
||||||
|
case 'deepObject':
|
||||||
|
if (!explode || Array.isArray(value) || typeof value !== 'object') {
|
||||||
|
console.warn('The style deepObject is only applicable for objects with explode=true');
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return deepObjectEncodeField(value, name);
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected style for query: ' + style);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeHeaderParameter(
|
||||||
|
style: OpenAPIParameterStyle,
|
||||||
|
explode: boolean,
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
switch (style) {
|
||||||
|
case 'simple':
|
||||||
|
const suffix = explode ? '*' : '';
|
||||||
|
|
||||||
|
// name is not important here, so use RFC6570 safe name ([a-zA-Z0-9_])
|
||||||
|
const name = '__redoc_param_name__';
|
||||||
|
const template = URLtemplate.parse(`{${name}${suffix}}`);
|
||||||
|
return decodeURIComponent(template.expand({ [name]: value }));
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected style for header: ' + style);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serializeCookieParameter(
|
||||||
|
name: string,
|
||||||
|
style: OpenAPIParameterStyle,
|
||||||
|
explode: boolean,
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
switch (style) {
|
||||||
|
case 'form':
|
||||||
|
return serializeFormValue(name, explode, value);
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected style for cookie: ' + style);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeParameterValueWithMime(value: any, mime: string): string {
|
||||||
|
if (isJsonLike(mime)) {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
} else {
|
||||||
|
console.warn(`Parameter serialization as ${mime} is not supported`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeParameterValue(
|
||||||
|
parameter: OpenAPIParameter & { serializationMime?: string },
|
||||||
|
value: any,
|
||||||
|
): string {
|
||||||
|
const { name, style, explode = false, serializationMime } = parameter;
|
||||||
|
|
||||||
|
if (serializationMime) {
|
||||||
|
switch (parameter.in) {
|
||||||
|
case 'path':
|
||||||
|
case 'header':
|
||||||
|
return serializeParameterValueWithMime(value, serializationMime);
|
||||||
|
case 'cookie':
|
||||||
|
case 'query':
|
||||||
|
return `${name}=${serializeParameterValueWithMime(value, serializationMime)}`;
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected parameter location: ' + parameter.in);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!style) {
|
||||||
|
console.warn(`Missing style attribute or content for parameter ${name}`);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (parameter.in) {
|
||||||
|
case 'path':
|
||||||
|
return serializePathParameter(name, style, explode, value);
|
||||||
|
case 'query':
|
||||||
|
return serializeQueryParameter(name, style, explode, value);
|
||||||
|
case 'header':
|
||||||
|
return serializeHeaderParameter(style, explode, value);
|
||||||
|
case 'cookie':
|
||||||
|
return serializeCookieParameter(name, style, explode, value);
|
||||||
|
default:
|
||||||
|
console.warn('Unexpected parameter location: ' + parameter.in);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function langFromMime(contentType: string): string {
|
export function langFromMime(contentType: string): string {
|
||||||
if (contentType.search(/xml/i) !== -1) {
|
if (contentType.search(/xml/i) !== -1) {
|
||||||
return 'xml';
|
return 'xml';
|
||||||
|
@ -356,7 +487,7 @@ export function mergeSimilarMediaTypes(types: Dict<OpenAPIMediaType>): Dict<Open
|
||||||
return mergedTypes;
|
return mergedTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandVariables(url: string, variables: object = {}) {
|
export function expandDefaultServerVariables(url: string, variables: object = {}) {
|
||||||
return url.replace(
|
return url.replace(
|
||||||
/(?:{)(\w+)(?:})/g,
|
/(?:{)(\w+)(?:})/g,
|
||||||
(match, name) => (variables[name] && variables[name].default) || match,
|
(match, name) => (variables[name] && variables[name].default) || match,
|
||||||
|
@ -367,32 +498,41 @@ export function normalizeServers(
|
||||||
specUrl: string | undefined,
|
specUrl: string | undefined,
|
||||||
servers: OpenAPIServer[],
|
servers: OpenAPIServer[],
|
||||||
): OpenAPIServer[] {
|
): OpenAPIServer[] {
|
||||||
const baseUrl =
|
const getHref = () => {
|
||||||
specUrl === undefined ? (IS_BROWSER ? window.location.href : '') : dirname(specUrl);
|
if (!IS_BROWSER) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const href = window.location.href;
|
||||||
|
return href.endsWith('.html') ? dirname(href) : href;
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseUrl = specUrl === undefined ? removeQueryString(getHref()) : dirname(specUrl);
|
||||||
|
|
||||||
if (servers.length === 0) {
|
if (servers.length === 0) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
url: baseUrl,
|
url: stripTrailingSlash(baseUrl),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeUrl(url: string, variables: object | undefined): string {
|
function normalizeUrl(url: string): string {
|
||||||
url = expandVariables(url, variables);
|
|
||||||
return resolveUrl(baseUrl, url);
|
return resolveUrl(baseUrl, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers.map(server => {
|
return servers.map(server => {
|
||||||
return {
|
return {
|
||||||
...server,
|
...server,
|
||||||
url: normalizeUrl(server.url, server.variables),
|
url: normalizeUrl(server.url),
|
||||||
description: server.description || '',
|
description: server.description || '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SECURITY_DEFINITIONS_COMPONENT_NAME = 'security-definitions';
|
export const SECURITY_DEFINITIONS_COMPONENT_NAME = 'security-definitions';
|
||||||
|
export const SECURITY_DEFINITIONS_JSX_NAME = 'SecurityDefinitions';
|
||||||
|
export const SCHEMA_DEFINITION_JSX_NAME = 'SchemaDefinition';
|
||||||
|
|
||||||
export let SECURITY_SCHEMES_SECTION_PREFIX = 'section/Authentication/';
|
export let SECURITY_SCHEMES_SECTION_PREFIX = 'section/Authentication/';
|
||||||
export function setSecuritySchemePrefix(prefix: string) {
|
export function setSecuritySchemePrefix(prefix: string) {
|
||||||
SECURITY_SCHEMES_SECTION_PREFIX = prefix;
|
SECURITY_SCHEMES_SECTION_PREFIX = prefix;
|
||||||
|
@ -416,6 +556,7 @@ export function isRedocExtension(key: string): boolean {
|
||||||
'x-servers': true,
|
'x-servers': true,
|
||||||
'x-tagGroups': true,
|
'x-tagGroups': true,
|
||||||
'x-traitTag': true,
|
'x-traitTag': true,
|
||||||
|
'x-additionalPropertiesName': true,
|
||||||
};
|
};
|
||||||
|
|
||||||
return key in redocExtensions;
|
return key in redocExtensions;
|
||||||
|
@ -434,3 +575,10 @@ export function extractExtensions(obj: object, showExtensions: string[] | true):
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function pluralizeType(displayType: string): string {
|
||||||
|
return displayType
|
||||||
|
.split(' or ')
|
||||||
|
.map(type => type.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/, '$1s$2'))
|
||||||
|
.join(' or ');
|
||||||
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@ if (process.env.JOB === 'e2e-guru') {
|
||||||
delete apisGuruList['googleapis.com:mirror']; // bad urls in images
|
delete apisGuruList['googleapis.com:mirror']; // bad urls in images
|
||||||
delete apisGuruList['googleapis.com:discovery']; // non-string references
|
delete apisGuruList['googleapis.com:discovery']; // non-string references
|
||||||
delete apisGuruList['clarify.io']; // non-string references
|
delete apisGuruList['clarify.io']; // non-string references
|
||||||
//delete apisGuruList['pushpay.com']; // https://github.com/Rebilly/ReDoc/issues/30
|
//delete apisGuruList['pushpay.com']; // https://github.com/Redocly/redoc/issues/30
|
||||||
delete apisGuruList['bbci.co.uk']; // too big
|
delete apisGuruList['bbci.co.uk']; // too big
|
||||||
delete apisGuruList['bbc.com']; // too big
|
delete apisGuruList['bbc.com']; // too big
|
||||||
delete apisGuruList['osisoft.com']; // too big
|
delete apisGuruList['osisoft.com']; // too big
|
||||||
|
|
|
@ -33,7 +33,7 @@ try {
|
||||||
const BANNER = `ReDoc - OpenAPI/Swagger-generated API Reference Documentation
|
const BANNER = `ReDoc - OpenAPI/Swagger-generated API Reference Documentation
|
||||||
-------------------------------------------------------------
|
-------------------------------------------------------------
|
||||||
Version: ${VERSION}
|
Version: ${VERSION}
|
||||||
Repo: https://github.com/Rebilly/ReDoc`;
|
Repo: https://github.com/Redocly/redoc`;
|
||||||
|
|
||||||
export default (env: { standalone?: boolean } = {}, { mode }) => ({
|
export default (env: { standalone?: boolean } = {}, { mode }) => ({
|
||||||
entry: env.standalone ? ['./src/polyfills.ts', './src/standalone.tsx'] : './src/index.ts',
|
entry: env.standalone ? ['./src/polyfills.ts', './src/standalone.tsx'] : './src/index.ts',
|
||||||
|
@ -65,13 +65,13 @@ export default (env: { standalone?: boolean } = {}, { mode }) => ({
|
||||||
? {
|
? {
|
||||||
esprima: 'esprima',
|
esprima: 'esprima',
|
||||||
'node-fetch': 'null',
|
'node-fetch': 'null',
|
||||||
|
'node-fetch-h2': 'null',
|
||||||
|
yaml: 'null',
|
||||||
|
'safe-json-stringify': 'null',
|
||||||
}
|
}
|
||||||
: (context, request, callback) => {
|
: (context, request, callback) => {
|
||||||
// ignore node-fetch dep of swagger2openapi as it is not used
|
// ignore node-fetch dep of swagger2openapi as it is not used
|
||||||
if (/node-fetch$/i.test(request)) {
|
if (/esprima|node-fetch|node-fetch-h2|yaml|safe-json-stringify$/i.test(request)) {
|
||||||
return callback(null, 'var undefined');
|
|
||||||
}
|
|
||||||
if (/esprima$/i.test(request)) {
|
|
||||||
return callback(null, 'var undefined');
|
return callback(null, 'var undefined');
|
||||||
}
|
}
|
||||||
return nodeExternals(context, request, callback);
|
return nodeExternals(context, request, callback);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user