Merge branch 'master' into update-watch-backend

This commit is contained in:
Roman Hotsiy 2019-09-30 09:49:59 +02:00 committed by GitHub
commit 2ca105bb94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
112 changed files with 8754 additions and 6972 deletions

View File

@ -27,7 +27,7 @@ You will need [Node.js](http://nodejs.org) at `v8.0.0+` and [Yarn](https://yarnp
After cloning the repo, run:
```bash
$ yarn install # or yarn
$ yarn install # or npm
```
### Commonly used NPM scripts

21
.github/workflows/unit-tests.yml vendored Normal file
View 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

View File

@ -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

View File

@ -1,3 +1,230 @@
# [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)
### Bug Fixes
* move swagger2openapi to deps because of missing transitive deps ([ed9b878](https://github.com/Rebilly/ReDoc/commit/ed9b878))
### Features
* display requestBody description [#833](https://github.com/Rebilly/ReDoc/issues/833) ([#838](https://github.com/Rebilly/ReDoc/issues/838)) ([56ca371](https://github.com/Rebilly/ReDoc/commit/56ca371))
# [2.0.0-rc.3](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.2...v2.0.0-rc.3) (2019-03-15)
### Bug Fixes
* add extra deref step for anyOf/oneOf variants ([d81b631](https://github.com/Rebilly/ReDoc/commit/d81b631)), closes [#810](https://github.com/Rebilly/ReDoc/issues/810)
* duplicate keys in request samples ([3ce5bff](https://github.com/Rebilly/ReDoc/commit/3ce5bff)), closes [#815](https://github.com/Rebilly/ReDoc/issues/815)
* escape backslashes in string literals ([#823](https://github.com/Rebilly/ReDoc/issues/823)) ([70faca1](https://github.com/Rebilly/ReDoc/commit/70faca1)), closes [#822](https://github.com/Rebilly/ReDoc/issues/822)
* escape quotes in string values ([0473165](https://github.com/Rebilly/ReDoc/commit/0473165)), closes [#882](https://github.com/Rebilly/ReDoc/issues/882)
* pin lunr version in ReDoc ([178ff4c](https://github.com/Rebilly/ReDoc/commit/178ff4c)), closes [#844](https://github.com/Rebilly/ReDoc/issues/844)
* set last section min-height ([4dd79cd](https://github.com/Rebilly/ReDoc/commit/4dd79cd)), closes [#820](https://github.com/Rebilly/ReDoc/issues/820)
### Features
* support externalValue for examples ([2cdfcd2](https://github.com/Rebilly/ReDoc/commit/2cdfcd2)), closes [#551](https://github.com/Rebilly/ReDoc/issues/551) [#840](https://github.com/Rebilly/ReDoc/issues/840)
* **cli:** Add templateOptions param to pass additional data to custom template ([#792](https://github.com/Rebilly/ReDoc/issues/792)) ([4e8ee03](https://github.com/Rebilly/ReDoc/commit/4e8ee03))
# [2.0.0-rc.2](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.1...v2.0.0-rc.2) (2019-01-27)
### Bug Fixes
* make padding for md code blocks and code samples consistent ([007752d](https://github.com/Rebilly/ReDoc/commit/007752d))
* make syntax highlighting for md js code blocks same as for payload samples ([d197c0f](https://github.com/Rebilly/ReDoc/commit/d197c0f))
* Only display API version if present ([#773](https://github.com/Rebilly/ReDoc/issues/773)) ([fb3cb36](https://github.com/Rebilly/ReDoc/commit/fb3cb36))
# [2.0.0-rc.1](https://github.com/Rebilly/ReDoc/compare/v2.0.0-rc.0...v2.0.0-rc.1) (2019-01-17)
### Bug Fixes
* allow docker container serving under non-root URLs ([#731](https://github.com/Rebilly/ReDoc/issues/731)) ([cfb6f0f](https://github.com/Rebilly/ReDoc/commit/cfb6f0f)), closes [#730](https://github.com/Rebilly/ReDoc/issues/730)
* make example/defaults badge consistent with code blocks ([fa39ce4](https://github.com/Rebilly/ReDoc/commit/fa39ce4))
* pattern constrain spacing ([c7436f2](https://github.com/Rebilly/ReDoc/commit/c7436f2))
* sidebar navigation issues when scrollYOffset is float number ([c04f387](https://github.com/Rebilly/ReDoc/commit/c04f387)), closes [#748](https://github.com/Rebilly/ReDoc/issues/748)
# [2.0.0-rc.0](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.41...v2.0.0-rc.0) (2018-11-27)
### Bug Fixes
* false-positive recursive detection with oneOf ([59eaa8d](https://github.com/Rebilly/ReDoc/commit/59eaa8d)), closes [#723](https://github.com/Rebilly/ReDoc/issues/723) [#585](https://github.com/Rebilly/ReDoc/issues/585)
* fix hideHostname also hiding basePath ([b5f3224](https://github.com/Rebilly/ReDoc/commit/b5f3224)), closes [#677](https://github.com/Rebilly/ReDoc/issues/677)
* fix spacing with nested markdown lists ([f2f6909](https://github.com/Rebilly/ReDoc/commit/f2f6909)), closes [#718](https://github.com/Rebilly/ReDoc/issues/718)
* improve scrolling performance in Chrome with non-wrapped json examples ([a69c402](https://github.com/Rebilly/ReDoc/commit/a69c402))
* nested oneOf button spacing ([3673720](https://github.com/Rebilly/ReDoc/commit/3673720)), closes [#719](https://github.com/Rebilly/ReDoc/issues/719)
* onLoaded callback not run on spec error ([e77df0c](https://github.com/Rebilly/ReDoc/commit/e77df0c)), closes [#690](https://github.com/Rebilly/ReDoc/issues/690)
* theme improvments by [@stasiukanya](https://github.com/stasiukanya) ([e2d0cd5](https://github.com/Rebilly/ReDoc/commit/e2d0cd5))
* **cli:** old peer dependency issue with styled-components ([#699](https://github.com/Rebilly/ReDoc/issues/699)) ([9e2853c](https://github.com/Rebilly/ReDoc/commit/9e2853c))
### Features
* Add feature to specify href for logo explicitly ([#645](https://github.com/Rebilly/ReDoc/issues/645)) ([87fd7d7](https://github.com/Rebilly/ReDoc/commit/87fd7d7))
* add support for markdown in Server Object ([155d214](https://github.com/Rebilly/ReDoc/commit/155d214))
* Add support for minLength and maxLength constraint humanization ([#700](https://github.com/Rebilly/ReDoc/issues/700)) ([f40568b](https://github.com/Rebilly/ReDoc/commit/f40568b)), closes [#42](https://github.com/Rebilly/ReDoc/issues/42) [/github.com/Rebilly/ReDoc/issues/42#issuecomment-371883853](https://github.com//github.com/Rebilly/ReDoc/issues/42/issues/issuecomment-371883853)
<a name="2.0.0-alpha.41"></a>
# [2.0.0-alpha.41](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.40...v2.0.0-alpha.41) (2018-10-18)

View File

@ -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**
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![dependencies Status](https://david-dm.org/Rebilly/ReDoc/status.svg)](https://david-dm.org/Rebilly/ReDoc) [![devDependencies Status](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
[![Build Status](https://travis-ci.org/Redocly/redoc.svg?branch=master)](https://travis-ci.org/Redocly/redoc) [![Coverage Status](https://coveralls.io/repos/Redocly/redoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Redocly/redoc?branch=master) [![dependencies Status](https://david-dm.org/Redocly/redoc/status.svg)](https://david-dm.org/Redocly/redoc) [![devDependencies Status](https://david-dm.org/Redocly/redoc/dev-status.svg)](https://david-dm.org/Redocly/redoc#info=devDependencies) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Redocly/redoc/blob/master/LICENSE)
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](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)**
![ReDoc demo](https://raw.githubusercontent.com/Rebilly/ReDoc/master/demo/redoc-demo.png)
![ReDoc demo](https://raw.githubusercontent.com/Redocly/redoc/master/demo/redoc-demo.png)
## [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>
![](docs/images/discriminator-demo.gif)
@ -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.
@ -70,6 +70,7 @@ Additionally, all the 1.x releases are hosted on our GitHub Pages-based **CDN**:
- [Shopify Draft Orders](https://help.shopify.com/api/draft-orders)
- [Discourse](http://docs.discourse.org)
- [APIs.guru](https://apis.guru/api-doc/)
- [FastAPI](https://github.com/tiangolo/fastapi)
## Deployment
@ -137,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:
@ -161,10 +162,10 @@ Also you can pass options:
```js
<RedocStandalone
specUrl="http://rebilly.github.io/RebillyAPI/swagger.json"
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
options={{
nativeScrollbars: true,
theme: { colors: { main: '#dd5522' } },
theme: { colors: { primary: { main: '#dd5522' } } },
}}
/>
```
@ -175,7 +176,7 @@ You can also specify `onLoaded` callback which will be called each time Redoc ha
```js
<RedocStandalone
specUrl="http://rebilly.github.io/RebillyAPI/swagger.json"
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
onLoaded={error => {
if (!error) {
console.log('Yay!');
@ -184,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:
@ -197,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
@ -205,7 +208,7 @@ Also you may rewrite some predefined environment variables defined in [Dockerfil
You can inject Security Definitions widget into any place of your specification `description`. Check out details [here](docs/security-definitions-injection.md).
### Swagger vendor extensions
ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions):
ReDoc makes use of the following [vendor extensions](https://swagger.io/specification/#specificationExtensions):
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
* [`x-code-samples`](docs/redoc-vendor-extensions.md#x-code-samples) - specify operation code samples
@ -215,6 +218,7 @@ ReDoc makes use of the following [vendor extensions](http://swagger.io/specifica
* [`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`.
@ -226,7 +230,7 @@ You can use all of the following options with standalone version on <redoc> tag
* **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);
* `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.~~
* `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.~~
* `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.
@ -239,7 +243,9 @@ You can use all of the following options with standalone version on <redoc> tag
* `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)
* `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`.
* `menuToggle` - if true clicking second time on expanded menu item will collapse it, default `false`
* `theme` - ReDoc theme. Not documented yet. For details check source code: [theme.ts](https://github.com/Redocly/redoc/blob/master/src/theme.ts)
## Advanced usage of standalone version
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:

23
cli/Dockerfile Normal file
View File

@ -0,0 +1,23 @@
# Package the 'redoc-cli' as a docker image.
#
# To build:
# $ cd <Redoc project directory>
# $ docker build -t redoc-cli -f cli/Dockerfile .
#
# To run:
# To display the command line options:
# $ docker run --rm -it redoc-cli --help
# .. will display the comand line help
#
# To turn `swagger.yml` file in the current directory, to html documentation 'redoc-static.html'
# $ docker run --rm -it -v $PWD:/data redoc-cli bundle swagger.yml
FROM node:alpine
RUN npm install -g redoc-cli
WORKDIR /data
EXPOSE 8080
ENTRYPOINT ["redoc-cli"]
CMD []

View File

@ -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`.

View File

@ -25,7 +25,10 @@ interface Options {
cdn?: boolean;
output?: string;
title?: string;
disableGoogleFont?: boolean;
port?: number;
templateFileName?: string;
templateOptions?: any;
redocOptions?: any;
}
@ -60,16 +63,17 @@ YargsParser.command(
yargs.demandOption('spec');
return yargs;
},
async (argv: any) => {
const config = {
ssr: argv.ssr,
watch: argv.watch,
templateFileName: argv.template,
async argv => {
const config: Options = {
ssr: argv.ssr as boolean,
watch: argv.watch as boolean,
templateFileName: argv.template as string,
templateOptions: argv.templateOptions || {},
redocOptions: argv.options || {},
};
try {
await serve(argv.port, argv.spec, config);
await serve(argv.port as number, argv.spec as string, config);
} catch (e) {
handleError(e);
}
@ -96,6 +100,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',
@ -108,10 +118,12 @@ YargsParser.command(
async (argv: any) => {
const config = {
ssr: true,
output: argv.o,
cdn: argv.cdn,
title: argv.title,
templateFileName: argv.template,
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: argv.options || {},
};
@ -128,6 +140,10 @@ YargsParser.command(
describe: 'Path to handlebars page template, see https://git.io/vh8fP for the example ',
type: 'string',
})
.options('templateOptions', {
describe:
'Additional options that you want pass to template. Use dot notation, e.g. templateOptions.metaDescription',
})
.options('options', {
describe: 'ReDoc options, use dot notation, e.g. options.nativeScrollbars',
}).argv;
@ -148,7 +164,9 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
},
);
} 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, {
@ -221,7 +239,15 @@ async function bundle(pathToSpec, options: Options = {}) {
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;
@ -264,6 +290,8 @@ async function getPageHTML(
: `<script>${redocStandaloneSrc}</script>`) + css
: '<script src="redoc.standalone.js"></script>',
title,
disableGoogleFont,
templateOptions,
});
}

View File

@ -1,35 +1,35 @@
{
"name": "redoc-cli",
"version": "0.7.0",
"version": "0.8.6",
"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.4.2",
"react-dom": "^16.4.2",
"redoc": "^2.0.0-alpha.41",
"styled-components": "^4.0.2",
"tslib": "^1.9.3",
"yargs": "^12.0.1"
},
"scripts": {
"ci-publish": "ci-publish"
"node-libs-browser": "^2.2.1",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"redoc": "2.0.0-rc.13",
"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"
}
}

View File

@ -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>

File diff suppressed because it is too large Load Diff

View File

@ -5,13 +5,13 @@
Serve remote spec by URL:
docker run -it --rm -p 80:80 \
-e SPEC_URL='http://localhost:8000/swagger.yaml' redoc
-e SPEC_URL='http://localhost:8000/swagger.yaml' redocly/redoc
Serve local file:
docker run -it --rm -p 80:80 \
-v $(PWD)/demo/swagger.yaml:/usr/share/nginx/html/swagger.yaml \
-e SPEC_URL=swagger.yaml redoc
-v $(pwd)/demo/swagger.yaml:/usr/share/nginx/html/swagger.yaml \
-e SPEC_URL=swagger.yaml redocly/redoc
## Runtime configuration options
@ -19,8 +19,8 @@ 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
docker build -t redoc .
docker build -t redocly/redoc .

View File

@ -6,6 +6,6 @@ sed -i -e "s|%PAGE_TITLE%|$PAGE_TITLE|g" /usr/share/nginx/html/index.html
sed -i -e "s|%PAGE_FAVICON%|$PAGE_FAVICON|g" /usr/share/nginx/html/index.html
sed -i -e "s|%SPEC_URL%|$SPEC_URL|g" /usr/share/nginx/html/index.html
sed -i -e "s|%REDOC_OPTIONS%|${REDOC_OPTIONS}|g" /usr/share/nginx/html/index.html
sed -i -e "s|80|${PORT}|g" /etc/nginx/nginx.conf
sed -i -e "s|\(listen\s*\) [0-9]*|\1 ${PORT}|g" /etc/nginx/nginx.conf
exec nginx -g 'daemon off;'

View File

@ -20,7 +20,7 @@
<body>
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
<script src="/redoc.standalone.js"></script>
<script src="redoc.standalone.js"></script>
</body>
</html>

2
custom.d.ts vendored
View File

@ -18,6 +18,8 @@ declare module '*.css' {
declare var __REDOC_VERSION__: string;
declare var __REDOC_REVISION__: string;
declare var reactHotLoaderGlobal: any;
interface Element {
scrollIntoViewIfNeeded(centerIfNeeded?: boolean): void;
}

View File

@ -4,16 +4,10 @@
*/
import * as React from 'react';
import styled, { StyledFunction } from 'styled-components';
import styled from '../src/styled-components';
function withProps<T, U extends HTMLElement = HTMLElement>(
styledFunction: StyledFunction<React.HTMLProps<U>>,
): StyledFunction<T & React.HTMLProps<U>> {
return styledFunction;
}
const DropDownItem = withProps<{ active: boolean }>(styled.li)`
${props => ((props as any).active ? 'background-color: #eee' : '')};
const DropDownItem = styled.li<{ active?: boolean }>`
${(props: any) => (props.active ? 'background-color: #eee' : '')};
padding: 13px 16px;
&:hover {
background-color: #eee;

View File

@ -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&amp;repo=ReDoc&amp;type=star&amp;count=true&amp;size=large"
src="https://ghbtns.com/github-btn.html?user=Redocly&amp;repo=redoc&amp;type=star&amp;count=true&amp;size=large"
frameBorder="0"
scrolling="0"
width="150px"

View File

@ -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,6 +79,10 @@ x-tagGroups:
- name: User Management
tags:
- user
- name: Models
tags:
- pet_model
- store_model
paths:
/pet:
parameters:
@ -754,6 +766,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,12 +883,12 @@ 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:
@ -926,3 +943,10 @@ components:
type: apiKey
name: api_key
in: header
examples:
Order:
value:
quantity: 1,
shipDate: 2018-10-19T16:46:45Z,
status: placed,
complete: false

View File

@ -17,9 +17,12 @@ const renderRoot = (props: RedocProps) =>
);
const big = window.location.search.indexOf('big') > -1;
const swagger = window.location.search.indexOf('swagger') > -1; // compatibility mode ?
const swagger = window.location.search.indexOf('swagger') > -1;
const specUrl = swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml';
const userUrl = window.location.search.match(/url=(.*)$/);
const specUrl =
(userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml');
let store;
const options: RedocRawOptions = { nativeScrollbars: false };

View File

@ -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

View File

@ -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'),
@ -57,8 +63,8 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
env.playground
? 'playground/hmr-playground.tsx'
: env.bench
? '../benchmark/index.tsx'
: 'index.tsx',
? '../benchmark/index.tsx'
: 'index.tsx',
),
],
output: {
@ -77,6 +83,12 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],
alias:
mode !== 'production'
? {
'react-dom': '@hot-loader/react-dom',
}
: {},
},
node: {
@ -88,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: {
@ -96,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/],
},
{
@ -105,7 +124,6 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
loader: 'css-loader',
options: {
sourceMap: true,
minimize: true,
},
},
},
@ -136,8 +154,8 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
template: env.playground
? 'demo/playground/index.html'
: env.bench
? 'benchmark/index.html'
: 'demo/index.html',
? 'benchmark/index.html'
: 'demo/index.html',
}),
new ForkTsCheckerWebpackPlugin(),
ignore(/js-yaml\/dumper\.js$/),

View File

@ -1,10 +1,10 @@
# ReDoc vendor extensions
ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions)
ReDoc makes use of the following [vendor extensions](https://swagger.io/specification/#specificationExtensions)
### Swagger Object vendor extensions
Extend OpenAPI root [Swagger Object](http://swagger.io/specification/#swaggerObject)
Extend OpenAPI root [Swagger Object](https://swagger.io/specification/#oasObject)
#### x-servers
Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#server-object). Currently doesn't support templates.
Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#server-object). Currently doesn't support templates.
#### x-tagGroups
@ -96,6 +96,7 @@ The information about API logo
| url | string | The URL pointing to the spec logo. MUST be in the format of a URL. It SHOULD be an absolute URL so your API definition is usable from any location
| backgroundColor | string | background color to be used. MUST be RGB color in [hexadecimal format] (https://en.wikipedia.org/wiki/Web_colors#Hex_triplet)
| altText | string | Text to use for alt tag on the logo. Defaults to 'logo' if nothing is provided.
| href | string | The URL pointing to the contact page. Default to 'info.contact.url' field of the OAS.
###### x-logo example
@ -106,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"
}
@ -119,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"
```
@ -131,14 +132,7 @@ Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
#### x-traitTag
| Field Name | Type | Description |
| :------------- | :------: | :---------- |
| x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
#### x-displayName
| Field Name | Type | Description |
| :------------- | :------: | :---------- |
| x-displayName | string | Define the text that is used for this tag in the menu and in section headings |
| x-traitTag | boolean | In Swagger two operations can have multiple tags. This property distinguishes between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
###### Usage in Redoc
Tags that have `x-traitTag` set to `true` are listed in side-menu but don't have any subitems (operations). Tag `description` is rendered as well.
@ -160,6 +154,12 @@ description: Pagination description (can use markdown syntax)
x-traitTag: true
```
#### x-displayName
| Field Name | Type | Description |
| :------------- | :------: | :---------- |
| x-displayName | string | Defines the text that is used for this tag in the menu and in section headings |
### Operation Object vendor extensions
Extends OpenAPI [Operation Object](http://swagger.io/specification/#operationObject)
#### x-code-samples
@ -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
View 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
View 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
View File

@ -0,0 +1,6 @@
<html>
<body>
<script src="../bundles/redoc.standalone.js">{}</script>
<div id="redoc" />
</body>
</html>;

View File

@ -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', () => {

View 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',
);
});
});
});

View File

@ -1,10 +1,10 @@
{
"name": "redoc",
"version": "2.0.0-alpha.41",
"version": "2.0.0-rc.14",
"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,107 +52,111 @@
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ."
},
"devDependencies": {
"@babel/core": "7.1.2",
"@babel/plugin-syntax-decorators": "7.1.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-jsx": "7.0.0",
"@babel/plugin-syntax-typescript": "7.0.0",
"@cypress/webpack-preprocessor": "3.0.0",
"@types/chai": "4.1.6",
"@types/dompurify": "^0.0.31",
"@types/enzyme": "^3.1.14",
"@types/enzyme-to-json": "^1.5.2",
"@types/jest": "^23.3.5",
"@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.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.18",
"@types/json-pointer": "^1.0.30",
"@types/lodash": "^4.14.117",
"@types/lunr": "^2.1.6",
"@types/mark.js": "^8.11.1",
"@types/marked": "^0.4.2",
"@types/prismjs": "^1.6.4",
"@types/prop-types": "^15.5.6",
"@types/react": "^16.4.18",
"@types/react-dom": "^16.0.9",
"@types/lodash": "^4.14.141",
"@types/lunr": "^2.3.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.0",
"@types/styled-components": "^4.0.1",
"@types/react-tabs": "^2.3.1",
"@types/styled-components": "^4.1.19",
"@types/tapable": "1.0.4",
"@types/webpack": "^4.4.17",
"@types/webpack-env": "^1.13.0",
"@types/yargs": "^12.0.1",
"babel-loader": "8.0.4",
"babel-plugin-styled-components": "^1.8.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.0",
"conventional-changelog-cli": "^2.0.5",
"copy-webpack-plugin": "^4.5.3",
"core-js": "^2.5.7",
"coveralls": "^3.0.2",
"css-loader": "^1.0.0",
"cypress": "~3.1.0",
"deploy-to-gh-pages": "^1.3.6",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.6.0",
"enzyme-to-json": "^3.3.4",
"fork-ts-checker-webpack-plugin": "0.4.10",
"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": "^23.6.0",
"license-checker": "^24.0.1",
"lodash": "^4.17.11",
"jest": "^24.9.0",
"license-checker": "^25.0.1",
"lodash": "^4.17.15",
"mobx": "^4.3.1",
"prettier": "^1.14.3",
"prettier-eslint": "^8.8.2",
"puppeteer": "^1.9.0",
"raf": "^3.4.0",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"rimraf": "^2.6.2",
"shelljs": "^0.8.1",
"prettier": "^1.18.2",
"prettier-eslint": "^9.0.0",
"raf": "^3.4.1",
"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.0.2",
"swagger2openapi": "^3.2.8",
"ts-jest": "23.10.4",
"ts-loader": "5.2.2",
"ts-node": "^7.0.1",
"tslint": "^5.11.0",
"tslint-react": "^3.4.0",
"typescript": "^3.1.3",
"webpack": "^4.21.0",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.9",
"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": {
"mobx": "^4.2.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"styled-components": "^4.0.1"
"core-js": "^3.1.4",
"mobx": "^4.2.0 || ^5.0.0",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"styled-components": "^4.1.1"
},
"dependencies": {
"classnames": "^2.2.6",
"decko": "^1.2.0",
"dompurify": "^1.0.7",
"eventemitter3": "^3.0.0",
"dompurify": "^2.0.3",
"eventemitter3": "^4.0.0",
"json-pointer": "^0.6.0",
"json-schema-ref-parser": "^6.0.1",
"lunr": "^2.3.2",
"json-schema-ref-parser": "^6.1.0",
"lunr": "2.3.6",
"mark.js": "^8.11.1",
"marked": "https://github.com/markedjs/marked#fb48827",
"memoize-one": "^4.0.0",
"mobx-react": "^5.2.5",
"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": "^2.0.2",
"prismjs": "^1.15.0",
"prop-types": "^15.6.2",
"react-dropdown": "^1.6.2",
"react-hot-loader": "^4.3.5",
"react-tabs": "^2.0.0",
"slugify": "^1.3.1",
"polished": "^3.4.1",
"prismjs": "^1.17.1",
"prop-types": "^15.7.2",
"react-dropdown": "^1.6.4",
"react-hot-loader": "^4.12.14",
"react-tabs": "^3.0.0",
"slugify": "^1.3.5",
"stickyfill": "^1.1.1",
"tslib": "^1.9.3"
"swagger2openapi": "^5.3.1",
"tslib": "^1.10.0",
"url-template": "^2.0.8"
},
"bundlesize": [
{
@ -161,7 +165,9 @@
}
],
"jest": {
"setupTestFrameworkScriptFile": "<rootDir>/src/setupTests.ts",
"setupFilesAfterEnv": [
"<rootDir>/src/setupTests.ts"
],
"preset": "ts-jest",
"collectCoverageFrom": [
"src/**/*.{ts,tsx}"

View File

@ -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<

View File

@ -19,6 +19,7 @@ const Tip = styled.div`
border-radius: 4px;
padding: 0.3em 0.6em;
text-align: center;
box-shadow: 0px 0px 5px 0px rgba(204, 204, 204, 1);
`;
const Content = styled.div`

View File

@ -1,7 +1,6 @@
import Dropdown from 'react-dropdown';
import { StyledComponentClass } from 'styled-components';
import styled, { withProps } from '../styled-components';
import styled from '../styled-components';
export interface DropdownOption {
label: string;
@ -14,17 +13,17 @@ export interface DropdownProps {
onChange: (val: DropdownOption) => void;
}
export const StyledDropdown = withProps<DropdownProps>(styled(Dropdown))`
export const StyledDropdown = styled(Dropdown)`
min-width: 100px;
display: inline-block;
position: relative;
width: auto;
font-family: ${props => props.theme.typography.headings.fontFamily};
.Dropdown-control {
.Dropdown-control {
font-family: ${props => props.theme.typography.headings.fontFamily};
position: relative;
font-size: .929em;
font-size: 0.929em;
width: 100%;
line-height: 1.5em;
vertical-align: middle;
@ -39,6 +38,8 @@ export const StyledDropdown = withProps<DropdownProps>(styled(Dropdown))`
margin-top: 5px;
background: white;
box-sizing: border-box;
&:hover {
border-color: ${props => props.theme.colors.primary.main};
color: ${props => props.theme.colors.primary.main};
@ -54,7 +55,7 @@ export const StyledDropdown = withProps<DropdownProps>(styled(Dropdown))`
display: block;
height: 0;
position: absolute;
right: 0.35em;
right: 0.6em;
top: 50%;
margin-top: -0.125em;
width: 0;
@ -84,14 +85,14 @@ export const StyledDropdown = withProps<DropdownProps>(styled(Dropdown))`
padding: 0.4em;
&.is-selected {
background-color: rgba(0, 0, 0, 0.05)
background-color: rgba(0, 0, 0, 0.05);
}
&:hover {
background-color: rgba(38, 50, 56, 0.12)
background-color: rgba(38, 50, 56, 0.12);
}
}
` as StyledComponentClass<any, DropdownProps>;
`;
export const SimpleDropdown = styled(StyledDropdown)`
margin-left: 10px;
@ -105,7 +106,7 @@ export const SimpleDropdown = styled(StyledDropdown)`
background: transparent;
&:hover {
color: ${props => props.theme.colors.main};
color: ${props => props.theme.colors.primary.main};
box-shadow: none;
}
}

View File

@ -1,6 +1,6 @@
// import { transparentize } from 'polished';
import styled, { extensionsHook, withProps } from '../styled-components';
import styled, { extensionsHook } from '../styled-components';
import { deprecatedCss } from './mixins';
export const PropertiesTableCaption = styled.caption`
@ -10,7 +10,7 @@ export const PropertiesTableCaption = styled.caption`
color: ${props => props.theme.colors.text.secondary};
`;
export const PropertyCell = styled.td`
export const PropertyCell = styled.td<{ kind?: string }>`
border-left: 1px solid ${props => props.theme.schema.linesColor};
box-sizing: border-box;
position: relative;
@ -58,7 +58,7 @@ export const PropertyCellWithInner = styled(PropertyCell)`
padding: 0;
`;
export const PropertyNameCell = withProps<{ kind?: string }>(styled(PropertyCell))`
export const PropertyNameCell = styled(PropertyCell)`
vertical-align: top;
line-height: 20px;
white-space: nowrap;

View File

@ -18,7 +18,7 @@ export const ClickablePropertyNameCell = styled(PropertyNameCell)`
export const FieldLabel = styled.span`
vertical-align: middle;
font-size: 0.929em;
font-size: ${({ theme }) => theme.typography.code.fontSize};
line-height: 20px;
`;
@ -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;
@ -58,7 +59,6 @@ export const PatternLabel = styled(FieldLabel)`
color: #3195a6;
&::before,
&::after {
content: '/';
font-weight: bold;
}
`;
@ -69,9 +69,10 @@ 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;
@ -79,6 +80,8 @@ export const ExampleValue = styled(FieldLabel)`
${extensionsHook('ExampleValue')};
`;
export const ExtensionValue = styled(ExampleValue)``;
export const ConstraintItem = styled(FieldLabel)`
border-radius: 2px;
${({ theme }) => `

View File

@ -7,14 +7,15 @@ const headerFontSize = {
};
export const headerCommonMixin = level => css`
font-family: ${props => props.theme.typography.headings.fontFamily};
font-weight: 400;
font-family: ${({ theme }) => theme.typography.headings.fontFamily};
font-weight: ${({ theme }) => theme.typography.headings.fontWeight};
font-size: ${headerFontSize[level]};
line-height: ${({ theme }) => theme.typography.headings.lineHeight};
`;
export const H1 = styled.h1`
${headerCommonMixin(1)};
color: ${props => props.theme.colors.primary.main};
color: ${({ theme }) => theme.colors.primary.main};
${extensionsHook('H1')};
`;

View File

@ -1,5 +1,5 @@
import { SECTION_ATTR } from '../services/MenuStore';
import styled, { media, withProps } from '../styled-components';
import styled, { media } from '../styled-components';
export const MiddlePanel = styled.div`
width: calc(100% - ${props => props.theme.rightPanel.width});
@ -12,17 +12,23 @@ export const MiddlePanel = styled.div`
`};
`;
export const Section = withProps<{ underlined?: boolean }>(
styled.div.attrs({
[SECTION_ATTR]: props => props.id,
} as any),
)`
export const Section = styled.div.attrs(props => ({
[SECTION_ATTR]: props.id,
}))<{ underlined?: boolean }>`
padding: ${props => props.theme.spacing.sectionVertical}px 0;
&:last-child {
min-height: calc(100vh + 1px);
}
& > &:last-child {
min-height: initial;
}
${media.lessThan('medium', true)`
padding: 0;
`}
${props =>
${(props: any) =>
(props.underlined &&
`
position: relative;
@ -41,7 +47,7 @@ export const Section = withProps<{ underlined?: boolean }>(
export const RightPanel = styled.div`
width: ${props => props.theme.rightPanel.width};
color: #fafbfc;
color: ${({ theme }) => theme.rightPanel.textColor};
background-color: ${props => props.theme.rightPanel.backgroundColor};
padding: 0 ${props => props.theme.spacing.sectionHorizontal}px;

View File

@ -1,4 +1,5 @@
import styled from '../styled-components';
import { PrismDiv } from './PrismDiv';
export const SampleControls = styled.div`
opacity: 0.4;
@ -21,3 +22,12 @@ export const SampleControlsWrap = styled.div`
opacity: 1;
}
`;
export const StyledPre = styled(PrismDiv.withComponent('pre'))`
font-family: ${props => props.theme.typography.code.fontFamily};
font-size: ${props => props.theme.typography.code.fontSize};
overflow-x: auto;
margin: 0;
white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
`;

View File

@ -1,7 +1,7 @@
import styled, { withProps } from '../styled-components';
import styled from '../styled-components';
export const OneOfList = styled.ul`
margin: 0;
margin: 0 0 3px 0;
padding: 0;
list-style: none;
display: inline-block;
@ -15,7 +15,7 @@ export const OneOfLabel = styled.span`
}
`;
export const OneOfButton = withProps<{ active: boolean }>(styled.li)`
export const OneOfButton = styled.li<{ active: boolean }>`
display: inline-block;
margin-right: 10px;
font-size: 0.8em;

View File

@ -1,5 +1,5 @@
import * as React from 'react';
import styled, { withProps } from '../styled-components';
import styled from '../styled-components';
const directionMap = {
left: '90deg',
@ -48,7 +48,7 @@ export const ShelfIcon = styled(IntShelfIcon)`
}
`;
export const Badge = withProps<{ type: string }>(styled.span)`
export const Badge = styled.span<{ type: string }>`
display: inline-block;
padding: 0 5px;
margin: 0;

View File

@ -1,4 +1,6 @@
import { darken } from 'polished';
import { Tabs as ReactTabs } from 'react-tabs';
import styled from '../styled-components';
export { Tab, TabList, TabPanel } from 'react-tabs';
@ -14,14 +16,15 @@ export const Tabs = styled(ReactTabs)`
padding: 5px 10px;
display: inline-block;
background-color: rgba(0, 0, 0, 0.2);
background-color: ${({ theme }) => theme.codeSample.backgroundColor};
border-bottom: 1px solid rgba(0, 0, 0, 0.5);
cursor: pointer;
text-align: center;
outline: none;
color: #ccc;
margin: 5px;
border: 1px solid #181f22;
color: ${({ theme }) => darken(theme.colors.tonalOffset, theme.rightPanel.textColor)};
margin: 0
${({ theme }) => `${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px`};
border: 1px solid ${({ theme }) => darken(0.05, theme.codeSample.backgroundColor)};
border-radius: 5px;
min-width: 60px;
font-size: 0.9em;
@ -29,7 +32,7 @@ export const Tabs = styled(ReactTabs)`
&.react-tabs__tab--selected {
color: ${props => props.theme.colors.text.primary};
background: #e2e2e2;
background: ${({ theme }) => theme.rightPanel.textColor};
}
&:only-child {
@ -55,12 +58,16 @@ export const Tabs = styled(ReactTabs)`
}
}
> .react-tabs__tab-panel {
background: #171e21;
background: ${({ theme }) => theme.codeSample.backgroundColor};
& > div,
& > pre {
padding: 20px;
padding: ${props => props.theme.spacing.unit * 4}px;
margin: 0;
}
& > div > pre {
padding: 0;
}
}
`;
@ -74,7 +81,7 @@ export const SmallTabs = styled(Tabs)`
font-size: 13px;
font-weight: normal;
border-bottom: 1px dashed;
color: #787b7d;
color: ${({ theme }) => darken(theme.colors.tonalOffset, theme.rightPanel.textColor)};
border-radius: 0;
background: none;
@ -83,7 +90,7 @@ export const SmallTabs = styled(Tabs)`
}
&.react-tabs__tab--selected {
color: #babcbf;
color: ${({ theme }) => theme.rightPanel.textColor};
background: none;
}
}
@ -91,8 +98,7 @@ export const SmallTabs = styled(Tabs)`
> .react-tabs__tab-panel {
& > div,
& > pre {
padding: 10px 0;
margin: 0;
padding: ${props => props.theme.spacing.unit * 2}px 0;
}
}
`;

View File

@ -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,12 +68,14 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
)) ||
null;
const version = (info.version && <span>({info.version})</span>) || null;
return (
<Section>
<Row>
<MiddlePanel className="api-info">
<ApiHeader>
{info.title} <span>({info.version})</span>
{info.title} {version}
</ApiHeader>
{!hideDownloadButton && (
<p>

View File

@ -12,19 +12,15 @@ export class ApiLogo extends React.Component<{ info: OpenAPIInfo }> {
return null;
}
const logoHref = logoInfo.href || (info.contact && info.contact.url);
// 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>
{info.contact && info.contact.url ? LinkWrap(info.contact.url)(logo) : logo}{' '}
<LogoWrap style={{ backgroundColor: logoInfo.backgroundColor }}>
{logoHref ? LinkWrap(logoHref)(logo) : logo}
</LogoWrap>
);
}

View File

@ -4,6 +4,7 @@ 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;
`;

View File

@ -1,10 +1,11 @@
import * as React from 'react';
import { ShelfIcon } from '../../common-elements';
import { OperationModel } from '../../services';
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,
@ -59,21 +60,26 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
/>
</EndpointInfo>
<ServersOverlay expanded={expanded}>
{operation.servers.map(server => (
<ServerItem key={server.url}>
<div>{server.description}</div>
<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>
)}

View File

@ -1,4 +1,4 @@
import styled, { withProps } from '../../styled-components';
import styled from '../../styled-components';
export const OperationEndpointWrap = styled.div`
cursor: pointer;
@ -7,17 +7,18 @@ export const OperationEndpointWrap = styled.div`
`;
export const ServerRelativeURL = styled.span`
font-family: ${props => props.theme.typography.headings.fontFamily};
font-family: ${props => props.theme.typography.code.fontFamily};
margin-left: 10px;
flex: 1;
overflow-x: hidden;
text-overflow: ellipsis;
`;
export const EndpointInfo = withProps<{ expanded?: boolean; inverted?: boolean }>(styled.div)`
export const EndpointInfo = styled.div<{ expanded?: boolean; inverted?: boolean }>`
padding: 10px 30px 10px ${props => (props.inverted ? '10px' : '20px')};
border-radius: ${props => (props.inverted ? '0' : '4px 4px 0 0')};
background-color: ${props => (props.inverted ? 'transparent' : '#222d32')};
background-color: ${props =>
props.inverted ? 'transparent' : props.theme.codeSample.backgroundColor};
display: flex;
white-space: nowrap;
align-items: center;
@ -33,12 +34,12 @@ export const EndpointInfo = withProps<{ expanded?: boolean; inverted?: boolean }
}
`;
export const HttpVerb = withProps<{ type: string }>(styled.span).attrs({
className: props => `http-verb ${props.type}`,
})`
export const HttpVerb = styled.span.attrs((props: { type: string }) => ({
className: `http-verb ${props.type}`,
}))<{ type: string }>`
font-size: 0.929em;
line-height: 20px;
background-color: ${props => props.theme.colors.http[props.type] || '#999999'};
background-color: ${(props: any) => props.theme.colors.http[props.type] || '#999999'};
color: #ffffff;
padding: 3px 10px;
text-transform: uppercase;
@ -46,7 +47,7 @@ export const HttpVerb = withProps<{ type: string }>(styled.span).attrs({
margin: 0;
`;
export const ServersOverlay = withProps<{ expanded: boolean }>(styled.div)`
export const ServersOverlay = styled.div<{ expanded: boolean }>`
position: absolute;
width: 100%;
z-index: 100;

View File

@ -1,10 +1,10 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import styled, { withProps } from '../../styled-components';
import styled from '../../styled-components';
import { OpenAPIExternalDocumentation } from '../../types';
import { linksCss } from '../Markdown/styled.elements';
const LinkWrap = withProps<{ compact?: boolean }>(styled.div)`
const LinkWrap = styled.div<{ compact?: boolean }>`
${linksCss};
${({ compact }) => (!compact ? 'margin: 1em 0' : '')}
`;

View File

@ -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>
);
}

View File

@ -1,4 +1,7 @@
import * as React from 'react';
import { ExtensionValue, FieldLabel } from '../../common-elements/fields';
import styled from '../../styled-components';
import { OptionsContext } from '../OptionsProvider';
@ -6,14 +9,9 @@ import { OptionsContext } from '../OptionsProvider';
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
const Extension = styled(StyledMarkdownBlock)`
opacity: 0.9;
margin: 2px 0;
`;
const ExtensionLable = styled.span`
font-style: italic;
`;
export interface ExtensionsProps {
extensions: {
[k: string]: any;
@ -22,15 +20,18 @@ export interface ExtensionsProps {
export class Extensions extends React.PureComponent<ExtensionsProps> {
render() {
const exts = this.props.extensions;
return (
<OptionsContext.Consumer>
{options => (
<>
{options.showExtensions &&
Object.keys(this.props.extensions).map(key => (
Object.keys(exts).map(key => (
<Extension key={key}>
<ExtensionLable>{key}</ExtensionLable>:{' '}
<code>{JSON.stringify(this.props.extensions[key])}</code>
<FieldLabel> {key.substring(2)}: </FieldLabel>{' '}
<ExtensionValue>
{typeof exts[key] === 'string' ? exts[key] : JSON.stringify(exts[key])}
</ExtensionValue>
</Extension>
))}
</>

View File

@ -65,21 +65,20 @@ export class Field extends React.Component<FieldProps> {
<FieldDetails {...this.props} />
</PropertyDetailsCell>
</tr>
{field.expanded &&
withSubSchema && (
<tr key={field.name + 'inner'}>
<PropertyCellWithInner colSpan={2}>
<InnerPropertiesWrap>
<Schema
schema={field.schema}
skipReadOnly={this.props.skipReadOnly}
skipWriteOnly={this.props.skipWriteOnly}
showTitle={this.props.showTitle}
/>
</InnerPropertiesWrap>
</PropertyCellWithInner>
</tr>
)}
{field.expanded && withSubSchema && (
<tr key={field.name + 'inner'}>
<PropertyCellWithInner colSpan={2}>
<InnerPropertiesWrap>
<Schema
schema={field.schema}
skipReadOnly={this.props.skipReadOnly}
skipWriteOnly={this.props.skipWriteOnly}
showTitle={this.props.showTitle}
/>
</InnerPropertiesWrap>
</PropertyCellWithInner>
</tr>
)}
</>
);
}

View File

@ -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>
);
}

View File

@ -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,29 @@ 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;
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 +58,18 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
)}
{schema.title && <TypeTitle> ({schema.title}) </TypeTitle>}
<ConstraintsView constraints={schema.constraints} />
{schema.nullable && <NullableLabel> Nullable </NullableLabel>}
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>}
{schema.pattern && <PatternLabel>{schema.pattern}</PatternLabel>}
{schema.isCircular && <RecursiveLabel> Recursive </RecursiveLabel>}
{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={enumSkipQuotes} 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} />

View File

@ -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>
);

View File

@ -1,9 +1,9 @@
import * as React from 'react';
import styled, { withProps } from '../../styled-components';
import styled from '../../styled-components';
import { Spinner } from './Spinner.svg';
const LoadingMessage = withProps<{ color: string }>(styled.div)`
const LoadingMessage = styled.div<{ color: string }>`
font-family: helvetica, sans;
width: 100%;
text-align: center;

View File

@ -1,6 +1,8 @@
import { headerCommonMixin, linkifyMixin } from '../../common-elements';
import { PrismDiv } from '../../common-elements/PrismDiv';
import styled, { css, extensionsHook, withProps } from '../../styled-components';
import styled, { css, extensionsHook, ResolvedThemeInterface } from '../../styled-components';
import { StyledComponent } from 'styled-components';
export const linksCss = css`
a {
@ -17,9 +19,11 @@ export const linksCss = css`
}
`;
export const StyledMarkdownBlock = withProps<{ compact?: boolean; inline?: boolean }>(
styled(PrismDiv),
)`
export const StyledMarkdownBlock = styled(PrismDiv as StyledComponent<
'div',
ResolvedThemeInterface,
{ compact?: boolean; inline?: boolean }
>)`
font-family: ${props => props.theme.typography.fontFamily};
font-weight: ${props => props.theme.typography.fontWeightRegular};
@ -66,8 +70,9 @@ export const StyledMarkdownBlock = withProps<{ compact?: boolean; inline?: boole
font-family: ${props => props.theme.typography.code.fontFamily};
border-radius: 2px;
border: 1px solid rgba(38, 50, 56, 0.1);
padding: 0.1em 0.25em 0.2em;
padding: 0 ${({ theme }) => theme.spacing.unit}px;
font-size: ${props => props.theme.typography.code.fontSize};
font-weight: ${({ theme }) => theme.typography.code.fontWeight};
word-break: break-word;
}
@ -77,7 +82,7 @@ export const StyledMarkdownBlock = withProps<{ compact?: boolean; inline?: boole
white-space:${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
background-color: #263238;
color: white;
padding: 12px 14px 15px 14px;
padding: ${props => props.theme.spacing.unit * 4}px;
overflow-x: auto;
line-height: normal;
border-radius: 0px
@ -113,9 +118,11 @@ export const StyledMarkdownBlock = withProps<{ compact?: boolean; inline?: boole
padding-left: 2em;
margin: 0;
margin-bottom: 1em;
// > li {
// margin: 0.5em 0;
// }
ul, ol {
margin-bottom: 0;
margin-top: 0;
}
}
table {
@ -135,7 +142,7 @@ export const StyledMarkdownBlock = withProps<{ compact?: boolean; inline?: boole
border-top: 1px solid #ccc;
&:nth-child(2n) {
background-color: #f8f8f8;
background-color: ${({ theme }) => theme.schema.nestedBackground};
}
}

View File

@ -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)}
</>
);

View File

@ -4,11 +4,12 @@ import { ParametersGroup } from './ParametersGroup';
import { UnderlinedHeader } from '../../common-elements';
import { MediaContentModel } from '../../services';
import { FieldModel, RequestBodyModel } from '../../services/models';
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema';
import { MediaContentModel } from '../../services';
import { Markdown } from '../Markdown/Markdown';
function safePush(obj, prop, item) {
if (!obj[prop]) {
@ -45,13 +46,15 @@ export class Parameters extends React.PureComponent<ParametersProps> {
const bodyContent = body && body.content;
const bodyDescription = body && body.description;
return (
<div>
<>
{paramsPlaces.map(place => (
<ParametersGroup key={place} place={place} parameters={paramsMap[place]} />
))}
{bodyContent && <BodyContent content={bodyContent} />}
</div>
{bodyContent && <BodyContent content={bodyContent} description={bodyDescription} />}
</>
);
}
}
@ -64,12 +67,17 @@ function DropdownWithinHeader(props) {
);
}
function BodyContent(props: { content: MediaContentModel }): JSX.Element {
const { content } = props;
function BodyContent(props: { content: MediaContentModel; description?: string }): JSX.Element {
const { content, description } = props;
return (
<MediaTypesSwitch content={content} renderDropdown={DropdownWithinHeader}>
{({ schema }) => {
return <Schema skipReadOnly={true} key="schema" schema={schema} />;
return (
<>
{description !== undefined && <Markdown source={description} />}
<Schema skipReadOnly={true} key="schema" schema={schema} />
</>
);
}}
</MediaTypesSwitch>
);

View File

@ -0,0 +1,40 @@
import * as React from 'react';
import { StyledPre } from '../../common-elements/samples';
import { ExampleModel } from '../../services/models';
import { ExampleValue } from './ExampleValue';
import { useExternalExample } from './exernalExampleHook';
export interface ExampleProps {
example: ExampleModel;
mimeType: string;
}
export function Example({ example, mimeType }: ExampleProps) {
if (example.value === undefined && example.externalValueUrl) {
return <ExternalExample example={example} mimeType={mimeType} />;
} else {
return <ExampleValue value={example.value} mimeType={mimeType} />;
}
}
export function ExternalExample({ example, mimeType }: ExampleProps) {
const value = useExternalExample(example, mimeType);
if (value === undefined) {
return <span>Loading...</span>;
}
if (value instanceof Error) {
return (
<StyledPre>
Error loading external example: <br />
<a className={'token string'} href={example.externalValueUrl} target="_blank">
{example.externalValueUrl}
</a>
</StyledPre>
);
}
return <ExampleValue value={value} mimeType={mimeType} />;
}

View File

@ -0,0 +1,22 @@
import * as React from 'react';
import { isJsonLike, langFromMime } from '../../utils/openapi';
import { JsonViewer } from '../JsonViewer/JsonViewer';
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
export interface ExampleValueProps {
value: any;
mimeType: string;
}
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} />;
}
}

View File

@ -1,51 +1,82 @@
import * as React from 'react';
import { SmallTabs, Tab, TabList, TabPanel } from '../../common-elements';
import { MediaTypeModel } from '../../services/models';
import { JsonViewer } from '../JsonViewer/JsonViewer';
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
import { NoSampleLabel } from './styled.elements';
import styled from '../../styled-components';
import { isJsonLike, langFromMime } from '../../utils';
import { DropdownProps } from '../../common-elements';
import { MediaTypeModel } from '../../services/models';
import { Markdown } from '../Markdown/Markdown';
import { Example } from './Example';
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;
const noSample = <NoSampleLabel>No sample</NoSampleLabel>;
const sampleView = isJsonLike(mimeType)
? sample => <JsonViewer data={sample} />
: sample =>
(sample !== undefined && (
<SourceCodeWithCopy lang={langFromMime(mimeType)} source={sample} />
)) ||
noSample;
const examplesNames = Object.keys(examples);
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>
<TabList>
{examplesNames.map(name => (
<Tab key={name}> {examples[name].summary || name} </Tab>
))}
</TabList>
{examplesNames.map(name => (
<TabPanel key={name}>{sampleView(examples[name].value)}</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];
return <div>{sampleView(examples[name].value)}</div>;
const example = examples[examplesNames[0]];
return (
<SamplesWrapper>
{example.description && <Markdown source={example.description} />}
<Example example={example} mimeType={mimeType} />
</SamplesWrapper>
);
}
}
}
const SamplesWrapper = styled.div`
margin-top: 15px;
`;

View File

@ -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>
);
}

View File

@ -0,0 +1,31 @@
import { useEffect, useRef, useState } from 'react';
import { ExampleModel } from '../../services/models/Example';
export function useExternalExample(example: ExampleModel, mimeType: string) {
const [, setIsLoading] = useState(true); // to trigger component reload
const value = useRef<any>(undefined);
const prevRef = useRef<ExampleModel | undefined>(undefined);
if (prevRef.current !== example) {
value.current = undefined;
}
prevRef.current = example;
useEffect(() => {
const load = async () => {
setIsLoading(true);
try {
value.current = await example.getExternalValue(mimeType);
} catch (e) {
value.current = e;
}
setIsLoading(false);
};
load();
}, [example, mimeType]);
return value.current;
}

View File

@ -1,37 +1,59 @@
// @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`
border-bottom: 1px solid rgba(255, 255, 255, 0.9);
padding: 12px;
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
margin: 0 0 10px 0;
display: block;
color: rgba(255, 255, 255, 0.8);
`;
export const DropdownLabel = styled.span`
font-family: ${({ theme }) => theme.typography.headings.fontFamily};
font-size: 12px;
position: absolute;
z-index: 1;
top: -11px;
left: 12px;
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
color: ${({ theme }) => transparentize(0.6, theme.rightPanel.textColor)};
`;
export const DropdownWrapper = styled.div`
position: relative;
`;
export const InvertedSimpleDropdown = styled(StyledDropdown)`
margin-left: 10px;
text-transform: none;
font-size: 0.929em;
border-bottom: 1px solid rgba(255, 255, 255, 0.9);
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: rgba(255, 255, 255, 0.9);
color: ${({ theme }) => theme.rightPanel.textColor};
box-shadow: none;
.Dropdown-arrow {
border-top-color: rgba(255, 255, 255, 0.9);
border-top-color: ${({ theme }) => theme.rightPanel.textColor};
}
}
.Dropdown-menu {
margin: 0;
margin-top: 2px;
}
`;

View File

@ -43,7 +43,14 @@ export const BackgroundStub = styled.div`
top: 0;
bottom: 0;
right: 0;
width: calc((100% - ${({ theme }) => theme.menu.width}) * 0.4);
width: ${({ theme }) => {
if (theme.rightPanel.width.endsWith('%')) {
const percents = parseInt(theme.rightPanel.width, 10);
return `calc((100% - ${theme.menu.width}) * ${percents / 100})`;
} else {
return theme.rightPanel.width;
}
}};
${media.lessThan('medium', true)`
display: none;
`};

View File

@ -1,10 +1,11 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { OperationModel } from '../../services/models';
import { OperationModel, RedocNormalizedOptions } from '../../services';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements';
import { OptionsContext } from '../OptionsProvider';
export interface RequestSamplesProps {
operation: OperationModel;
@ -12,6 +13,8 @@ export interface RequestSamplesProps {
@observer
export class RequestSamples extends React.Component<RequestSamplesProps> {
static contextType = OptionsContext;
context: RedocNormalizedOptions;
operation: OperationModel;
render() {
@ -21,16 +24,20 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
const samples = operation.codeSamples;
const hasSamples = hasBodySample || samples.length > 0;
const hideTabList =
samples.length + (hasBodySample ? 1 : 0) === 1
? this.context.hideSingleRequestSampleTab
: false;
return (
(hasSamples && (
<div>
<RightPanelHeader> Request samples </RightPanelHeader>
<Tabs defaultIndex={0}>
<TabList>
<TabList hidden={hideTabList}>
{hasBodySample && <Tab key="payload"> Payload </Tab>}
{samples.map(sample => (
<Tab key={sample.lang}>
<Tab key={sample.lang + '_' + (sample.label || '')}>
{sample.label !== undefined ? sample.label : sample.lang}
</Tab>
))}

View File

@ -28,12 +28,11 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
code={code}
opened={expanded}
/>
{expanded &&
!empty && (
<ResponseDetailsWrap>
<ResponseDetails response={this.props.response} />
</ResponseDetailsWrap>
)}
{expanded && !empty && (
<ResponseDetailsWrap>
<ResponseDetails response={this.props.response} />
</ResponseDetailsWrap>
)}
</div>
);
}

View File

@ -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'}
/>
)}

View File

@ -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;

View File

@ -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

View 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;
}
`;

View File

@ -1,5 +1,6 @@
import * as React from 'react';
import { darken } from 'polished';
import styled from '../../styled-components';
import { MenuItemLabel } from '../SideMenu/styled.elements';
@ -7,19 +8,20 @@ export const SearchWrap = styled.div`
padding: 5px 0;
`;
export const SearchInput = styled.input.attrs({
export const SearchInput = styled.input.attrs(() => ({
className: 'search-input',
})`
}))`
width: calc(100% - ${props => props.theme.spacing.unit * 8}px);
box-sizing: border-box;
margin: 0 ${props => props.theme.spacing.unit * 4}px;
padding: 5px ${props => props.theme.spacing.unit * 2}px 5px
${props => props.theme.spacing.unit * 4}px;
border: 0;
border-bottom: 1px solid #e1e1e1;
border-bottom: 1px solid ${({ theme }) => darken(0.1, theme.menu.backgroundColor)};
font-family: ${({ theme }) => theme.typography.fontFamily};
font-weight: bold;
font-size: 13px;
color: ${props => props.theme.colors.text};
color: ${props => props.theme.menu.textColor};
background-color: transparent;
outline: none;
`;
@ -44,7 +46,7 @@ export const SearchIcon = styled((props: { className?: string }) => (
width: 0.9em;
path {
fill: ${props => props.theme.colors.text};
fill: ${props => props.theme.menu.textColor};
}
`;

View File

@ -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';
@ -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>

View File

@ -57,22 +57,19 @@ export class MenuItem extends React.Component<MenuItemProps> {
{item.name}
{this.props.children}
</MenuItemTitle>
{(item.depth > 0 &&
item.items.length > 0 && (
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
)) ||
{(item.depth > 0 && item.items.length > 0 && (
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
)) ||
null}
</MenuItemLabel>
)}
{!withoutChildren &&
item.items &&
item.items.length > 0 && (
<MenuItems
expanded={item.expanded}
items={item.items}
onActivate={this.props.onActivate}
/>
)}
{!withoutChildren && item.items && item.items.length > 0 && (
<MenuItems
expanded={item.expanded}
items={item.items}
onActivate={this.props.onActivate}
/>
)}
</MenuItemLi>
);
}
@ -83,7 +80,7 @@ export interface OperationMenuItemContentProps {
}
@observer
class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
export class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
render() {
const { item } = this.props;
return (

View File

@ -2,6 +2,7 @@ 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';
@ -9,6 +10,7 @@ import { RedocAttribution } from './styled.elements';
@observer
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
static contextType = OptionsContext;
private _updateScroll?: () => void;
render() {
@ -23,7 +25,7 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
>
<MenuItems items={store.items} onActivate={this.activate} root={true} />
<RedocAttribution>
<a target="_blank" href="https://github.com/Rebilly/ReDoc">
<a target="_blank" href="https://github.com/Redocly/redoc">
Documentation Powered by ReDoc
</a>
</RedocAttribution>
@ -32,6 +34,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) {

View File

@ -1,11 +1,12 @@
import * as classnames from 'classnames';
import { darken } from 'polished';
import { deprecatedCss, ShelfIcon } from '../../common-elements';
import styled, { css, withProps } from '../../styled-components';
import styled, { css } from '../../styled-components';
export const OperationBadge = withProps<{ type: string }>(styled.span).attrs({
className: props => `operation-type ${props.type}`,
})`
export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
className: `operation-type ${props.type}`,
}))<{ type: string }>`
width: 32px;
display: inline-block;
height: ${props => props.theme.typography.code.fontSize};
@ -57,21 +58,21 @@ export const OperationBadge = withProps<{ type: string }>(styled.span).attrs({
}
&.head {
background-color: ${props => props.theme.colors.http.head};
background-color: ${props => props.theme.colors.http.head};
}
`;
function menuItemActiveBg(depth): string {
function menuItemActiveBg(depth, { theme }): string {
if (depth > 1) {
return '#e1e1e1';
return darken(0.1, theme.menu.backgroundColor);
} else if (depth === 1) {
return '#f0f0f0';
return darken(0.05, theme.menu.backgroundColor);
} else {
return '';
}
}
export const MenuItemUl = withProps<{ expanded: boolean }>(styled.ul)`
export const MenuItemUl = styled.ul<{ expanded: boolean }>`
margin: 0;
padding: 0;
@ -82,7 +83,7 @@ export const MenuItemUl = withProps<{ expanded: boolean }>(styled.ul)`
${props => (props.expanded ? '' : 'display: none;')};
`;
export const MenuItemLi = withProps<{ depth: number }>(styled.li)`
export const MenuItemLi = styled.li<{ depth: number }>`
list-style: none inside none;
overflow: hidden;
text-overflow: ellipsis;
@ -97,7 +98,7 @@ export const menuItemDepth = {
font-size: 0.8em;
padding-bottom: 0;
cursor: default;
color: ${props => props.theme.colors.text.primary};
color: ${props => props.theme.menu.textColor};
`,
1: css`
font-size: 0.929em;
@ -107,25 +108,25 @@ export const menuItemDepth = {
}
`,
2: css`
color: ${props => props.theme.colors.text.primary};
color: ${props => props.theme.menu.textColor};
`,
};
export const MenuItemLabel = withProps<{
export interface MenuItemLabelType {
depth: number;
active: boolean;
deprecated?: boolean;
type?: string;
}>(styled.label).attrs({
}
export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
role: 'menuitem',
className: props =>
classnames('-depth' + props.depth, {
active: props.active,
}),
})`
className: classnames('-depth' + props.depth, {
active: props.active,
}),
}))<MenuItemLabelType>`
cursor: pointer;
color: ${props =>
props.active ? props.theme.colors.primary.main : props.theme.colors.text.primary};
color: ${props => (props.active ? props.theme.colors.primary.main : props.theme.menu.textColor)};
margin: 0;
padding: 12.5px ${props => props.theme.spacing.unit * 4}px;
${({ depth, type, theme }) =>
@ -134,12 +135,12 @@ export const MenuItemLabel = withProps<{
justify-content: space-between;
font-family: ${props => props.theme.typography.headings.fontFamily};
${props => menuItemDepth[props.depth]};
background-color: ${props => (props.active ? menuItemActiveBg(props.depth) : '')};
background-color: ${props => (props.active ? menuItemActiveBg(props.depth, props) : '')};
${props => (props.deprecated && deprecatedCss) || ''};
&:hover {
background-color: ${props => menuItemActiveBg(props.depth)};
background-color: ${props => menuItemActiveBg(props.depth, props)};
}
${ShelfIcon} {
@ -151,7 +152,7 @@ export const MenuItemLabel = withProps<{
}
`;
export const MenuItemTitle = withProps<{ width?: string }>(styled.span)`
export const MenuItemTitle = styled.span<{ width?: string }>`
display: inline-block;
vertical-align: middle;
width: ${props => (props.width ? props.width : 'auto')};
@ -171,8 +172,8 @@ export const RedocAttribution = styled.div`
a,
a:visited,
a:hover {
color: ${theme.colors.text.primary} !important;
border-top: 1px solid #e1e1e1;
color: ${theme.menu.textColor} !important;
border-top: 1px solid ${darken(0.1, theme.menu.backgroundColor)};
padding: ${theme.spacing.unit}px 0;
display: block;
}

View File

@ -1,19 +1,8 @@
import * as React from 'react';
import { highlight } from '../../utils';
import { SampleControls, SampleControlsWrap } from '../../common-elements';
import { SampleControls, SampleControlsWrap, StyledPre } from '../../common-elements';
import { CopyButtonWrapper } from '../../common-elements/CopyButtonWrapper';
import { PrismDiv } from '../../common-elements/PrismDiv';
import styled from '../../styled-components';
const StyledPre = styled(PrismDiv.withComponent('pre'))`
font-family: ${props => props.theme.typography.code.fontFamily};
font-size: ${props => props.theme.typography.code.fontSize};
overflow-x: auto;
margin: 0;
white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
`;
export interface SourceCodeProps {
source: string;

View File

@ -3,7 +3,7 @@ import * as React from 'react';
import { MenuStore } from '../../services/MenuStore';
import { RedocNormalizedOptions, RedocRawOptions } from '../../services/RedocNormalizedOptions';
import styled, { media, withProps } from '../../styled-components';
import styled, { media } from '../../styled-components';
import { IS_BROWSER } from '../../utils/index';
import { OptionsContext } from '../OptionsProvider';
import { AnimatedChevronButton } from './ChevronSvg';
@ -19,9 +19,13 @@ export interface StickySidebarProps {
menu: MenuStore;
}
export interface StickySidebarState {
offsetTop?: string;
}
const stickyfill = Stickyfill && Stickyfill();
const StyledStickySidebar = withProps<{ open?: boolean }>(styled.div)`
const StyledStickySidebar = styled.div<{ open?: boolean }>`
width: ${props => props.theme.menu.width};
background-color: ${props => props.theme.menu.backgroundColor};
overflow: hidden;
@ -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 = {};
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 || this.getScrollYOffset(this.context);
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 });
// };
}

View File

@ -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",

View File

@ -8,7 +8,6 @@ export * from './Schema/';
export * from './SearchBox/SearchBox';
export * from './Operation/Operation';
export * from './Loading/Loading';
export * from './RedocStandalone';
export * from './JsonViewer';
export * from './Markdown/Markdown';
export { StyledMarkdownBlock } from './Markdown/styled.elements';
@ -29,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';

View File

@ -4,7 +4,11 @@ import defaultTheme from '../theme';
export default class TestThemeProvider extends React.Component {
render() {
return <ThemeProvider theme={defaultTheme}>{this.props.children}</ThemeProvider>;
return (
<ThemeProvider theme={defaultTheme}>
{React.Children.only(this.props.children as any)}
</ThemeProvider>
);
}
}

View File

@ -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';

View File

@ -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,
}),
},
},
};

View File

@ -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();
}
}
}

View File

@ -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
View 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;
}

View File

@ -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, '\\$&')}`);
};
@ -118,7 +119,7 @@ export class MarkdownRenderer {
.trim();
}
headingRule = (text: string, level: number, raw: string) => {
headingRule = (text: string, level: number, raw: string, slugger: marked.Slugger) => {
if (level === 1) {
this.currentTopHeading = this.saveHeading(text, level);
} else if (level === 2) {
@ -129,7 +130,7 @@ export class MarkdownRenderer {
this.currentTopHeading && this.currentTopHeading.id,
);
}
return this.originalHeadingRule(text, level, raw);
return this.originalHeadingRule(text, level, raw, slugger);
};
renderMd(rawText: string, extractHeadings: boolean = false): string {

View File

@ -42,8 +42,8 @@ export class MenuBuilder {
const items: ContentItemModel[] = [];
const tagsMap = MenuBuilder.getTagsWithOperations(spec);
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', options));
if (spec['x-tagGroups']) {
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;

View File

@ -187,6 +187,7 @@ export class OpenAPIParser {
...schema,
allOf: undefined,
parentRefs: [],
title: schema.title || (isNamedDefinition($ref) ? JsonPointer.baseName($ref) : undefined),
};
// avoid mutating inner objects
@ -257,17 +258,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;
}
@ -291,6 +287,12 @@ export class OpenAPIParser {
return res;
}
exitParents(shema: MergedOpenAPISchema) {
for (const parent$ref of shema.parentRefs || []) {
this.exitRef({ $ref: parent$ref });
}
}
private hoistOneOfs(schema: OpenAPISchema) {
if (schema.allOf === undefined) {
return schema;
@ -304,9 +306,14 @@ export class OpenAPIParser {
const afterAllOf = allOf.slice(i + 1);
return {
oneOf: sub.oneOf.map(part => {
return this.mergeAllOf({
const merged = this.mergeAllOf({
allOf: [...beforeAllOf, part, ...afterAllOf],
});
// each oneOf should be independent so exiting all the parent refs
// otherwise it will cause false-positive recursive detection
this.exitParents(merged);
return merged;
}),
};
}

View File

@ -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 = 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);
}
}

View File

@ -66,7 +66,8 @@ export class ScrollService {
}
element.scrollIntoView();
if (this._scrollParent && this._scrollParent.scrollBy) {
(this._scrollParent.scrollBy as any)(0, -this.options.scrollYOffset());
// adding 1 account rounding errors in case scrollYOffset is float-number
(this._scrollParent.scrollBy as any)(0, -this.options.scrollYOffset() + 1);
}
}

View File

@ -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
}

View File

@ -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,
},
],
},

View File

@ -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": {

View File

@ -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,

View File

@ -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');
});
});
});

View File

@ -1,14 +1,64 @@
import { OpenAPIExample, Referenced } from '../../types';
import { resolve as urlResolve } from 'url';
import { OpenAPIEncoding, OpenAPIExample, Referenced } from '../../types';
import { isFormUrlEncoded, isJsonLike, urlFormEncodePayload } from '../../utils/openapi';
import { OpenAPIParser } from '../OpenAPIParser';
const externalExamplesCache: { [url: string]: Promise<any> } = {};
export class ExampleModel {
value: any;
summary?: string;
description?: string;
externalValue?: string;
externalValueUrl?: string;
constructor(parser: OpenAPIParser, infoOrRef: Referenced<OpenAPIExample>) {
Object.assign(this, parser.deref(infoOrRef));
constructor(
parser: OpenAPIParser,
infoOrRef: Referenced<OpenAPIExample>,
public mime: string,
encoding?: { [field: string]: OpenAPIEncoding },
) {
const example = parser.deref(infoOrRef);
this.value = example.value;
this.summary = example.summary;
this.description = example.description;
if (example.externalValue) {
this.externalValueUrl = urlResolve(parser.specUrl || '', example.externalValue);
}
parser.exitRef(infoOrRef);
if (isFormUrlEncoded(mime) && this.value && typeof this.value === 'object') {
this.value = urlFormEncodePayload(this.value, encoding);
}
}
getExternalValue(mimeType: string): Promise<any> {
if (!this.externalValueUrl) {
return Promise.resolve(undefined);
}
if (externalExamplesCache[this.externalValueUrl]) {
return externalExamplesCache[this.externalValueUrl];
}
externalExamplesCache[this.externalValueUrl] = fetch(this.externalValueUrl).then(res => {
return res.text().then(txt => {
if (!res.ok) {
return Promise.reject(new Error(txt));
}
if (isJsonLike(mimeType)) {
try {
return JSON.parse(txt);
} catch (e) {
return txt;
}
} else {
return txt;
}
});
});
return externalExamplesCache[this.externalValueUrl];
}
}

View File

@ -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);

View File

@ -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;

View File

@ -1,6 +1,6 @@
import * as Sampler from 'openapi-sampler';
import { OpenAPIExample, OpenAPIMediaType } from '../../types';
import { OpenAPIMediaType } from '../../types';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { SchemaModel } from './Schema';
@ -9,7 +9,7 @@ import { OpenAPIParser } from '../OpenAPIParser';
import { ExampleModel } from './Example';
export class MediaTypeModel {
examples?: { [name: string]: OpenAPIExample };
examples?: { [name: string]: ExampleModel };
schema?: SchemaModel;
name: string;
isRequestType: boolean;
@ -30,10 +30,18 @@ export class MediaTypeModel {
this.schema = info.schema && new SchemaModel(parser, info.schema, '', options);
this.onlyRequiredInSamples = options.onlyRequiredInSamples;
if (info.examples !== undefined) {
this.examples = mapValues(info.examples, example => new ExampleModel(parser, example));
this.examples = mapValues(
info.examples,
example => new ExampleModel(parser, example, name, info.encoding),
);
} else if (info.example !== undefined) {
this.examples = {
default: new ExampleModel(parser, { value: info.example }),
default: new ExampleModel(
parser,
{ value: parser.shalowDeref(info.example) },
name,
info.encoding,
),
};
} else if (isJsonLike(name)) {
this.generateExample(parser, info);
@ -49,29 +57,31 @@ export class MediaTypeModel {
if (this.schema && this.schema.oneOf) {
this.examples = {};
for (const subSchema of this.schema.oneOf) {
const sample = Sampler.sample(
subSchema.rawSchema,
samplerOptions,
parser.spec,
);
const sample = Sampler.sample(subSchema.rawSchema, samplerOptions, parser.spec);
if (this.schema.discriminatorProp && typeof sample === 'object' && sample) {
sample[this.schema.discriminatorProp] = subSchema.title;
}
this.examples[subSchema.title] = {
value: sample,
};
this.examples[subSchema.title] = new ExampleModel(
parser,
{
value: sample,
},
this.name,
info.encoding,
);
}
} else if (this.schema) {
this.examples = {
default: new ExampleModel(parser, {
value: Sampler.sample(
info.schema,
samplerOptions,
parser.spec,
),
}),
default: new ExampleModel(
parser,
{
value: Sampler.sample(info.schema, samplerOptions, parser.spec),
},
this.name,
info.encoding,
),
};
}
}

View File

@ -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;

View File

@ -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;
@ -75,11 +78,7 @@ export class SchemaModel {
this.init(parser, isChild);
parser.exitRef(schemaOrRef);
for (const parent$ref of this.schema.parentRefs || []) {
// exit all the refs visited during allOf traverse
parser.exitRef({ $ref: parent$ref });
}
parser.exitParents(this.schema);
if (options.showExtensions) {
this.extensions = extractExtensions(this.schema, options.showExtensions);
@ -149,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) {
@ -164,20 +163,38 @@ export class SchemaModel {
}
private initOneOf(oneOf: OpenAPISchema[], parser: OpenAPIParser) {
this.oneOf = oneOf!.map(
(variant, idx) =>
new SchemaModel(
parser,
// merge base schema into each of oneOf's subschemas
{
// variant may already have allOf so merge it to not get overwritten
...parser.mergeAllOf(variant, this.pointer + '/oneOf/' + idx),
allOf: [{ ...this.schema, oneOf: undefined, anyOf: undefined }],
} as OpenAPISchema,
this.pointer + '/oneOf/' + idx,
this.options,
),
);
this.oneOf = oneOf!.map((variant, idx) => {
const derefVariant = parser.deref(variant);
const merged = parser.mergeAllOf(derefVariant, this.pointer + '/oneOf/' + idx);
// try to infer title
const title =
isNamedDefinition(variant.$ref) && !merged.title
? JsonPointer.baseName(variant.$ref)
: merged.title;
const schema = new SchemaModel(
parser,
// merge base schema into each of oneOf's subschemas
{
// variant may already have allOf so merge it to not get overwritten
...merged,
title,
allOf: [{ ...this.schema, oneOf: undefined, anyOf: undefined }],
} as OpenAPISchema,
this.pointer + '/oneOf/' + idx,
this.options,
);
parser.exitRef(variant);
// each oneOf should be independent so exiting all the parent refs
// otherwise it will cause false-positive recursive detection
parser.exitParents(merged);
return schema;
});
this.displayType = this.oneOf
.map(schema => {
let name =
@ -206,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;
}
}
@ -275,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',

View File

@ -1,20 +1,9 @@
import * as React from 'react';
import * as styledComponents from 'styled-components';
import { ResolvedThemeInterface } from './theme';
export { ResolvedThemeInterface };
export type InterpolationFunction<P> = styledComponents.InterpolationFunction<P>;
export type StyledFunction<T> = styledComponents.ThemedStyledFunction<T, ResolvedThemeInterface>;
function withProps<T, U extends HTMLElement = HTMLElement>(
styledFunction: StyledFunction<React.HTMLProps<U>>,
): StyledFunction<T & React.HTMLProps<U>> {
return styledFunction;
}
const {
default: styled,
css,
@ -54,7 +43,7 @@ export const media = {
},
};
export { css, createGlobalStyle, keyframes, ThemeProvider, withProps };
export { css, createGlobalStyle, keyframes, ThemeProvider };
export default styled;
export function extensionsHook(styledName: string) {

Some files were not shown because too many files have changed in this diff Show More