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/types`**: contains extra typescript typings including OpenAPI doc typings
|
||||
- **`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
|
||||
|
|
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
|
||||
node_js:
|
||||
- '8'
|
||||
cache: yarn
|
||||
- '10'
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
# we also need to cache folder with Cypress binary
|
||||
- ~/.cache
|
||||
env:
|
||||
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_NAME: RedocBot
|
||||
- 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=
|
||||
addons:
|
||||
chrome: stable
|
||||
apt:
|
||||
packages:
|
||||
- libgconf-2-4 # for cypress
|
||||
before_script: npm run bundle
|
||||
script: npm test && ([ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run e2e-ci || npm
|
||||
run e2e)
|
||||
|
@ -27,9 +34,6 @@ deploy:
|
|||
api_key: "$NPM_TOKEN"
|
||||
on:
|
||||
tags: true
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
script: cd cli && yarn install && yarn ci-publish || true
|
||||
- provider: script
|
||||
skip_cleanup: true
|
||||
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)
|
||||
|
||||
|
||||
|
|
65
README.md
65
README.md
|
@ -1,27 +1,27 @@
|
|||
<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**
|
||||
|
||||
[](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/)
|
||||
|
||||
|
||||
</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)
|
||||
|
||||
## Features
|
||||
- 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
|
||||
- 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)
|
||||
|
||||
## 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] ~~better navigation (menu improvements + search)~~
|
||||
- [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
|
||||
- `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
|
||||
- `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.
|
||||
|
@ -138,7 +138,7 @@ For npm:
|
|||
|
||||
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:
|
||||
|
||||
|
@ -165,7 +165,7 @@ Also you can pass options:
|
|||
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
|
||||
options={{
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
[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
|
||||
|
||||
|
@ -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-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-additionalPropertiesName`](docs/redoc-vendor-extensions.md#x-additionalPropertiesName) - ability to supply a descriptive name for the additional property keys
|
||||
|
||||
### `<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`.
|
||||
|
||||
* `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` can be specified in various ways:
|
||||
* **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;
|
||||
* **function**: A getter function. Must return a number representing the offset (in pixels);
|
||||
* **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.
|
||||
* **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).
|
||||
* `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.~~
|
||||
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
|
||||
* `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)
|
||||
* `theme` - ReDoc theme. Not documented yet. For details check source code: [theme.ts](https://github.com/Redocly/redoc/blob/master/src/theme.ts).
|
||||
* `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!**
|
||||
|
||||
## Advanced usage of standalone version
|
||||
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](https://github.com/Rebilly/ReDoc)'s Command Line Interface**
|
||||
**[ReDoc](https://github.com/Redocly/redoc)'s Command Line Interface**
|
||||
|
||||
## 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).
|
||||
|
@ -16,6 +16,7 @@ Some examples:
|
|||
|
||||
- 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`
|
||||
- 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`.
|
||||
|
|
111
cli/index.ts
111
cli/index.ts
|
@ -14,7 +14,7 @@ import * as zlib from 'zlib';
|
|||
import { createStore, loadAndBundleSpec, Redoc } from 'redoc';
|
||||
|
||||
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 YargsParser from 'yargs';
|
||||
|
@ -25,7 +25,10 @@ interface Options {
|
|||
cdn?: boolean;
|
||||
output?: string;
|
||||
title?: string;
|
||||
disableGoogleFont?: boolean;
|
||||
port?: number;
|
||||
templateFileName?: string;
|
||||
templateOptions?: any;
|
||||
redocOptions?: any;
|
||||
}
|
||||
|
||||
|
@ -61,13 +64,16 @@ YargsParser.command(
|
|||
return yargs;
|
||||
},
|
||||
async argv => {
|
||||
const config = {
|
||||
ssr: argv.ssr,
|
||||
watch: argv.watch,
|
||||
templateFileName: argv.template,
|
||||
redocOptions: argv.options || {},
|
||||
const config: Options = {
|
||||
ssr: argv.ssr as boolean,
|
||||
watch: argv.watch as boolean,
|
||||
templateFileName: argv.template as string,
|
||||
templateOptions: argv.templateOptions || {},
|
||||
redocOptions: getObjectOrJSON(argv.options),
|
||||
};
|
||||
|
||||
console.log(config);
|
||||
|
||||
try {
|
||||
await serve(argv.port, argv.spec, config);
|
||||
} catch (e) {
|
||||
|
@ -96,6 +102,12 @@ YargsParser.command(
|
|||
default: 'ReDoc documentation',
|
||||
});
|
||||
|
||||
yargs.options('disableGoogleFont', {
|
||||
describe: 'Disable Google Font',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
});
|
||||
|
||||
yargs.option('cdn', {
|
||||
describe: 'Do not include ReDoc source code into html page, use link to CDN instead',
|
||||
type: 'boolean',
|
||||
|
@ -105,14 +117,16 @@ YargsParser.command(
|
|||
yargs.demandOption('spec');
|
||||
return yargs;
|
||||
},
|
||||
async argv => {
|
||||
async (argv: any) => {
|
||||
const config = {
|
||||
ssr: true,
|
||||
output: argv.o,
|
||||
cdn: argv.cdn,
|
||||
title: argv.title,
|
||||
templateFileName: argv.template,
|
||||
redocOptions: argv.options || {},
|
||||
output: argv.o as string,
|
||||
cdn: argv.cdn as boolean,
|
||||
title: argv.title as string,
|
||||
disableGoogleFont: argv.disableGoogleFont as boolean,
|
||||
templateFileName: argv.template as string,
|
||||
templateOptions: argv.templateOptions || {},
|
||||
redocOptions: getObjectOrJSON(argv.options),
|
||||
};
|
||||
|
||||
try {
|
||||
|
@ -148,7 +162,9 @@ async function serve(port: any, pathToSpec: any, options: any = {}) {
|
|||
},
|
||||
);
|
||||
} else if (request.url === '/') {
|
||||
respondWithGzip(pageHTML, request, response);
|
||||
respondWithGzip(pageHTML, request, response, {
|
||||
'Content-Type': 'text/html',
|
||||
});
|
||||
} else if (request.url === '/spec.json') {
|
||||
const specStr = JSON.stringify(spec, null, 2);
|
||||
respondWithGzip(specStr, request, response, {
|
||||
|
@ -170,21 +186,34 @@ async function serve(port: any, pathToSpec: any, options: any = {}) {
|
|||
if (options.watch && existsSync(pathToSpec)) {
|
||||
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
||||
const watchOptions = {
|
||||
ignored: /(^|[\/\\])\../,
|
||||
ignored: [/(^|[\/\\])\../, /___jb_[a-z]+___$/],
|
||||
ignoreInitial: true,
|
||||
};
|
||||
|
||||
const watcher = watch(pathToSpecDirectory, watchOptions);
|
||||
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
|
||||
.on('change', async path => {
|
||||
log(`${path} changed, updating docs`);
|
||||
try {
|
||||
spec = await loadAndBundleSpec(pathToSpec);
|
||||
pageHTML = await getPageHTML(spec, pathToSpec, options);
|
||||
log('Updated successfully');
|
||||
} catch (e) {
|
||||
console.error('Error while updating: ', e.message);
|
||||
}
|
||||
handlePath(path);
|
||||
})
|
||||
.on('add', async path => {
|
||||
log(`File ${path} added, updating docs`);
|
||||
handlePath(path);
|
||||
})
|
||||
.on('addDir', path => {
|
||||
log(`↗ Directory ${path} added. Files in here will trigger reload.`);
|
||||
})
|
||||
.on('error', error => console.error(`Watcher error: ${error}`))
|
||||
.on('ready', () => log(`👀 Watching ${pathToSpecDirectory} for changes...`));
|
||||
|
@ -208,7 +237,15 @@ async function bundle(pathToSpec, options: any = {}) {
|
|||
async function getPageHTML(
|
||||
spec: any,
|
||||
pathToSpec: string,
|
||||
{ ssr, cdn, title, templateFileName, redocOptions = {} }: Options,
|
||||
{
|
||||
ssr,
|
||||
cdn,
|
||||
title,
|
||||
disableGoogleFont,
|
||||
templateFileName,
|
||||
templateOptions,
|
||||
redocOptions = {},
|
||||
}: Options,
|
||||
) {
|
||||
let html;
|
||||
let css;
|
||||
|
@ -242,15 +279,17 @@ async function getPageHTML(
|
|||
ssr
|
||||
? 'hydrate(__redoc_state, container);'
|
||||
: `init("spec.json", ${JSON.stringify(redocOptions)}, container)`
|
||||
};
|
||||
};
|
||||
|
||||
</script>`,
|
||||
redocHead: ssr
|
||||
? (cdn
|
||||
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
|
||||
: `<script>${redocStandaloneSrc}</script>`) + css
|
||||
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
|
||||
: `<script>${redocStandaloneSrc}</script>`) + css
|
||||
: '<script src="redoc.standalone.js"></script>',
|
||||
title,
|
||||
disableGoogleFont,
|
||||
templateOptions,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -312,3 +351,25 @@ function handleError(error: Error) {
|
|||
console.error(error.stack);
|
||||
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",
|
||||
"version": "0.7.0",
|
||||
"version": "0.9.2",
|
||||
"description": "ReDoc's Command Line Interface",
|
||||
"main": "index.js",
|
||||
"bin": "index.js",
|
||||
"repository": "https://github.com/Rebilly/ReDoc",
|
||||
"repository": "https://github.com/Redocly/redoc",
|
||||
"author": "Roman Hotsiy <gotsijroman@gmail.com>",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
},
|
||||
"dependencies": {
|
||||
"chokidar": "^2.0.4",
|
||||
"handlebars": "^4.0.11",
|
||||
"isarray": "^2.0.4",
|
||||
"chokidar": "^3.0.2",
|
||||
"handlebars": "^4.1.2",
|
||||
"isarray": "^2.0.5",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mobx": "^4.2.0",
|
||||
"react": "^16.6.3",
|
||||
"react-dom": "^16.6.3",
|
||||
"node-libs-browser": "^2.2.1",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"redoc": "github:BusinessDuck/ReDoc#master",
|
||||
"styled-components": "^4.1.1",
|
||||
"tslib": "^1.9.3",
|
||||
"yargs": "^12.0.5",
|
||||
"node-fetch-h2": "^2.3.1-0",
|
||||
"builtin-status-codes": "^3.0.0",
|
||||
"to-arraybuffer": "^1.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"ci-publish": "ci-publish"
|
||||
"styled-components": "^4.3.2",
|
||||
"tslib": "^1.10.0",
|
||||
"yargs": "^13.3.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chokidar": "^1.7.5",
|
||||
"@types/chokidar": "^2.1.3",
|
||||
"@types/handlebars": "^4.0.39",
|
||||
"@types/mkdirp": "^0.5.2",
|
||||
"ci-publish": "^1.3.1"
|
||||
"@types/mkdirp": "^0.5.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
}
|
||||
</style>
|
||||
{{{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>
|
||||
|
||||
<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
|
||||
- `SPEC_URL` (default `"http://petstore.swagger.io/v2/swagger.json"`) - URL to spec
|
||||
- `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
|
||||
|
||||
|
|
|
@ -82,7 +82,7 @@ class DemoApp extends React.Component<
|
|||
<>
|
||||
<Heading>
|
||||
<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>
|
||||
<ControlsContainer>
|
||||
<ComboBox
|
||||
|
@ -97,7 +97,7 @@ class DemoApp extends React.Component<
|
|||
</CorsCheckbox>
|
||||
</ControlsContainer>
|
||||
<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"
|
||||
scrolling="0"
|
||||
width="150px"
|
||||
|
|
|
@ -15,15 +15,15 @@ info:
|
|||
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.
|
||||
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
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
|
||||
# OpenAPI Specification
|
||||
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.
|
||||
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
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
|
||||
# Cross-Origin Resource Sharing
|
||||
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
||||
|
@ -38,7 +38,7 @@ info:
|
|||
OAuth2 - an open protocol to allow secure authorization in a simple
|
||||
and standard method from web, mobile and desktop applications.
|
||||
|
||||
<security-definitions />
|
||||
<SecurityDefinitions />
|
||||
|
||||
version: 1.0.0
|
||||
title: Swagger Petstore
|
||||
|
@ -46,9 +46,9 @@ info:
|
|||
contact:
|
||||
name: API Support
|
||||
email: apiteam@swagger.io
|
||||
url: https://github.com/Rebilly/ReDoc
|
||||
url: https://github.com/Redocly/redoc
|
||||
x-logo:
|
||||
url: 'https://rebilly.github.io/ReDoc/petstore-logo.png'
|
||||
url: 'https://redocly.github.io/redoc/petstore-logo.png'
|
||||
altText: Petstore logo
|
||||
license:
|
||||
name: Apache 2.0
|
||||
|
@ -63,6 +63,14 @@ tags:
|
|||
description: Access to Petstore orders
|
||||
- name: 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:
|
||||
- name: General
|
||||
tags:
|
||||
|
@ -71,9 +79,21 @@ x-tagGroups:
|
|||
- name: User Management
|
||||
tags:
|
||||
- user
|
||||
- name: Models
|
||||
tags:
|
||||
- pet_model
|
||||
- store_model
|
||||
paths:
|
||||
/pet:
|
||||
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
|
||||
in: cookie
|
||||
description: Some cookie
|
||||
|
@ -666,6 +686,7 @@ components:
|
|||
type: string
|
||||
description: The measured skill for hunting
|
||||
default: lazy
|
||||
example: adventurous
|
||||
enum:
|
||||
- clueless
|
||||
- lazy
|
||||
|
@ -754,6 +775,11 @@ components:
|
|||
description: Indicates whenever order was completed or not
|
||||
type: boolean
|
||||
default: false
|
||||
readOnly: true
|
||||
rqeuestId:
|
||||
description: Unique Request Id
|
||||
type: string
|
||||
writeOnly: true
|
||||
xml:
|
||||
name: Order
|
||||
Pet:
|
||||
|
@ -866,14 +892,13 @@ components:
|
|||
as well as digits
|
||||
format: password
|
||||
minLength: 8
|
||||
pattern: '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'
|
||||
pattern: '/(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])/'
|
||||
example: drowssaP123
|
||||
phone:
|
||||
description: User phone number in international format
|
||||
type: string
|
||||
pattern: '^\+(?:[0-9]-?){6,14}[0-9]$'
|
||||
pattern: '/^\+(?:[0-9]-?){6,14}[0-9]$/'
|
||||
example: +1-202-555-0192
|
||||
nullable: true
|
||||
userStatus:
|
||||
description: User status
|
||||
type: integer
|
||||
|
@ -926,3 +951,10 @@ components:
|
|||
type: apiKey
|
||||
name: api_key
|
||||
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
|
||||
[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)
|
||||
tool and [ReDoc](https://github.com/Rebilly/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).
|
||||
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
# OpenAPI Specification
|
||||
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.
|
||||
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
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
# Cross-Origin Resource Sharing
|
||||
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.
|
||||
|
@ -39,9 +39,9 @@ info:
|
|||
termsOfService: 'http://swagger.io/terms/'
|
||||
contact:
|
||||
email: apiteam@swagger.io
|
||||
url: https://github.com/Rebilly/ReDoc
|
||||
url: https://github.com/Redocly/redoc
|
||||
x-logo:
|
||||
url: 'https://rebilly.github.io/ReDoc/petstore-logo.png'
|
||||
url: 'https://redocly.github.io/redoc/petstore-logo.png'
|
||||
altText: Petstore logo
|
||||
license:
|
||||
name: Apache 2.0
|
||||
|
|
|
@ -38,7 +38,6 @@ const babelLoader = mode => ({
|
|||
['@babel/plugin-syntax-decorators', { legacy: true }],
|
||||
'@babel/plugin-syntax-dynamic-import',
|
||||
'@babel/plugin-syntax-jsx',
|
||||
mode !== 'production' ? 'react-hot-loader/babel' : undefined,
|
||||
[
|
||||
'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 }) => ({
|
||||
entry: [
|
||||
root('../src/polyfills.ts'),
|
||||
|
@ -94,6 +100,9 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
externals: {
|
||||
esprima: 'esprima',
|
||||
'node-fetch': 'null',
|
||||
'node-fetch-h2': 'null',
|
||||
yaml: 'null',
|
||||
'safe-json-stringify': 'null',
|
||||
},
|
||||
|
||||
module: {
|
||||
|
@ -102,7 +111,11 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [tsLoader(env), babelLoader(mode)],
|
||||
use: compact([
|
||||
mode !== 'production' ? babelHotLoader : undefined,
|
||||
tsLoader(env),
|
||||
babelLoader(mode),
|
||||
]),
|
||||
exclude: [/node_modules/],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -107,7 +107,7 @@ json
|
|||
"version": "1.0.0",
|
||||
"title": "Swagger Petstore",
|
||||
"x-logo": {
|
||||
"url": "https://rebilly.github.io/ReDoc/petstore-logo.png",
|
||||
"url": "https://redocly.github.io/redoc/petstore-logo.png",
|
||||
"backgroundColor": "#FFFFFF",
|
||||
"altText": "Petstore logo"
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ info:
|
|||
version: "1.0.0"
|
||||
title: "Swagger Petstore"
|
||||
x-logo:
|
||||
url: "https://rebilly.github.io/ReDoc/petstore-logo.png"
|
||||
url: "https://redocly.github.io/redoc/petstore-logo.png"
|
||||
backgroundColor: "#FFFFFF"
|
||||
altText: "Petstore logo"
|
||||
```
|
||||
|
@ -278,3 +278,31 @@ PayPalPayment:
|
|||
|
||||
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`.
|
||||
|
||||
#### 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', () => {
|
||||
cy.get('.menu-content')
|
||||
.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', () => {
|
||||
|
|
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",
|
||||
"version": "2.0.0-rc.4",
|
||||
"version": "2.0.0-rc.18",
|
||||
"description": "ReDoc",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Rebilly/ReDoc"
|
||||
"url": "git://github.com/Redocly/redoc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9",
|
||||
|
@ -28,7 +28,7 @@
|
|||
"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: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",
|
||||
"e2e": "cypress run",
|
||||
"e2e-ci": "cypress run --record",
|
||||
|
@ -36,7 +36,7 @@
|
|||
"cy:open": "cypress open",
|
||||
"bundle:clean": "rimraf bundles",
|
||||
"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",
|
||||
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
|
||||
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json",
|
||||
|
@ -52,79 +52,81 @@
|
|||
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.3.4",
|
||||
"@babel/core": "7.6.2",
|
||||
"@babel/plugin-syntax-decorators": "7.2.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-syntax-jsx": "7.2.0",
|
||||
"@babel/plugin-syntax-typescript": "7.3.3",
|
||||
"@cypress/webpack-preprocessor": "4.0.3",
|
||||
"@hot-loader/react-dom": "^16.8.4",
|
||||
"@types/chai": "4.1.7",
|
||||
"@types/dompurify": "^0.0.32",
|
||||
"@types/enzyme": "^3.9.0",
|
||||
"@cypress/webpack-preprocessor": "4.1.0",
|
||||
"@hot-loader/react-dom": "^16.9.0",
|
||||
"@types/chai": "4.2.3",
|
||||
"@types/dompurify": "^0.0.33",
|
||||
"@types/enzyme": "^3.10.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/lodash": "^4.14.122",
|
||||
"@types/lodash": "^4.14.141",
|
||||
"@types/lunr": "^2.3.2",
|
||||
"@types/mark.js": "^8.11.3",
|
||||
"@types/marked": "^0.6.3",
|
||||
"@types/prismjs": "^1.9.1",
|
||||
"@types/prop-types": "^15.7.0",
|
||||
"@types/react": "^16.8.7",
|
||||
"@types/react-dom": "^16.8.2",
|
||||
"@types/mark.js": "^8.11.4",
|
||||
"@types/marked": "^0.6.5",
|
||||
"@types/prismjs": "^1.16.0",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/react": "^16.9.3",
|
||||
"@types/react-dom": "^16.9.1",
|
||||
"@types/react-hot-loader": "^4.1.0",
|
||||
"@types/react-tabs": "^2.3.1",
|
||||
"@types/styled-components": "^4.1.12",
|
||||
"@types/styled-components": "^4.1.19",
|
||||
"@types/tapable": "1.0.4",
|
||||
"@types/webpack": "^4.4.25",
|
||||
"@types/webpack-env": "^1.13.9",
|
||||
"@types/yargs": "^12.0.9",
|
||||
"babel-loader": "8.0.5",
|
||||
"babel-plugin-styled-components": "^1.10.0",
|
||||
"@types/webpack": "^4.39.2",
|
||||
"@types/webpack-env": "^1.14.0",
|
||||
"@types/yargs": "^13.0.3",
|
||||
"babel-loader": "8.0.6",
|
||||
"babel-plugin-styled-components": "^1.10.6",
|
||||
"beautify-benchmark": "^0.2.4",
|
||||
"bundlesize": "^0.17.1",
|
||||
"conventional-changelog-cli": "^2.0.12",
|
||||
"copy-webpack-plugin": "^5.0.0",
|
||||
"core-js": "^2.6.5",
|
||||
"coveralls": "^3.0.3",
|
||||
"css-loader": "^2.1.1",
|
||||
"cypress": "~3.1.5",
|
||||
"deploy-to-gh-pages": "^1.3.6",
|
||||
"enzyme": "^3.9.0",
|
||||
"enzyme-adapter-react-16": "^1.10.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"fork-ts-checker-webpack-plugin": "1.0.0",
|
||||
"bundlesize": "^0.18.0",
|
||||
"conventional-changelog-cli": "^2.0.23",
|
||||
"copy-webpack-plugin": "^5.0.4",
|
||||
"core-js": "^3.2.1",
|
||||
"coveralls": "^3.0.6",
|
||||
"css-loader": "^3.2.0",
|
||||
"cypress": "~3.4.1",
|
||||
"deploy-to-gh-pages": "^1.3.7",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.14.0",
|
||||
"enzyme-to-json": "^3.4.0",
|
||||
"fork-ts-checker-webpack-plugin": "1.5.0",
|
||||
"html-webpack-plugin": "^3.1.0",
|
||||
"jest": "^24.3.1",
|
||||
"jest": "^24.9.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"lodash": "^4.17.11",
|
||||
"lodash": "^4.17.15",
|
||||
"mobx": "^4.3.1",
|
||||
"prettier": "^1.16.4",
|
||||
"prettier-eslint": "^8.8.2",
|
||||
"puppeteer": "^1.13.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prettier-eslint": "^9.0.0",
|
||||
"raf": "^3.4.1",
|
||||
"react": "^16.8.4",
|
||||
"react-dom": "^16.8.4",
|
||||
"rimraf": "^2.6.3",
|
||||
"react": "^16.10.1",
|
||||
"react-dom": "^16.10.1",
|
||||
"rimraf": "^3.0.0",
|
||||
"shelljs": "^0.8.3",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"style-loader": "^0.23.1",
|
||||
"styled-components": "^4.1.3",
|
||||
"ts-jest": "24.0.0",
|
||||
"ts-loader": "5.3.3",
|
||||
"ts-node": "^8.0.3",
|
||||
"tslint": "^5.13.1",
|
||||
"tslint-react": "^3.4.0",
|
||||
"typescript": "^3.3.3333",
|
||||
"webpack": "^4.29.6",
|
||||
"webpack-cli": "^3.2.3",
|
||||
"webpack-dev-server": "^3.2.1",
|
||||
"style-loader": "^1.0.0",
|
||||
"styled-components": "^4.4.0",
|
||||
"ts-jest": "24.1.0",
|
||||
"ts-loader": "6.2.0",
|
||||
"ts-node": "^8.4.1",
|
||||
"tslint": "^5.20.0",
|
||||
"tslint-react": "^4.1.0",
|
||||
"typescript": "^3.6.3",
|
||||
"unfetch": "^4.1.0",
|
||||
"url-polyfill": "^1.1.7",
|
||||
"webpack": "^4.41.0",
|
||||
"webpack-cli": "^3.3.9",
|
||||
"webpack-dev-server": "^3.8.1",
|
||||
"webpack-node-externals": "^1.6.0",
|
||||
"workerize-loader": "^1.0.4",
|
||||
"workerize-loader": "^1.1.0",
|
||||
"yaml-js": "^0.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"core-js": "^3.1.4",
|
||||
"mobx": "^4.2.0 || ^5.0.0",
|
||||
"react": "^16.8.4",
|
||||
"react-dom": "^16.8.4",
|
||||
|
@ -133,27 +135,28 @@
|
|||
"dependencies": {
|
||||
"classnames": "^2.2.6",
|
||||
"decko": "^1.2.0",
|
||||
"dompurify": "^1.0.10",
|
||||
"eventemitter3": "^3.0.0",
|
||||
"dompurify": "^2.0.3",
|
||||
"eventemitter3": "^4.0.0",
|
||||
"json-pointer": "^0.6.0",
|
||||
"json-schema-ref-parser": "^6.1.0",
|
||||
"lunr": "2.3.6",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "^0.6.1",
|
||||
"memoize-one": "^5.0.0",
|
||||
"mobx-react": "^5.4.3",
|
||||
"openapi-sampler": "1.0.0-beta.14",
|
||||
"marked": "^0.7.0",
|
||||
"memoize-one": "~5.0.5",
|
||||
"mobx-react": "^6.1.3",
|
||||
"openapi-sampler": "1.0.0-beta.15",
|
||||
"perfect-scrollbar": "^1.4.0",
|
||||
"polished": "^3.0.3",
|
||||
"prismjs": "^1.15.0",
|
||||
"polished": "^3.4.1",
|
||||
"prismjs": "^1.17.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-dropdown": "^1.6.4",
|
||||
"react-hot-loader": "^4.8.0",
|
||||
"react-hot-loader": "^4.12.14",
|
||||
"react-tabs": "^3.0.0",
|
||||
"slugify": "^1.3.4",
|
||||
"slugify": "^1.3.5",
|
||||
"stickyfill": "^1.1.1",
|
||||
"swagger2openapi": "^5.2.3",
|
||||
"tslib": "^1.9.3"
|
||||
"swagger2openapi": "^5.3.1",
|
||||
"tslib": "^1.10.0",
|
||||
"url-template": "^2.0.8"
|
||||
},
|
||||
"bundlesize": [
|
||||
{
|
||||
|
|
|
@ -5,11 +5,7 @@ import { ClipboardService } from '../services/ClipboardService';
|
|||
|
||||
export interface CopyButtonWrapperProps {
|
||||
data: any;
|
||||
children: (
|
||||
props: {
|
||||
renderCopyButton: (() => React.ReactNode);
|
||||
},
|
||||
) => React.ReactNode;
|
||||
children: (props: { renderCopyButton: () => React.ReactNode }) => React.ReactNode;
|
||||
}
|
||||
|
||||
export class CopyButtonWrapper extends React.PureComponent<
|
||||
|
|
|
@ -55,7 +55,7 @@ export const StyledDropdown = styled(Dropdown)`
|
|||
display: block;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
right: 0.35em;
|
||||
right: 0.3em;
|
||||
top: 50%;
|
||||
margin-top: -0.125em;
|
||||
width: 0;
|
||||
|
@ -97,7 +97,7 @@ export const StyledDropdown = styled(Dropdown)`
|
|||
export const SimpleDropdown = styled(StyledDropdown)`
|
||||
margin-left: 10px;
|
||||
text-transform: none;
|
||||
font-size: 0.929em;
|
||||
font-size: 0.969em;
|
||||
|
||||
.Dropdown-control {
|
||||
font-size: 1em;
|
||||
|
|
|
@ -75,7 +75,7 @@ export const PropertyNameCell = styled(PropertyCell)`
|
|||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
font-size: 0.929em;
|
||||
font-family: ${props => props.theme.typography.headings.fontFamily};
|
||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||
|
||||
&.deprecated {
|
||||
${deprecatedCss};
|
||||
|
|
|
@ -32,6 +32,7 @@ export const TypeName = styled(FieldLabel)`
|
|||
|
||||
export const TypeTitle = styled(FieldLabel)`
|
||||
color: ${props => props.theme.schema.typeTitleColor};
|
||||
word-break: break-word;
|
||||
`;
|
||||
|
||||
export const TypeFormat = TypeName;
|
||||
|
@ -60,14 +61,6 @@ export const PatternLabel = styled(FieldLabel)`
|
|||
&::after {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: ' /';
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '/ ';
|
||||
}
|
||||
`;
|
||||
|
||||
export const ExampleValue = styled(FieldLabel)`
|
||||
|
@ -76,11 +69,9 @@ export const ExampleValue = styled(FieldLabel)`
|
|||
background-color: ${transparentize(0.95, theme.colors.text.primary)};
|
||||
color: ${transparentize(0.1, theme.colors.text.primary)};
|
||||
|
||||
margin: ${theme.spacing.unit}px;
|
||||
padding: 0 ${theme.spacing.unit}px;
|
||||
border: 1px solid ${transparentize(0.9, theme.colors.text.primary)};
|
||||
font-family: ${theme.typography.code.fontFamily};
|
||||
color: ${theme.typography.code.color};
|
||||
}`};
|
||||
& + & {
|
||||
margin-left: 0;
|
||||
|
@ -99,6 +90,7 @@ export const ConstraintItem = styled(FieldLabel)`
|
|||
margin: 0 ${theme.spacing.unit}px;
|
||||
padding: 0 ${theme.spacing.unit}px;
|
||||
border: 1px solid ${transparentize(0.9, theme.colors.primary.main)};
|
||||
font-family: ${theme.typography.code.fontFamily};
|
||||
}`};
|
||||
& + & {
|
||||
margin-left: 0;
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import { SECTION_ATTR } from '../services/MenuStore';
|
||||
import styled, { media } from '../styled-components';
|
||||
|
||||
export const MiddlePanel = styled.div`
|
||||
width: 100%;
|
||||
padding: ${props =>
|
||||
`${props.theme.spacing.sectionVertical}px ${props.theme.spacing.sectionHorizontal}px`};
|
||||
export const MiddlePanel = styled.div<{ compact?: boolean }>`
|
||||
width: calc(100% - ${props => props.theme.rightPanel.width});
|
||||
padding: 0 ${props => 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 => ({
|
||||
[SECTION_ATTR]: props.id,
|
||||
}))<{ underlined?: boolean }>`
|
||||
})) <{ underlined?: boolean }>`
|
||||
padding: ${props => props.theme.spacing.sectionVertical}px 0;
|
||||
|
||||
&:last-child {
|
||||
|
|
|
@ -18,6 +18,7 @@ export const OneOfLabel = styled.span`
|
|||
export const OneOfButton = styled.li<{ active: boolean }>`
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 0.8em;
|
||||
cursor: pointer;
|
||||
border: 1px solid ${props => props.theme.colors.primary.main};
|
||||
|
|
|
@ -98,7 +98,7 @@ export const SmallTabs = styled(Tabs)`
|
|||
> .react-tabs__tab-panel {
|
||||
& > div,
|
||||
& > 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;
|
||||
|
||||
const website =
|
||||
(info.contact &&
|
||||
info.contact.url && (
|
||||
<InfoSpan>
|
||||
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
||||
</InfoSpan>
|
||||
)) ||
|
||||
(info.contact && info.contact.url && (
|
||||
<InfoSpan>
|
||||
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
||||
</InfoSpan>
|
||||
)) ||
|
||||
null;
|
||||
|
||||
const email =
|
||||
(info.contact &&
|
||||
info.contact.email && (
|
||||
<InfoSpan>
|
||||
{info.contact.name || 'E-mail'}:{' '}
|
||||
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
||||
</InfoSpan>
|
||||
)) ||
|
||||
(info.contact && info.contact.email && (
|
||||
<InfoSpan>
|
||||
{info.contact.name || 'E-mail'}:{' '}
|
||||
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
||||
</InfoSpan>
|
||||
)) ||
|
||||
null;
|
||||
|
||||
const terms =
|
||||
|
@ -70,11 +68,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
)) ||
|
||||
null;
|
||||
|
||||
const version =
|
||||
(info.version && (
|
||||
<span>({info.version})</span>
|
||||
)) ||
|
||||
null;
|
||||
const version = (info.version && <span>({info.version})</span>) || null;
|
||||
|
||||
return (
|
||||
<Section>
|
||||
|
|
|
@ -17,13 +17,11 @@ export class ApiLogo extends React.Component<{ info: OpenAPIInfo }> {
|
|||
// Use the english word logo if no alt text is provided
|
||||
const altText = logoInfo.altText ? logoInfo.altText : 'logo';
|
||||
|
||||
const logo = (
|
||||
<LogoImgEl
|
||||
src={logoInfo.url}
|
||||
style={{ backgroundColor: logoInfo.backgroundColor }}
|
||||
alt={altText}
|
||||
/>
|
||||
const logo = <LogoImgEl src={logoInfo.url} alt={altText} />;
|
||||
return (
|
||||
<LogoWrap style={{ backgroundColor: logoInfo.backgroundColor }}>
|
||||
{logoHref ? LinkWrap(logoHref)(logo) : logo}
|
||||
</LogoWrap>
|
||||
);
|
||||
return <LogoWrap>{logoHref ? LinkWrap(logoHref)(logo) : logo}</LogoWrap>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@ import styled from '../../styled-components';
|
|||
export const LogoImgEl = styled.img`
|
||||
max-height: ${props => props.theme.logo.maxHeight};
|
||||
max-width: ${props => props.theme.logo.maxWidth};
|
||||
padding: ${props => props.theme.logo.gutter};
|
||||
width: 100%;
|
||||
display: block;
|
||||
`;
|
||||
|
||||
export const LogoWrap = styled.div`
|
||||
text-align: center;
|
||||
padding: ${props => props.theme.logo.gutter};
|
||||
`;
|
||||
|
||||
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
|
||||
export class SectionItem extends React.Component<ContentItemProps> {
|
||||
|
@ -71,7 +71,7 @@ export class SectionItem extends React.Component<ContentItemProps> {
|
|||
return (
|
||||
<>
|
||||
<Row>
|
||||
<MiddlePanel>
|
||||
<MiddlePanel compact={level !== 1}>
|
||||
<Header>
|
||||
<ShareLink to={this.props.item.id} />
|
||||
{name}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { Markdown } from '../Markdown/Markdown';
|
|||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
|
||||
|
||||
import { getBasePath } from '../../utils';
|
||||
import { expandDefaultServerVariables, getBasePath } from '../../utils';
|
||||
import {
|
||||
EndpointInfo,
|
||||
HttpVerb,
|
||||
|
@ -60,21 +60,26 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
|
|||
/>
|
||||
</EndpointInfo>
|
||||
<ServersOverlay expanded={expanded}>
|
||||
{operation.servers.map(server => (
|
||||
<ServerItem key={server.url}>
|
||||
<Markdown source={server.description || ''} compact={true} />
|
||||
<SelectOnClick>
|
||||
<ServerUrl>
|
||||
<span>
|
||||
{hideHostname || options.hideHostname
|
||||
? getBasePath(server.url)
|
||||
: server.url}
|
||||
</span>
|
||||
{operation.path}
|
||||
</ServerUrl>
|
||||
</SelectOnClick>
|
||||
</ServerItem>
|
||||
))}
|
||||
{operation.servers.map(server => {
|
||||
const normalizedUrl = options.expandDefaultServerVariables
|
||||
? expandDefaultServerVariables(server.url, server.variables)
|
||||
: server.url;
|
||||
return (
|
||||
<ServerItem key={normalizedUrl}>
|
||||
<Markdown source={server.description || ''} compact={true} />
|
||||
<SelectOnClick>
|
||||
<ServerUrl>
|
||||
<span>
|
||||
{hideHostname || options.hideHostname
|
||||
? getBasePath(normalizedUrl)
|
||||
: normalizedUrl}
|
||||
</span>
|
||||
{operation.path}
|
||||
</ServerUrl>
|
||||
</SelectOnClick>
|
||||
</ServerItem>
|
||||
);
|
||||
})}
|
||||
</ServersOverlay>
|
||||
</OperationEndpointWrap>
|
||||
)}
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import * as React from 'react';
|
||||
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
||||
|
||||
import { l } from '../../services/Labels';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
|
||||
export interface EnumValuesProps {
|
||||
values: string[];
|
||||
type: string;
|
||||
}
|
||||
|
||||
export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
||||
static contextType = OptionsContext;
|
||||
render() {
|
||||
const { values, type } = this.props;
|
||||
const { enumSkipQuotes } = this.context;
|
||||
if (!values.length) {
|
||||
return null;
|
||||
}
|
||||
|
@ -16,11 +21,17 @@ export class EnumValues extends React.PureComponent<EnumValuesProps> {
|
|||
return (
|
||||
<div>
|
||||
<FieldLabel>
|
||||
{type === 'array' ? 'Items' : ''} {values.length === 1 ? 'Value' : 'Enum'}:
|
||||
</FieldLabel>
|
||||
{values.map((value, idx) => (
|
||||
<ExampleValue key={idx}>{JSON.stringify(value)} </ExampleValue>
|
||||
))}
|
||||
{type === 'array' ? l('enumArray') : ''}{' '}
|
||||
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||
</FieldLabel>{' '}
|
||||
{values.map((value, idx) => {
|
||||
const exampleValue = enumSkipQuotes ? value : JSON.stringify(value);
|
||||
return (
|
||||
<React.Fragment key={idx}>
|
||||
<ExampleValue>{exampleValue}</ExampleValue>{' '}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { ExampleValue, FieldLabel } from '../../common-elements/fields';
|
|||
export interface FieldDetailProps {
|
||||
value?: any;
|
||||
label: string;
|
||||
raw?: boolean;
|
||||
}
|
||||
|
||||
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
||||
|
@ -11,10 +12,12 @@ export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
|||
if (this.props.value === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = this.props.raw ? this.props.value : JSON.stringify(this.props.value);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FieldLabel> {this.props.label} </FieldLabel>{' '}
|
||||
<ExampleValue> {JSON.stringify(this.props.value)} </ExampleValue>
|
||||
<FieldLabel> {this.props.label} </FieldLabel> <ExampleValue>{value}</ExampleValue>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
TypePrefix,
|
||||
TypeTitle,
|
||||
} from '../../common-elements/fields';
|
||||
import { serializeParameterValue } from '../../utils/openapi';
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { EnumValues } from './EnumValues';
|
||||
|
@ -19,12 +20,31 @@ import { FieldDetail } from './FieldDetail';
|
|||
|
||||
import { Badge } from '../../common-elements/';
|
||||
|
||||
import { l } from '../../services/Labels';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
|
||||
export class FieldDetails extends React.PureComponent<FieldProps> {
|
||||
static contextType = OptionsContext;
|
||||
render() {
|
||||
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
|
||||
const { enumSkipQuotes } = this.context;
|
||||
|
||||
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 (
|
||||
<div>
|
||||
<div>
|
||||
|
@ -40,18 +60,18 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
|
|||
)}
|
||||
{schema.title && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||
<ConstraintsView constraints={schema.constraints} />
|
||||
{schema.nullable && <NullableLabel> Nullable </NullableLabel>}
|
||||
{schema.pattern && <PatternLabel>{schema.pattern}</PatternLabel>}
|
||||
{schema.isCircular && <RecursiveLabel> Recursive </RecursiveLabel>}
|
||||
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>}
|
||||
{schema.pattern && <PatternLabel> {schema.pattern} </PatternLabel>}
|
||||
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
||||
</div>
|
||||
{deprecated && (
|
||||
<div>
|
||||
<Badge type="warning"> Deprecated </Badge>
|
||||
<Badge type="warning"> {l('deprecated')} </Badge>
|
||||
</div>
|
||||
)}
|
||||
<FieldDetail label={'Default:'} value={schema.default} />
|
||||
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
||||
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
||||
{showExamples && <FieldDetail label={'Example:'} value={example} />}
|
||||
{exampleField}
|
||||
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
||||
<div>
|
||||
<Markdown compact={true} source={description} />
|
||||
|
|
|
@ -5,6 +5,7 @@ import { SampleControls } from '../../common-elements';
|
|||
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
|
||||
import { PrismDiv } from '../../common-elements/PrismDiv';
|
||||
import { jsonToHTML } from '../../utils/jsonToHtml';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { jsonStyles } from './style';
|
||||
|
||||
export interface JsonProps {
|
||||
|
@ -32,12 +33,18 @@ class Json extends React.PureComponent<JsonProps> {
|
|||
<span onClick={this.expandAll}> Expand all </span>
|
||||
<span onClick={this.collapseAll}> Collapse all </span>
|
||||
</SampleControls>
|
||||
<PrismDiv
|
||||
className={this.props.className}
|
||||
// tslint:disable-next-line
|
||||
ref={node => (this.node = node!)}
|
||||
dangerouslySetInnerHTML={{ __html: jsonToHTML(this.props.data) }}
|
||||
/>
|
||||
<OptionsContext.Consumer>
|
||||
{options => (
|
||||
<PrismDiv
|
||||
className={this.props.className}
|
||||
// tslint:disable-next-line
|
||||
ref={node => (this.node = node!)}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</OptionsContext.Consumer>
|
||||
</JsonViewerWrap>
|
||||
);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as React from 'react';
|
|||
|
||||
import { DropdownProps } from '../../common-elements/dropdown';
|
||||
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
|
||||
import { DropdownLabel, DropdownWrapper } from '../PayloadSamples/styled.elements';
|
||||
|
||||
export interface MediaTypeChildProps {
|
||||
schema: SchemaModel;
|
||||
|
@ -11,6 +12,8 @@ export interface MediaTypeChildProps {
|
|||
|
||||
export interface MediaTypesSwitchProps {
|
||||
content?: MediaContentModel;
|
||||
withLabel?: boolean;
|
||||
|
||||
renderDropdown: (props: DropdownProps) => 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 (
|
||||
<>
|
||||
{this.props.renderDropdown({
|
||||
value: options[activeMimeIdx],
|
||||
options,
|
||||
onChange: this.switchMedia,
|
||||
})}
|
||||
<Wrapper>
|
||||
{this.props.renderDropdown({
|
||||
value: options[activeMimeIdx],
|
||||
options,
|
||||
onChange: this.switchMedia,
|
||||
})}
|
||||
</Wrapper>
|
||||
{this.props.children(content.active)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -2,9 +2,6 @@ import * as React from 'react';
|
|||
|
||||
import { StyledPre } from '../../common-elements/samples';
|
||||
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 { useExternalExample } from './exernalExampleHook';
|
||||
|
||||
|
@ -22,14 +19,13 @@ export function Example({ example, mimeType }: ExampleProps) {
|
|||
}
|
||||
|
||||
export function ExternalExample({ example, mimeType }: ExampleProps) {
|
||||
let value = useExternalExample(example, mimeType);
|
||||
const value = useExternalExample(example, mimeType);
|
||||
|
||||
if (value === undefined) {
|
||||
return <span>Loading...</span>;
|
||||
}
|
||||
|
||||
if (value instanceof Error) {
|
||||
console.log(value);
|
||||
return (
|
||||
<StyledPre>
|
||||
Error loading external example: <br />
|
||||
|
@ -40,13 +36,5 @@ export function ExternalExample({ example, mimeType }: ExampleProps) {
|
|||
);
|
||||
}
|
||||
|
||||
if (isJsonLike(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} />;
|
||||
}
|
||||
return <ExampleValue value={value} mimeType={mimeType} />;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@ export function ExampleValue({ value, mimeType }: ExampleValueProps) {
|
|||
if (isJsonLike(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} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,33 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import { SmallTabs, Tab, TabList, TabPanel } from '../../common-elements';
|
||||
import { MediaTypeModel } from '../../services/models';
|
||||
import styled from '../../styled-components';
|
||||
|
||||
import { DropdownProps } from '../../common-elements';
|
||||
import { MediaTypeModel } from '../../services/models';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { Example } from './Example';
|
||||
import { NoSampleLabel } from './styled.elements';
|
||||
import { DropdownLabel, DropdownWrapper, NoSampleLabel } from './styled.elements';
|
||||
|
||||
export interface PayloadSamplesProps {
|
||||
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() {
|
||||
const { activeIdx } = this.state;
|
||||
const examples = this.props.mediaType.examples || {};
|
||||
const mimeType = this.props.mediaType.name;
|
||||
|
||||
|
@ -21,28 +37,46 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
|
|||
if (examplesNames.length === 0) {
|
||||
return noSample;
|
||||
}
|
||||
|
||||
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 (
|
||||
<SmallTabs defaultIndex={0}>
|
||||
<TabList>
|
||||
{examplesNames.map(name => (
|
||||
<Tab key={name}> {examples[name].summary || name} </Tab>
|
||||
))}
|
||||
</TabList>
|
||||
{examplesNames.map(name => (
|
||||
<TabPanel key={name}>
|
||||
<Example example={examples[name]} mimeType={mimeType} />
|
||||
</TabPanel>
|
||||
))}
|
||||
</SmallTabs>
|
||||
<SamplesWrapper>
|
||||
<DropdownWrapper>
|
||||
<DropdownLabel>Example</DropdownLabel>
|
||||
{this.props.renderDropdown({
|
||||
value: options[activeIdx],
|
||||
options,
|
||||
onChange: this.switchMedia,
|
||||
})}
|
||||
</DropdownWrapper>
|
||||
<div>
|
||||
{description && <Markdown source={description} />}
|
||||
<Example example={example} mimeType={mimeType} />
|
||||
</div>
|
||||
</SamplesWrapper>
|
||||
);
|
||||
} else {
|
||||
const name = examplesNames[0];
|
||||
const example = examples[examplesNames[0]];
|
||||
return (
|
||||
<div>
|
||||
<Example example={examples[name]} mimeType={mimeType} />
|
||||
</div>
|
||||
<SamplesWrapper>
|
||||
{example.description && <Markdown source={example.description} />}
|
||||
<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 { MediaTypeSamples } from './MediaTypeSamples';
|
||||
|
||||
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
||||
|
||||
import { MediaContentModel } from '../../services/models';
|
||||
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
|
||||
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
|
||||
import { InvertedSimpleDropdown, MimeLabel } from './styled.elements';
|
||||
|
||||
export interface PayloadSamplesProps {
|
||||
|
@ -21,8 +20,14 @@ export class PayloadSamples extends React.Component<PayloadSamplesProps> {
|
|||
}
|
||||
|
||||
return (
|
||||
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown}>
|
||||
{mediaType => <MediaTypeSamples key="samples" mediaType={mediaType} />}
|
||||
<MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown} withLabel={true}>
|
||||
{mediaType => (
|
||||
<MediaTypeSamples
|
||||
key="samples"
|
||||
mediaType={mediaType}
|
||||
renderDropdown={this.renderDropdown}
|
||||
/>
|
||||
)}
|
||||
</MediaTypesSwitch>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -13,22 +13,19 @@ export function useExternalExample(example: ExampleModel, mimeType: string) {
|
|||
|
||||
prevRef.current = example;
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
const load = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
value.current = await example.getExternalValue(mimeType);
|
||||
} catch (e) {
|
||||
value.current = e;
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
value.current = await example.getExternalValue(mimeType);
|
||||
} catch (e) {
|
||||
value.current = e;
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
load();
|
||||
},
|
||||
[example, mimeType],
|
||||
);
|
||||
load();
|
||||
}, [example, mimeType]);
|
||||
|
||||
return value.current;
|
||||
}
|
||||
|
|
|
@ -1,42 +1,58 @@
|
|||
// @ts-ignore
|
||||
import Dropdown from 'react-dropdown';
|
||||
|
||||
import { transparentize } from 'polished';
|
||||
import styled from '../../styled-components';
|
||||
|
||||
import { StyledDropdown } from '../../common-elements';
|
||||
|
||||
export const MimeLabel = styled.div`
|
||||
position: relative;
|
||||
top: -35px;
|
||||
left: 132px;
|
||||
margin: 0;
|
||||
font-size: 0.929em;
|
||||
color: #000;
|
||||
padding: 12px;
|
||||
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||
margin: 0 0 10px 0;
|
||||
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)`
|
||||
margin-left: 10px;
|
||||
text-transform: none;
|
||||
font-size: 0.929em;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.rightPanel.textColor};
|
||||
margin: 0 0 10px 0;
|
||||
display: block;
|
||||
|
||||
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
|
||||
.Dropdown-control {
|
||||
margin-top: 0;
|
||||
}
|
||||
.Dropdown-control,
|
||||
.Dropdown-control:hover {
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
padding: 0 1.2em 0 0;
|
||||
padding: 0.9em 1.6em 0.9em 0.9em;
|
||||
background: transparent;
|
||||
color: ${({ theme }) => theme.rightPanel.textColor};
|
||||
box-shadow: none;
|
||||
|
||||
.Dropdown-arrow {
|
||||
border-top-color: ${({ theme }) => theme.rightPanel.textColor};
|
||||
}
|
||||
}
|
||||
.Dropdown-menu {
|
||||
margin: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
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 { SourceCodeWithCopy } from '../SourceCode/SourceCode';
|
||||
|
||||
|
@ -11,6 +12,8 @@ export interface RequestSamplesProps {
|
|||
|
||||
@observer
|
||||
export class RequestSamples extends React.Component<RequestSamplesProps> {
|
||||
static contextType = OptionsContext;
|
||||
context: RedocNormalizedOptions;
|
||||
operation: OperationModel;
|
||||
|
||||
render() {
|
||||
|
@ -20,6 +23,7 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
|
|||
const samples = operation.codeSamples;
|
||||
|
||||
const hasSamples = hasBodySample || samples.length > 0;
|
||||
|
||||
return (
|
||||
(hasSamples && (
|
||||
<div>
|
||||
|
|
|
@ -22,7 +22,7 @@ export class ResponseTitle extends React.PureComponent<ResponseTitleProps> {
|
|||
<ShelfIcon
|
||||
size={'1.5em'}
|
||||
color={type}
|
||||
direction={opened ? 'up' : 'down'}
|
||||
direction={opened ? 'down' : 'right'}
|
||||
float={'left'}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -34,9 +34,9 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
|||
|
||||
const filteredFields = needFilter
|
||||
? fields.filter(item => {
|
||||
return (
|
||||
(this.props.skipReadOnly && !item.schema.readOnly) ||
|
||||
(this.props.skipWriteOnly && !item.schema.writeOnly)
|
||||
return !(
|
||||
(this.props.skipReadOnly && item.schema.readOnly) ||
|
||||
(this.props.skipWriteOnly && item.schema.writeOnly)
|
||||
);
|
||||
})
|
||||
: fields;
|
||||
|
|
|
@ -10,6 +10,8 @@ import { ArraySchema } from './ArraySchema';
|
|||
import { ObjectSchema } from './ObjectSchema';
|
||||
import { OneOfSchema } from './OneOfSchema';
|
||||
|
||||
import { l } from '../../services/Labels';
|
||||
|
||||
export interface SchemaOptions {
|
||||
showTitle?: boolean;
|
||||
skipReadOnly?: boolean;
|
||||
|
@ -34,7 +36,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
|||
<div>
|
||||
<TypeName>{schema.displayType}</TypeName>
|
||||
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
|
||||
<RecursiveLabel> Recursive </RecursiveLabel>
|
||||
<RecursiveLabel> {l('recursive')} </RecursiveLabel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -42,9 +44,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
|||
if (discriminatorProp !== undefined) {
|
||||
if (!oneOf || !oneOf.length) {
|
||||
throw new Error(
|
||||
`Looks like you are using discriminator wrong: you don't have any definition inherited from the ${
|
||||
schema.title
|
||||
}`,
|
||||
`Looks like you are using discriminator wrong: you don't have any definition inherited from the ${schema.title}`,
|
||||
);
|
||||
}
|
||||
return (
|
||||
|
@ -64,9 +64,9 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
|||
|
||||
switch (type) {
|
||||
case 'object':
|
||||
return <ObjectSchema {...this.props as any} />;
|
||||
return <ObjectSchema {...(this.props as any)} />;
|
||||
case 'array':
|
||||
return <ArraySchema {...this.props as any} />;
|
||||
return <ArraySchema {...(this.props as any)} />;
|
||||
}
|
||||
|
||||
// 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 { OpenAPISecurityScheme } from '../../types';
|
||||
import { titleize } from '../../utils/helpers';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
||||
|
||||
|
@ -48,7 +49,7 @@ export class OAuthFlow extends React.PureComponent<OAuthFlowProps> {
|
|||
<strong> Scopes: </strong>
|
||||
</div>
|
||||
<ul>
|
||||
{Object.keys(flow!.scopes).map(scope => (
|
||||
{Object.keys(flow!.scopes || {}).map(scope => (
|
||||
<li key={scope}>
|
||||
<code>{scope}</code> - <Markdown inline={true} source={flow!.scopes[scope] || ''} />
|
||||
</li>
|
||||
|
@ -84,7 +85,7 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
|||
</tr>
|
||||
{scheme.apiKey ? (
|
||||
<tr>
|
||||
<th> {scheme.apiKey.in} parameter name:</th>
|
||||
<th> {titleize(scheme.apiKey.in || '')} parameter name:</th>
|
||||
<td> {scheme.apiKey.name} </td>
|
||||
</tr>
|
||||
) : scheme.http ? (
|
||||
|
@ -93,13 +94,12 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
|||
<th> HTTP Authorization Scheme </th>
|
||||
<td> {scheme.http.scheme} </td>
|
||||
</tr>,
|
||||
scheme.http.scheme === 'bearer' &&
|
||||
scheme.http.bearerFormat && (
|
||||
<tr key="bearer">
|
||||
<th> Bearer format </th>
|
||||
<td> "{scheme.http.bearerFormat}" </td>
|
||||
</tr>
|
||||
),
|
||||
scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
|
||||
<tr key="bearer">
|
||||
<th> Bearer format </th>
|
||||
<td> "{scheme.http.bearerFormat}" </td>
|
||||
</tr>
|
||||
),
|
||||
]
|
||||
) : scheme.openId ? (
|
||||
<tr>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// import { observe } from 'mobx';
|
||||
import { observer } from 'mobx-react';
|
||||
import * as React from 'react';
|
||||
|
||||
|
@ -15,7 +16,7 @@ export interface MenuItemProps {
|
|||
|
||||
@observer
|
||||
export class MenuItem extends React.Component<MenuItemProps> {
|
||||
ref: Element | null;
|
||||
ref = React.createRef<HTMLLabelElement>();
|
||||
|
||||
activate = (evt: React.MouseEvent<HTMLElement>) => {
|
||||
this.props.onActivate!(this.props.item);
|
||||
|
@ -31,24 +32,15 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
|||
}
|
||||
|
||||
scrollIntoViewIfActive() {
|
||||
if (this.props.item.active && this.ref) {
|
||||
this.ref.scrollIntoViewIfNeeded();
|
||||
if (this.props.item.active && this.ref.current) {
|
||||
this.ref.current.scrollIntoViewIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
saveRef = ref => {
|
||||
this.ref = ref;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { item, withoutChildren } = this.props;
|
||||
return (
|
||||
<MenuItemLi
|
||||
onClick={this.activate}
|
||||
depth={item.depth}
|
||||
ref={this.saveRef}
|
||||
data-item-id={item.id}
|
||||
>
|
||||
<MenuItemLi onClick={this.activate} depth={item.depth} data-item-id={item.id}>
|
||||
{item.type === 'operation' ? (
|
||||
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
||||
) : (
|
||||
|
@ -83,11 +75,24 @@ export interface OperationMenuItemContentProps {
|
|||
}
|
||||
|
||||
@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() {
|
||||
const { item } = this.props;
|
||||
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>
|
||||
<MenuItemTitle width="calc(100% - 38px)">
|
||||
{item.name}
|
||||
|
|
|
@ -2,12 +2,14 @@ import { observer } from 'mobx-react';
|
|||
import * as React from 'react';
|
||||
|
||||
import { IMenuItem, MenuStore } from '../../services/MenuStore';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { MenuItems } from './MenuItems';
|
||||
|
||||
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
|
||||
|
||||
@observer
|
||||
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
|
||||
static contextType = OptionsContext;
|
||||
private _updateScroll?: () => void;
|
||||
|
||||
render() {
|
||||
|
@ -26,6 +28,10 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
|||
}
|
||||
|
||||
activate = (item: IMenuItem) => {
|
||||
if (item && item.active && this.context.menuToggle) {
|
||||
return item.expanded ? item.collapse() : item.expand();
|
||||
}
|
||||
|
||||
this.props.menu.activateAndScroll(item, true);
|
||||
setTimeout(() => {
|
||||
if (this._updateScroll) {
|
||||
|
|
|
@ -19,6 +19,10 @@ export interface StickySidebarProps {
|
|||
menu: MenuStore;
|
||||
}
|
||||
|
||||
export interface StickySidebarState {
|
||||
offsetTop?: string;
|
||||
}
|
||||
|
||||
const stickyfill = Stickyfill && Stickyfill();
|
||||
|
||||
const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
||||
|
@ -77,13 +81,26 @@ const FloatingButton = styled.div`
|
|||
`;
|
||||
|
||||
@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;
|
||||
|
||||
componentDidMount() {
|
||||
if (stickyfill) {
|
||||
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() {
|
||||
|
@ -92,7 +109,7 @@ export class StickyResponsiveSidebar extends React.Component<StickySidebarProps>
|
|||
}
|
||||
}
|
||||
|
||||
getScrollYOffset(options) {
|
||||
getScrollYOffset(options: RedocNormalizedOptions) {
|
||||
let top;
|
||||
if (this.props.scrollYOffset !== undefined) {
|
||||
top = RedocNormalizedOptions.normalizeScrollYOffset(this.props.scrollYOffset)();
|
||||
|
@ -105,43 +122,32 @@ export class StickyResponsiveSidebar extends React.Component<StickySidebarProps>
|
|||
render() {
|
||||
const open = this.props.menu.sideBarOpened;
|
||||
|
||||
const style = options => {
|
||||
const top = this.getScrollYOffset(options);
|
||||
return {
|
||||
top,
|
||||
height: `calc(100vh - ${top})`,
|
||||
};
|
||||
};
|
||||
const top = this.state.offsetTop;
|
||||
|
||||
return (
|
||||
<OptionsContext.Consumer>
|
||||
{options => (
|
||||
<>
|
||||
<StyledStickySidebar
|
||||
open={open}
|
||||
className={this.props.className}
|
||||
style={style(options)}
|
||||
// tslint:disable-next-line
|
||||
ref={el => {
|
||||
this.stickyElement = el as any;
|
||||
}}
|
||||
>
|
||||
{this.props.children}
|
||||
</StyledStickySidebar>
|
||||
<FloatingButton onClick={this.toggleNavMenu}>
|
||||
<AnimatedChevronButton open={open} />
|
||||
</FloatingButton>
|
||||
</>
|
||||
)}
|
||||
</OptionsContext.Consumer>
|
||||
<>
|
||||
<StyledStickySidebar
|
||||
open={open}
|
||||
className={this.props.className}
|
||||
style={{
|
||||
top,
|
||||
height: `calc(100vh - ${top})`,
|
||||
}}
|
||||
// tslint:disable-next-line
|
||||
ref={el => {
|
||||
this.stickyElement = el as any;
|
||||
}}
|
||||
>
|
||||
{this.props.children}
|
||||
</StyledStickySidebar>
|
||||
<FloatingButton onClick={this.toggleNavMenu}>
|
||||
<AnimatedChevronButton open={open} />
|
||||
</FloatingButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
private toggleNavMenu = () => {
|
||||
this.props.menu.toggleSidebar();
|
||||
};
|
||||
|
||||
// private closeNavMenu = () => {
|
||||
// this.setState({ open: false });
|
||||
// };
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"description": "",
|
||||
"example": undefined,
|
||||
"expanded": false,
|
||||
"explode": false,
|
||||
"in": undefined,
|
||||
"kind": "field",
|
||||
"name": "packSize",
|
||||
|
@ -59,6 +60,7 @@ exports[`Components SchemaView discriminator should correctly render discriminat
|
|||
"description": "",
|
||||
"example": undefined,
|
||||
"expanded": false,
|
||||
"explode": false,
|
||||
"in": undefined,
|
||||
"kind": "field",
|
||||
"name": "type",
|
||||
|
|
|
@ -28,3 +28,5 @@ export * from './OptionsProvider';
|
|||
export * from './SideMenu/';
|
||||
export * from './StickySidebar/StickyResponsiveSidebar';
|
||||
export * from './SearchBox/SearchBox';
|
||||
export * from './SchemaDefinition/SchemaDefinition';
|
||||
export * from './SourceCode/SourceCode';
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
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/es/promise';
|
||||
|
||||
import 'core-js/es6/map';
|
||||
import 'core-js/es6/symbol';
|
||||
import 'core-js/es/array/find';
|
||||
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 { SearchStore } from './SearchStore';
|
||||
|
||||
import { SchemaDefinition } from '../components/SchemaDefinition/SchemaDefinition';
|
||||
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 {
|
||||
menu: {
|
||||
|
@ -151,5 +156,18 @@ const DEFAULT_OPTIONS: RedocRawOptions = {
|
|||
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) {
|
||||
(document as any).selection.empty();
|
||||
} 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 {
|
||||
return IS_BROWSER ? window.location.hash.substring(1) : '';
|
||||
return IS_BROWSER ? decodeURIComponent(window.location.hash.substring(1)) : '';
|
||||
}
|
||||
|
||||
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 { highlight, safeSlugify } from '../utils';
|
||||
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
||||
import { AppStore } from './AppStore';
|
||||
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
|
||||
|
@ -65,6 +65,7 @@ export class MarkdownRenderer {
|
|||
container: MarkdownHeading[] = this.headings,
|
||||
parentId?: string,
|
||||
): MarkdownHeading {
|
||||
name = unescapeHTMLChars(name);
|
||||
const item = {
|
||||
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
|
||||
name,
|
||||
|
@ -88,7 +89,7 @@ export class MarkdownRenderer {
|
|||
}
|
||||
|
||||
attachHeadingsDescriptions(rawText: string) {
|
||||
const buildRegexp = heading => {
|
||||
const buildRegexp = (heading: MarkdownHeading) => {
|
||||
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
||||
};
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export class MenuBuilder {
|
|||
|
||||
const items: ContentItemModel[] = [];
|
||||
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) {
|
||||
items.push(
|
||||
...MenuBuilder.getTagGroupsItems(parser, undefined, spec['x-tagGroups'], tagsMap, options),
|
||||
|
@ -59,14 +59,16 @@ export class MenuBuilder {
|
|||
*/
|
||||
static addMarkdownItems(
|
||||
description: string,
|
||||
parent: GroupModel | undefined,
|
||||
initialDepth: number,
|
||||
options: RedocNormalizedOptions,
|
||||
): ContentItemModel[] {
|
||||
const renderer = new MarkdownRenderer(options);
|
||||
const headings = renderer.extractHeadings(description || '');
|
||||
|
||||
const mapHeadingsDeep = (parent, items, depth = 1) =>
|
||||
const mapHeadingsDeep = (_parent, items, depth = 1) =>
|
||||
items.map(heading => {
|
||||
const group = new GroupModel('section', heading, parent);
|
||||
const group = new GroupModel('section', heading, _parent);
|
||||
group.depth = depth;
|
||||
if (heading.items) {
|
||||
group.items = mapHeadingsDeep(group, heading.items, depth + 1);
|
||||
|
@ -82,7 +84,7 @@ export class MenuBuilder {
|
|||
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);
|
||||
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
|
||||
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);
|
||||
continue;
|
||||
}
|
||||
|
||||
item.items = [
|
||||
...MenuBuilder.addMarkdownItems(tag.description || '', item, item.depth + 1, options),
|
||||
...this.getOperationsItems(parser, item, tag, item.depth + 1, options),
|
||||
];
|
||||
|
||||
res.push(item);
|
||||
}
|
||||
return res;
|
||||
|
|
|
@ -116,7 +116,7 @@ export class MenuStore {
|
|||
}
|
||||
|
||||
if (isScrolledDown) {
|
||||
const el = this.getElementAt(itemIdx + 1);
|
||||
const el = this.getElementAtOrFirstChild(itemIdx + 1);
|
||||
if (this.scroll.isElementBellow(el)) {
|
||||
break;
|
||||
}
|
||||
|
@ -163,6 +163,18 @@ export class MenuStore {
|
|||
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
|
||||
*/
|
||||
|
@ -189,6 +201,11 @@ export class MenuStore {
|
|||
if ((this.activeItem && this.activeItem.id) === (item && item.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item && item.type === 'group') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deactivate(this.activeItem);
|
||||
if (!item) {
|
||||
this.history.replace('', rewriteHistory);
|
||||
|
|
|
@ -4,7 +4,11 @@ import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
|
|||
|
||||
import { appendToMdHeading, IS_BROWSER } from '../utils/';
|
||||
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 { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
|
||||
|
@ -40,6 +44,7 @@ class RefCounter {
|
|||
export class OpenAPIParser {
|
||||
specUrl?: string;
|
||||
spec: OpenAPISpec;
|
||||
mergeRefs: Set<string>;
|
||||
|
||||
private _refCounter: RefCounter = new RefCounter();
|
||||
|
||||
|
@ -53,6 +58,8 @@ export class OpenAPIParser {
|
|||
|
||||
this.spec = spec;
|
||||
|
||||
this.mergeRefs = new Set();
|
||||
|
||||
const href = IS_BROWSER ? window.location.href : '';
|
||||
if (typeof specUrl === 'string') {
|
||||
this.specUrl = urlResolve(href, specUrl);
|
||||
|
@ -74,7 +81,10 @@ export class OpenAPIParser {
|
|||
) {
|
||||
// Automatically inject Authentication section with SecurityDefinitions component
|
||||
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);
|
||||
spec.info.description = appendToMdHeading(description, 'Authentication', comment);
|
||||
}
|
||||
|
@ -176,7 +186,12 @@ export class OpenAPIParser {
|
|||
schema: OpenAPISchema,
|
||||
$ref?: string,
|
||||
forceCircular: boolean = false,
|
||||
used$Refs = new Set<string>(),
|
||||
): MergedOpenAPISchema {
|
||||
if ($ref) {
|
||||
used$Refs.add($ref);
|
||||
}
|
||||
|
||||
schema = this.hoistOneOfs(schema);
|
||||
|
||||
if (schema.allOf === undefined) {
|
||||
|
@ -187,6 +202,7 @@ export class OpenAPIParser {
|
|||
...schema,
|
||||
allOf: undefined,
|
||||
parentRefs: [],
|
||||
title: schema.title || (isNamedDefinition($ref) ? JsonPointer.baseName($ref) : undefined),
|
||||
};
|
||||
|
||||
// avoid mutating inner objects
|
||||
|
@ -197,16 +213,25 @@ export class OpenAPIParser {
|
|||
receiver.items = { ...receiver.items };
|
||||
}
|
||||
|
||||
const allOfSchemas = schema.allOf.map(subSchema => {
|
||||
const resolved = this.deref(subSchema, forceCircular);
|
||||
const subRef = subSchema.$ref || undefined;
|
||||
const subMerged = this.mergeAllOf(resolved, subRef, forceCircular);
|
||||
receiver.parentRefs!.push(...(subMerged.parentRefs || []));
|
||||
return {
|
||||
$ref: subRef,
|
||||
schema: subMerged,
|
||||
};
|
||||
});
|
||||
const allOfSchemas = schema.allOf
|
||||
.map(subSchema => {
|
||||
if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const resolved = this.deref(subSchema, forceCircular);
|
||||
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) {
|
||||
if (
|
||||
|
@ -257,17 +282,12 @@ export class OpenAPIParser {
|
|||
receiver.parentRefs!.push(subSchemaRef);
|
||||
if (receiver.title === undefined && isNamedDefinition(subSchemaRef)) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// name of definition or title on top level
|
||||
if (schema.title === undefined && isNamedDefinition($ref)) {
|
||||
receiver.title = JsonPointer.baseName($ref);
|
||||
}
|
||||
|
||||
return receiver;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import defaultTheme, { ResolvedThemeInterface, resolveTheme, ThemeInterface } fr
|
|||
import { querySelector } from '../utils/dom';
|
||||
import { isNumeric, mergeObjects } from '../utils/helpers';
|
||||
|
||||
import { LabelsConfigRaw, setRedocLabels } from './Labels';
|
||||
import { MDXComponentMeta } from './MarkdownRenderer';
|
||||
|
||||
export interface RedocRawOptions {
|
||||
|
@ -20,10 +21,19 @@ export interface RedocRawOptions {
|
|||
disableSearch?: boolean | string;
|
||||
onlyRequiredInSamples?: boolean | string;
|
||||
showExtensions?: boolean | string | string[];
|
||||
hideSingleRequestSampleTab?: boolean | string;
|
||||
menuToggle?: boolean | string;
|
||||
jsonSampleExpandLevel?: number | string | 'all';
|
||||
|
||||
unstable_ignoreMimeParameters?: boolean;
|
||||
|
||||
allowedMdComponents?: Dict<MDXComponentMeta>;
|
||||
|
||||
labels?: LabelsConfigRaw;
|
||||
|
||||
enumSkipQuotes?: boolean | string;
|
||||
|
||||
expandDefaultServerVariables?: boolean;
|
||||
}
|
||||
|
||||
function argValueToBoolean(val?: string | boolean): boolean {
|
||||
|
@ -106,6 +116,16 @@ export class RedocNormalizedOptions {
|
|||
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;
|
||||
scrollYOffset: () => number;
|
||||
hideHostname: boolean;
|
||||
|
@ -120,11 +140,17 @@ export class RedocNormalizedOptions {
|
|||
disableSearch: boolean;
|
||||
onlyRequiredInSamples: boolean;
|
||||
showExtensions: boolean | string[];
|
||||
hideSingleRequestSampleTab: boolean;
|
||||
menuToggle: boolean;
|
||||
jsonSampleExpandLevel: number;
|
||||
enumSkipQuotes: boolean;
|
||||
|
||||
/* tslint:disable-next-line */
|
||||
unstable_ignoreMimeParameters: boolean;
|
||||
allowedMdComponents: Dict<MDXComponentMeta>;
|
||||
|
||||
expandDefaultServerVariables: boolean;
|
||||
|
||||
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
||||
raw = { ...defaults, ...raw };
|
||||
const hook = raw.theme && raw.theme.extensionsHook;
|
||||
|
@ -134,6 +160,9 @@ export class RedocNormalizedOptions {
|
|||
|
||||
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.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
|
||||
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
|
||||
|
@ -147,9 +176,17 @@ export class RedocNormalizedOptions {
|
|||
this.disableSearch = true; //argValueToBoolean(raw.disableSearch);
|
||||
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
||||
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.allowedMdComponents = raw.allowedMdComponents || {};
|
||||
|
||||
this.expandDefaultServerVariables = argValueToBoolean(raw.expandDefaultServerVariables);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as lunr from 'lunr';
|
|||
|
||||
try {
|
||||
// tslint:disable-next-line
|
||||
require('core-js/es6/promise'); // bundle into worker
|
||||
require('core-js/es/promise'); // bundle into worker
|
||||
} catch (_) {
|
||||
// nope
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ Object {
|
|||
"type": "string",
|
||||
},
|
||||
},
|
||||
"title": undefined,
|
||||
},
|
||||
Object {
|
||||
"allOf": undefined,
|
||||
|
@ -38,6 +39,7 @@ Object {
|
|||
"type": "string",
|
||||
},
|
||||
},
|
||||
"title": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -59,6 +61,7 @@ Object {
|
|||
"type": "string",
|
||||
},
|
||||
},
|
||||
"title": undefined,
|
||||
},
|
||||
Object {
|
||||
"allOf": undefined,
|
||||
|
@ -76,6 +79,7 @@ Object {
|
|||
"type": "string",
|
||||
},
|
||||
},
|
||||
"title": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -10,6 +10,13 @@
|
|||
"in": "path",
|
||||
"name": "test_name",
|
||||
"schema": { "type": "string" }
|
||||
},
|
||||
"serializationParam": {
|
||||
"in": "query",
|
||||
"name": "serialization_test_name",
|
||||
"schema": { "type": "array" },
|
||||
"style": "form",
|
||||
"explode": true
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
|
|
|
@ -26,6 +26,23 @@ describe('Models', () => {
|
|||
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)', () => {
|
||||
const field = new FieldModel(
|
||||
parser,
|
||||
|
|
|
@ -37,7 +37,7 @@ describe('Models', () => {
|
|||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const schema = new SchemaModel(parser, spec.components.schemas.WithArray, '', opts);
|
||||
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(
|
||||
parser: OpenAPIParser,
|
||||
infoOrRef: Referenced<OpenAPIExample>,
|
||||
mime: string,
|
||||
public mime: string,
|
||||
encoding?: { [field: string]: OpenAPIEncoding },
|
||||
) {
|
||||
const example = parser.deref(infoOrRef);
|
||||
|
|
|
@ -1,12 +1,30 @@
|
|||
import { action, observable } from 'mobx';
|
||||
|
||||
import { OpenAPIParameter, Referenced } from '../../types';
|
||||
import {
|
||||
OpenAPIParameter,
|
||||
OpenAPIParameterLocation,
|
||||
OpenAPIParameterStyle,
|
||||
Referenced,
|
||||
} from '../../types';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
import { extractExtensions } from '../../utils/openapi';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
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
|
||||
*/
|
||||
|
@ -20,9 +38,13 @@ export class FieldModel {
|
|||
description: string;
|
||||
example?: string;
|
||||
deprecated: boolean;
|
||||
in?: string;
|
||||
in?: OpenAPIParameterLocation;
|
||||
kind: string;
|
||||
extensions?: Dict<any>;
|
||||
explode: boolean;
|
||||
style?: OpenAPIParameterStyle;
|
||||
|
||||
serializationMime?: string;
|
||||
|
||||
constructor(
|
||||
parser: OpenAPIParser,
|
||||
|
@ -35,11 +57,29 @@ export class FieldModel {
|
|||
this.name = infoOrRef.name || info.name;
|
||||
this.in = info.in;
|
||||
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 =
|
||||
info.description === undefined ? this.schema.description || '' : info.description;
|
||||
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;
|
||||
parser.exitRef(infoOrRef);
|
||||
|
||||
|
|
|
@ -40,7 +40,14 @@ export class GroupModel implements IMenuItem {
|
|||
this.type = type;
|
||||
this.name = tagOrGroup['x-displayName'] || tagOrGroup.name;
|
||||
this.level = (tagOrGroup as MarkdownHeading).level || 1;
|
||||
|
||||
// remove sections from markdown, same as in ApiInfo
|
||||
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.externalDocs = (tagOrGroup as OpenAPITag).externalDocs;
|
||||
|
||||
|
|
|
@ -77,8 +77,8 @@ export class OperationModel implements IMenuItem {
|
|||
operationSpec.operationId !== undefined
|
||||
? 'operation/' + operationSpec.operationId
|
||||
: parent !== undefined
|
||||
? parent.id + this.pointer
|
||||
: this.pointer;
|
||||
? parent.id + this.pointer
|
||||
: this.pointer;
|
||||
|
||||
this.name = getOperationSummary(operationSpec);
|
||||
this.description = operationSpec.description;
|
||||
|
|
|
@ -14,10 +14,13 @@ import {
|
|||
isNamedDefinition,
|
||||
isPrimitiveType,
|
||||
JsonPointer,
|
||||
pluralizeType,
|
||||
sortByField,
|
||||
sortByRequired,
|
||||
} from '../../utils/';
|
||||
|
||||
import { l } from '../Labels';
|
||||
|
||||
// TODO: refactor this model, maybe use getters instead of copying all the values
|
||||
export class SchemaModel {
|
||||
pointer: string;
|
||||
|
@ -145,9 +148,9 @@ export class SchemaModel {
|
|||
this.fields = buildFields(parser, schema, this.pointer, this.options);
|
||||
} else if (this.type === 'array' && schema.items) {
|
||||
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.typePrefix = this.items.typePrefix + 'Array of ';
|
||||
this.typePrefix = this.items.typePrefix + l('arrayOf');
|
||||
this.title = this.title || this.items.title;
|
||||
this.isPrimitive = this.items.isPrimitive;
|
||||
if (this.example === undefined && this.items.example !== undefined) {
|
||||
|
@ -220,7 +223,7 @@ export class SchemaModel {
|
|||
if (variant.$ref === undefined) {
|
||||
continue;
|
||||
}
|
||||
const name = JsonPointer.dirName(variant.$ref);
|
||||
const name = JsonPointer.baseName(variant.$ref);
|
||||
derived[variant.$ref] = name;
|
||||
}
|
||||
}
|
||||
|
@ -289,7 +292,10 @@ function buildFields(
|
|||
new FieldModel(
|
||||
parser,
|
||||
{
|
||||
name: 'property name *',
|
||||
name: (typeof additionalProps === 'object'
|
||||
? additionalProps['x-additionalPropertiesName'] || 'property name'
|
||||
: 'property name'
|
||||
).concat('*'),
|
||||
required: false,
|
||||
schema: additionalProps === true ? {} : additionalProps,
|
||||
kind: 'additionalProperties',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import slugify from 'slugify';
|
||||
import { mapWithLast, appendToMdHeading, mergeObjects, safeSlugify } from '../helpers';
|
||||
import { appendToMdHeading, mapWithLast, mergeObjects, safeSlugify, titleize } from '../helpers';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('helpers', () => {
|
||||
|
@ -60,7 +60,7 @@ describe('Utils', () => {
|
|||
test('should behave like Object.assign on the top level', () => {
|
||||
const obj1 = { a: { a1: 'A1' }, c: 'C' };
|
||||
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', () => {
|
||||
const obj1 = { a: ['A', 'B'] };
|
||||
|
@ -68,5 +68,11 @@ describe('Utils', () => {
|
|||
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,
|
||||
mergeParams,
|
||||
normalizeServers,
|
||||
pluralizeType,
|
||||
serializeParameterValue,
|
||||
} from '../';
|
||||
|
||||
import { OpenAPIParser } from '../../services';
|
||||
import { OpenAPIParameter } from '../../types';
|
||||
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||
import { OpenAPIParameter, OpenAPIParameterLocation, OpenAPIParameterStyle } from '../../types';
|
||||
import { expandDefaultServerVariables } from '../openapi';
|
||||
|
||||
describe('Utils', () => {
|
||||
describe('openapi getStatusCode', () => {
|
||||
|
@ -295,11 +298,8 @@ describe('Utils', () => {
|
|||
it('should expand variables', () => {
|
||||
const servers = normalizeServers('', [
|
||||
{
|
||||
url: '{protocol}{host}{basePath}',
|
||||
url: 'http://{host}{basePath}',
|
||||
variables: {
|
||||
protocol: {
|
||||
default: 'http://',
|
||||
},
|
||||
host: {
|
||||
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(servers[1].url).toEqual('http://127.0.0.2:{port}');
|
||||
expect(servers[2].url).toEqual('http://127.0.0.3');
|
||||
expect(expandDefaultServerVariables(servers[0].url, servers[0].variables)).toEqual(
|
||||
'http://127.0.0.1/path/to/endpoint',
|
||||
);
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
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
|
||||
export const mergeObjects = <T extends object = object>(target: T, ...sources: T[]): T => {
|
||||
export const mergeObjects = (target: any, ...sources: any[]): any => {
|
||||
if (!sources.length) {
|
||||
return target;
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ export function resolveUrl(url: string, to: string) {
|
|||
let res;
|
||||
if (to.startsWith('//')) {
|
||||
const { protocol: specProtocol } = parse(url);
|
||||
res = `${specProtocol}${to}`;
|
||||
res = `${specProtocol || 'https:'}${to}`;
|
||||
} else if (isAbsoluteUrl(to)) {
|
||||
res = to;
|
||||
} else if (!to.startsWith('/')) {
|
||||
|
@ -163,5 +163,38 @@ export function resolveUrl(url: string, to: 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;
|
||||
const COLLAPSE_LEVEL = 2;
|
||||
|
||||
export function jsonToHTML(json) {
|
||||
export function jsonToHTML(json, maxExpandLevel) {
|
||||
level = 1;
|
||||
let output = '';
|
||||
output += '<div class="redoc-json">';
|
||||
output += valueToHTML(json);
|
||||
output += '<code>';
|
||||
output += valueToHTML(json, maxExpandLevel);
|
||||
output += '</code>';
|
||||
output += '</div>';
|
||||
return output;
|
||||
}
|
||||
|
@ -21,8 +22,8 @@ function htmlEncode(t) {
|
|||
: '';
|
||||
}
|
||||
|
||||
function escapeForStringLiteral(str: string) {
|
||||
return str.replace(/([\\"])/g, '\\$1');
|
||||
function stringifyStringLiteral(str: string) {
|
||||
return JSON.stringify(str).slice(1, -1);
|
||||
}
|
||||
|
||||
function decorateWithSpan(value, className) {
|
||||
|
@ -33,20 +34,20 @@ function punctuation(val) {
|
|||
return '<span class="token punctuation">' + val + '</span>';
|
||||
}
|
||||
|
||||
function valueToHTML(value) {
|
||||
function valueToHTML(value, maxExpandLevel: number) {
|
||||
const valueType = typeof value;
|
||||
let output = '';
|
||||
if (value === undefined || value === null) {
|
||||
output += decorateWithSpan('null', 'token keyword');
|
||||
} else if (value && value.constructor === Array) {
|
||||
level++;
|
||||
output += arrayToHTML(value);
|
||||
output += arrayToHTML(value, maxExpandLevel);
|
||||
level--;
|
||||
} else if (value && value.constructor === Date) {
|
||||
output += decorateWithSpan('"' + value.toISOString() + '"', 'token string');
|
||||
} else if (valueType === 'object') {
|
||||
level++;
|
||||
output += objectToHTML(value);
|
||||
output += objectToHTML(value, maxExpandLevel);
|
||||
level--;
|
||||
} else if (valueType === 'number') {
|
||||
output += decorateWithSpan(value, 'token number');
|
||||
|
@ -57,11 +58,11 @@ function valueToHTML(value) {
|
|||
'<a href="' +
|
||||
value +
|
||||
'">' +
|
||||
htmlEncode(escapeForStringLiteral(value)) +
|
||||
htmlEncode(stringifyStringLiteral(value)) +
|
||||
'</a>' +
|
||||
decorateWithSpan('"', 'token string');
|
||||
} else {
|
||||
output += decorateWithSpan('"' + escapeForStringLiteral(value) + '"', 'token string');
|
||||
output += decorateWithSpan('"' + stringifyStringLiteral(value) + '"', 'token string');
|
||||
}
|
||||
} else if (valueType === 'boolean') {
|
||||
output += decorateWithSpan(value, 'token boolean');
|
||||
|
@ -70,8 +71,8 @@ function valueToHTML(value) {
|
|||
return output;
|
||||
}
|
||||
|
||||
function arrayToHTML(json) {
|
||||
const collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : '';
|
||||
function arrayToHTML(json, maxExpandLevel: number) {
|
||||
const collapsed = level > maxExpandLevel ? 'collapsed' : '';
|
||||
let output = `<div class="collapser"></div>${punctuation(
|
||||
'[',
|
||||
)}<span class="ellipsis"></span><ul class="array collapsible">`;
|
||||
|
@ -80,7 +81,7 @@ function arrayToHTML(json) {
|
|||
for (let i = 0; i < length; i++) {
|
||||
hasContents = true;
|
||||
output += '<li><div class="hoverable ' + collapsed + '">';
|
||||
output += valueToHTML(json[i]);
|
||||
output += valueToHTML(json[i], maxExpandLevel);
|
||||
if (i < length - 1) {
|
||||
output += ',';
|
||||
}
|
||||
|
@ -93,8 +94,8 @@ function arrayToHTML(json) {
|
|||
return output;
|
||||
}
|
||||
|
||||
function objectToHTML(json) {
|
||||
const collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : '';
|
||||
function objectToHTML(json, maxExpandLevel: number) {
|
||||
const collapsed = level > maxExpandLevel ? 'collapsed' : '';
|
||||
const keys = Object.keys(json);
|
||||
const length = keys.length;
|
||||
let output = `<div class="collapser"></div>${punctuation(
|
||||
|
@ -106,7 +107,7 @@ function objectToHTML(json) {
|
|||
hasContents = true;
|
||||
output += '<li><div class="hoverable ' + collapsed + '">';
|
||||
output += '<span class="property token string">"' + htmlEncode(key) + '"</span>: ';
|
||||
output += valueToHTML(json[key]);
|
||||
output += valueToHTML(json[key], maxExpandLevel);
|
||||
if (i < length - 1) {
|
||||
output += punctuation(',');
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import { OpenAPISpec } from '../types';
|
|||
|
||||
export async function loadAndBundleSpec(specUrlOrObject: object | string): Promise<OpenAPISpec> {
|
||||
const parser = new JsonSchemaRefParser();
|
||||
const spec = await parser.bundle(specUrlOrObject, {
|
||||
const spec = (await parser.bundle(specUrlOrObject, {
|
||||
resolve: { http: { withCredentials: false } },
|
||||
} as object);
|
||||
} as object)) as any;
|
||||
|
||||
if (spec.swagger !== undefined) {
|
||||
return convertSwagger2OpenAPI(spec);
|
||||
|
@ -19,7 +19,7 @@ export async function loadAndBundleSpec(specUrlOrObject: object | string): Promi
|
|||
export function convertSwagger2OpenAPI(spec: any): Promise<OpenAPISpec> {
|
||||
console.warn('[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0');
|
||||
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
|
||||
if (err) {
|
||||
return reject(err);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { dirname } from 'path';
|
||||
const URLtemplate = require('url-template');
|
||||
|
||||
import { OpenAPIParser } from '../services/OpenAPIParser';
|
||||
import {
|
||||
|
@ -6,12 +7,13 @@ import {
|
|||
OpenAPIMediaType,
|
||||
OpenAPIOperation,
|
||||
OpenAPIParameter,
|
||||
OpenAPIParameterStyle,
|
||||
OpenAPISchema,
|
||||
OpenAPIServer,
|
||||
Referenced,
|
||||
} from '../types';
|
||||
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 {
|
||||
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';
|
||||
}
|
||||
|
||||
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 {
|
||||
if (Array.isArray(fieldVal)) {
|
||||
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
|
||||
* 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] || {};
|
||||
switch (style) {
|
||||
case 'form':
|
||||
return formEncodeField(fieldVal, fieldName, explode);
|
||||
break;
|
||||
return serializeFormValue(fieldName, explode, fieldVal);
|
||||
case 'spaceDelimited':
|
||||
return delimitedEncodeField(fieldVal, fieldName, '%20');
|
||||
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 {
|
||||
if (contentType.search(/xml/i) !== -1) {
|
||||
return 'xml';
|
||||
|
@ -356,7 +487,7 @@ export function mergeSimilarMediaTypes(types: Dict<OpenAPIMediaType>): Dict<Open
|
|||
return mergedTypes;
|
||||
}
|
||||
|
||||
function expandVariables(url: string, variables: object = {}) {
|
||||
export function expandDefaultServerVariables(url: string, variables: object = {}) {
|
||||
return url.replace(
|
||||
/(?:{)(\w+)(?:})/g,
|
||||
(match, name) => (variables[name] && variables[name].default) || match,
|
||||
|
@ -367,32 +498,41 @@ export function normalizeServers(
|
|||
specUrl: string | undefined,
|
||||
servers: OpenAPIServer[],
|
||||
): OpenAPIServer[] {
|
||||
const baseUrl =
|
||||
specUrl === undefined ? (IS_BROWSER ? window.location.href : '') : dirname(specUrl);
|
||||
const getHref = () => {
|
||||
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) {
|
||||
return [
|
||||
{
|
||||
url: baseUrl,
|
||||
url: stripTrailingSlash(baseUrl),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function normalizeUrl(url: string, variables: object | undefined): string {
|
||||
url = expandVariables(url, variables);
|
||||
function normalizeUrl(url: string): string {
|
||||
return resolveUrl(baseUrl, url);
|
||||
}
|
||||
|
||||
return servers.map(server => {
|
||||
return {
|
||||
...server,
|
||||
url: normalizeUrl(server.url, server.variables),
|
||||
url: normalizeUrl(server.url),
|
||||
description: server.description || '',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
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 function setSecuritySchemePrefix(prefix: string) {
|
||||
SECURITY_SCHEMES_SECTION_PREFIX = prefix;
|
||||
|
@ -416,6 +556,7 @@ export function isRedocExtension(key: string): boolean {
|
|||
'x-servers': true,
|
||||
'x-tagGroups': true,
|
||||
'x-traitTag': true,
|
||||
'x-additionalPropertiesName': true,
|
||||
};
|
||||
|
||||
return key in redocExtensions;
|
||||
|
@ -434,3 +575,10 @@ export function extractExtensions(obj: object, showExtensions: string[] | true):
|
|||
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:discovery']; // 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['bbc.com']; // too big
|
||||
delete apisGuruList['osisoft.com']; // too big
|
||||
|
|
|
@ -33,7 +33,7 @@ try {
|
|||
const BANNER = `ReDoc - OpenAPI/Swagger-generated API Reference Documentation
|
||||
-------------------------------------------------------------
|
||||
Version: ${VERSION}
|
||||
Repo: https://github.com/Rebilly/ReDoc`;
|
||||
Repo: https://github.com/Redocly/redoc`;
|
||||
|
||||
export default (env: { standalone?: boolean } = {}, { mode }) => ({
|
||||
entry: env.standalone ? ['./src/polyfills.ts', './src/standalone.tsx'] : './src/index.ts',
|
||||
|
@ -65,13 +65,13 @@ export default (env: { standalone?: boolean } = {}, { mode }) => ({
|
|||
? {
|
||||
esprima: 'esprima',
|
||||
'node-fetch': 'null',
|
||||
'node-fetch-h2': 'null',
|
||||
yaml: 'null',
|
||||
'safe-json-stringify': 'null',
|
||||
}
|
||||
: (context, request, callback) => {
|
||||
// ignore node-fetch dep of swagger2openapi as it is not used
|
||||
if (/node-fetch$/i.test(request)) {
|
||||
return callback(null, 'var undefined');
|
||||
}
|
||||
if (/esprima$/i.test(request)) {
|
||||
if (/esprima|node-fetch|node-fetch-h2|yaml|safe-json-stringify$/i.test(request)) {
|
||||
return callback(null, 'var undefined');
|
||||
}
|
||||
return nodeExternals(context, request, callback);
|
||||
|
|
Loading…
Reference in New Issue
Block a user