Merge remote-tracking branch 'upstream/master'

This commit is contained in:
anastasiia-developer 2022-04-19 11:57:51 +03:00
commit 3987d0b811
183 changed files with 44367 additions and 14837 deletions

View File

@ -17,7 +17,7 @@ module.exports = {
version: 'detect', version: 'detect',
}, },
}, },
plugins: ['@typescript-eslint', 'import'], plugins: ['react', 'react-hooks', '@typescript-eslint', 'import'],
rules: { rules: {
'@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off',
@ -31,6 +31,8 @@ module.exports = {
'@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-var-requires': 'off',
'react/prop-types': 'off', 'react/prop-types': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'import/no-extraneous-dependencies': 'error', 'import/no-extraneous-dependencies': 'error',
'import/no-internal-modules': [ 'import/no-internal-modules': [

2
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,2 @@
* @Redocly/keyboard-warriors
/docs/ @Redocly/technical-writers

22
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,22 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'type: bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Minimal reproducible OpenAPI snippet(if possible)**
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Describe the problem to be solved**
A clear and concise description of what problem to be solved
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

13
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,13 @@
## What/Why/How?
## Reference
## Testing
## Screenshots (optional)
## Check yourself
- [ ] Code is linted
- [ ] Tested
- [ ] All new/updated code is covered with tests

6
.github/sync.yml vendored Normal file
View File

@ -0,0 +1,6 @@
group:
- files:
- source: docs/
dest: docs/redoc
repos: |
Redocly/docs

View File

@ -1,42 +0,0 @@
name: Redoc demo CI/CD
on:
push:
tags:
- v[0-9]*.[0-9]*.[0-9]*
jobs:
build-and-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- run: npm run bundle
- run: npm test
deploy:
needs: build-and-unit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: cache node modules
uses: actions/cache@v1
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ hashFiles('package-lock.json') }}
npm-
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Install dependencies
run: npm ci
- name: Build package
run: npm run build:demo
- name: Deploy to S3 bucket
run: npm run deploy:demo
- name: Invalidate
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DEMO_DISTRIBUTION_ID }} --paths "/*"

12
.github/workflows/e2e-tests.yml vendored Normal file
View File

@ -0,0 +1,12 @@
name: Tests e2e
on: [push]
jobs:
build-and-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci
- run: npm run bundle
- run: npm run e2e

45
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Publish Docker image
on:
release:
types: [published]
jobs:
push_to_registry:
name: Push Docker image to GitHub Packages
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare
id: prep
run: |
DOCKER_IMAGE=ghcr.io/redocly/redoc/cli
VERSION=edge
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
elif [[ $GITHUB_REF == refs/heads/* ]]; then
VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
elif [[ $GITHUB_REF == refs/pull/* ]]; then
VERSION=pr-${{ github.event.number }}
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
if [ "${{ github.event_name }}" = "push" ]; then
TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}"
fi
echo ::set-output name=version::${VERSION}
echo ::set-output name=tags::${TAGS}
echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
- name: Push to GitHub Packages
uses: docker/build-push-action@v2
with:
context: ./cli
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.prep.outputs.tags }}

120
.github/workflows/publish-cli.yml vendored Normal file
View File

@ -0,0 +1,120 @@
name: Publish cli
on:
push:
branches:
- master
jobs:
bundle:
needs: [check-version-cli]
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ hashFiles('package-lock.json') }}
npm-
- run: npm ci
- run: npm run bundle
- name: Store bundle artifact
uses: actions/upload-artifact@v2
with:
name: bundles-cli
path: bundles
retention-days: 1
unit-tests:
needs: [check-version-cli]
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- run: npm test
e2e-tests:
needs: [bundle]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- name: Download bundled artifact
uses: actions/download-artifact@v2
with:
name: bundles-cli
path: bundles
- run: npm run e2e
bundle-cli:
needs: [check-version-cli]
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ hashFiles('package-lock.json') }}
npm-
- name: Install dependencies
run: npm ci
- name: Bundle
run: npm run compile:cli
- name: Store bundle artifact
uses: actions/upload-artifact@v2
with:
name: cli
path: cli
retention-days: 1
check-version-cli:
name: Check Version
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.check.outputs.changed }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
- name: Check if version has been updated
id: check
uses: EndBug/version-check@v2.0.1
with:
file-name: ./cli/package.json
file-url: https://unpkg.com/redoc-cli/package.json
static-checking: localIsNew
publish-cli:
needs: [bundle-cli, unit-tests, e2e-tests]
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- uses: actions/checkout@v2
- name: Download cli bundled artifact
uses: actions/download-artifact@v2
with:
name: cli
path: cli
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ hashFiles('package-lock.json') }}
npm-
- name: Publish to NPM
run: cd cli/ && npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

100
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,100 @@
name: Publish
on:
push:
tags:
- v[0-9]*.[0-9]*.[0-9]*
jobs:
bundle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ hashFiles('package-lock.json') }}
npm-
- run: npm ci
- run: npm run bundle
- name: Store bundle artifact
uses: actions/upload-artifact@v2
with:
name: bundles
path: bundles
retention-days: 1
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- run: npm test
e2e-tests:
needs: [bundle]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- name: Download bundled artifact
uses: actions/download-artifact@v2
with:
name: bundles
path: bundles
- run: npm run e2e
# disable this for now
# deploy-demo:
# needs: [bundle, unit-tests, e2e-tests]
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v1
# - name: Configure AWS Credentials
# uses: aws-actions/configure-aws-credentials@v1
# with:
# aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
# aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# aws-region: us-east-1
# - name: Install dependencies
# run: npm ci
# - name: Download bundled artifacts
# uses: actions/download-artifact@v2
# with:
# name: bundles
# path: bundles
# - name: Build package
# run: npm run build:demo
# - name: Deploy to S3 bucket
# run: npm run deploy:demo
# - name: Invalidate
# run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DEMO_DISTRIBUTION_ID }} --paths "/*"
publish:
needs: [bundle, unit-tests, e2e-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- uses: actions/checkout@v2
- name: Download bundled artifacts
uses: actions/download-artifact@v2
with:
name: bundles
path: bundles
- name: Cache node modules
uses: actions/cache@v2
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: |
npm-${{ hashFiles('package-lock.json') }}
npm-
- name: Before deploy
run: npm ci && npm run declarations
- name: Publish to NPM
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

18
.github/workflows/sync.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Sync Files
on:
push:
branches:
- master
workflow_dispatch:
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@master
- name: Run GitHub File Sync
uses: Redocly/repo-file-sync-action@master
with:
GH_PAT: ${{ secrets.GH_PAT }}
COMMIT_PREFIX: "sync:"
SKIP_PR: true

View File

@ -9,4 +9,4 @@ jobs:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- run: npm ci - run: npm ci
- run: npm run bundle - run: npm run bundle
- run: npm test - run: npm test

2
.gitignore vendored
View File

@ -36,3 +36,5 @@ cli/index.js
stats.json stats.json
yarn.lock yarn.lock
.idea .idea
.vscode
.eslintcache

4
.husky/pre-commit Executable file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run pre-commit

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
*.md

View File

@ -1,37 +0,0 @@
language: node_js
node_js:
- '10'
cache:
directories:
- "~/.cache"
env:
global:
- GH_REF: github.com/Redocly/redoc.git
- GIT_AUTHOR_EMAIL: redoc-bot@users.noreply.github.com
- GIT_AUTHOR_NAME: RedocBot
- secure: apiavCfCQngL9Een1m7MIXMf3bqO3rY4YY59TMBl/yFKi80CEsHPHhgVUkl6hC+aM5PeBt/vgjh37rHMX31j/pcSZ4Z8SO/4Bwr36iHfhSxSEuAQog8P07qWqH7wYYWGIVmF682stgl0fYF+GN92sx/6edFVzsWVECf2G7imtICKSTbhKGm3Dhn2JwGnhD7eyfgZ33omgiaswumdu0xABoXDfqSZR+16fC4Ap5rhv3fXO9ndvRNy1STn376nT+my6e86UrQL4aS/S+HNHgIe1BUs+5cOp6Jgw6t0ie7phY0EAiECsRxy9K4e3Dctv9m6+Wma4+vy65MS0zGyrqey6oyV4l827sCOjrD1qcqc9bX6FlMSouVoNfE4ZjINNAbgigTaiLSoDSPcf5I5smkkM2ezzFOMSZwZxNdaNL2LKb97vc8m/ZUkv0sKZyT7oqVL7aJweEivsSHj5l2KR8Z7XrVB1y2eI6GvyTSa/d+CL4dSRzjh8+IRN047YBrdTKD5IkdT0upfoBu14WPUfFmLKxX+iMCslXRWb6kwojhrWNYmZvL65KRAzJ6+eIPDG/W5QUOpYyYT77bLlBQjVo6NmVvl9v3HMECq9CHH0ivKFBGPiKMOx7cJkTax3FuyznOW2WCXB9kTb5Zk9toaiNlSp9L6ll/h2Eyxa6n6sWUgmmM=
- secure: vVRg9BKGBwF2MbXQnEccFL+XW0/7RaBmge9k7jbGYScBwkP3XjnQ/Xaj0cvTz2CM2EqXsbpwfvr4Jo+enW/E3MGy5RiEzv5hUe/jIFRR0gfAFbZxSTvg5xiFhTDffqQk0fncO4jXu+wPO5lZ2CMRWzyXz3i1MZhjMcAgoDr1+TRss/EGXLNHxr2RM88tpUW0fV2prIRoyGqhCgnYZtrm7hmr41Ej+itg1MqZLml/Rjkt3KsNgI+z0O5Qn3QSAO8GtPZqeftQxAjevOmxZGcssxY8EJvqbjAujr4y51WncXpEmCRPSY2J9R5+fkgZurqwnJapbQpjwKYemok3ps7EHg2gWkAlmPdQO4LKpbffGkM/o5b+8+HdIuQZugsSWQD9hUSftTAFLcfA1isi7V2lHE1m8bX/vk9zIyDdcPSwIaFe9y+w3PexwFmTjPLq+nia/UY2kARFZMEIFAJby6gkA70DcAJ50QOM86InJu5DSzGbIssgTGAXCn0TPPyGveaurVLw8x61j3yh8LDF46gUHey3rqv6WjpCM9h/vg7X/gq5ve/5Q2KHscUKfs/sA53Mt7qPeqRZY1QCaaRjzqJO/ZraHqWWeKmPKaWhPGR0kYEnkvB+K9GZ+HNSWCltjCO4SJ1xeEl7CRqQxAwdiMATF5SKqyiC+bn5oc35mFgbRF8=
- secure: ela1tn4wkJQZ8O4iv+4pIZi5cebxeCStVF1tEUe6qa6WWgJYVXmS2tEv3QQ36NUBFrP58Y6yl10XguPnvj/2BCqcZI4FUBHh3BfiBoUtXxDCVKI5LtlniNiOFGUwfzEeYka8T51zFlcUXSCCaxHkRZbmBsIzeJ39UwTi5fy0qwLv9GgL0czhwm8I8sZ8gyWdGmqpXNFEsb9JP4ZA3mw2qpWkGpGAqQPD9XSCkU3LmX1/ltwsBMAgGYKLLo7vU8d5KV2c8L1Gnxfl6BvfmqUD/dsas/1rnk08rU2nez5ekuQa2tJRkDLOv8bqvrGRLjHSUa3yPuisC6SsDGSU7/3DcozZyYsz7WQ6WI8tYabyjqqeJTF1N8a5T3IbZaZNV1J4JHOO9Cb/y7gIg4edANg6tbe7MzZpdEPRBnw6OkdTdirpNsWQ/jnfpY1hn6mraQZz/q8yaz3W21NjbBJhVnvfh5gWLKQ3YAAziCBhmmrThFhUu0czz+G920MuFo477TBcxvlrE7CaNJ0Q6yYkDehEPOv3jvEs1QVHPwuRrlaLTbBhrlTICKZ58gdX30O8N4i0Xgp/v6qrC03bplnMQc8E/uC61wcVLJixnlZVp8FODpUvPjsxVFkpuNSOIAaiqcERmoiPXx05Epzmr78hjU5rYCx/1MmVoeB4gs9YO+4guD4=
addons:
chrome: stable
apt:
packages:
- libgconf-2-4
before_script: npm run bundle
script: npm test && ([ "${TRAVIS_PULL_REQUEST}" = "false" ] && npm run e2e-ci || npm
run e2e)
after_script: cat ./coverage/lcov.info | coveralls
before_deploy: npm run compile:cli && npm run declarations
deploy:
- provider: npm
skip_cleanup: true
email: gotsijroman@gmail.com
tag: next
api_key: "$NPM_TOKEN"
on:
tags: true
- provider: script
skip_cleanup: true
script: npm run deploy:demo
on:
tags: true

View File

@ -1,4 +0,0 @@
{
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -1,3 +1,314 @@
# [2.0.0-rc.66](https://github.com/Redocly/redoc/compare/v2.0.0-rc.65...v2.0.0-rc.66) (2022-03-30)
### Bug Fixes
* add handle local files for serve command ([#1810](https://github.com/Redocly/redoc/issues/1810)) ([117071e](https://github.com/Redocly/redoc/commit/117071ee83a32d9b3350d8afe2bdb6365a44e2ec))
* move comma out of code block in SecurityRequirement.tsx ([#1924](https://github.com/Redocly/redoc/issues/1924)) ([ab3e8a8](https://github.com/Redocly/redoc/commit/ab3e8a8f80f453066c5495e73ac932a8fef0830a))
* rename bandle command and add deprecate notice ([#1935](https://github.com/Redocly/redoc/issues/1935)) ([eb096b6](https://github.com/Redocly/redoc/commit/eb096b69be52568fc581027161c7d0c4b26c56c1))
### Features
* add support for displaying operationId in the sidebar ([#1927](https://github.com/Redocly/redoc/issues/1927)) ([09786f2](https://github.com/Redocly/redoc/commit/09786f2a5ade6303ea00512483b172347721ca70))
* add nonce support ([#1566](https://github.com/Redocly/redoc/issues/1566)) ([c75ac9c](https://github.com/Redocly/redoc/commit/c75ac9cf70012e2d539b379aab2f0974d088db07))
# [2.0.0-rc.65](https://github.com/Redocly/redoc/compare/v2.0.0-rc.64...v2.0.0-rc.65) (2022-03-15)
### Bug Fixes
* auth link scroll for Firerox ([#1922](https://github.com/Redocly/redoc/issues/1922)) ([fe67e9c](https://github.com/Redocly/redoc/commit/fe67e9c332fee716582a00d60fdf34767bff22d4))
* improve customization fab ([#1891](https://github.com/Redocly/redoc/issues/1891)) ([635f379](https://github.com/Redocly/redoc/commit/635f379eb086268c91eef715148eca8f080cfb86))
* sanitize array of items ([#1920](https://github.com/Redocly/redoc/issues/1920)) ([059bd80](https://github.com/Redocly/redoc/commit/059bd8000e5fd65753d5ca9e0c47940394e0c79b))
* use x-displayName in securityDefinitions [#1444](https://github.com/Redocly/redoc/pull/1444)) ([ac6fb4](https://github.com/Redocly/redoc/commit/
# [2.0.0-rc.64](https://github.com/Redocly/redoc/compare/v2.0.0-rc.63...v2.0.0-rc.64) (2022-02-24)
### Bug Fixes
* bump json-pointer version to avoid CVE-2021-23820 ([#1910](https://github.com/Redocly/redoc/issues/1910)) ([777efdd](https://github.com/Redocly/redoc/commit/777efdde35c1c8dc79dd714e1666279e9192dddb))
* external ref in schema definition ([#1894](https://github.com/Redocly/redoc/issues/1894)) ([57cdd9f](https://github.com/Redocly/redoc/commit/57cdd9f6da38418d6214ac3c6480c5847ecd0228))
# [2.0.0-rc.63](https://github.com/Redocly/redoc/compare/v2.0.0-rc.61...v2.0.0-rc.63) (2022-01-27)
### Bug Fixes
* scroll in sidebar ([b5b0d61](https://github.com/Redocly/redoc/commit/b5b0d61b3568ac2a8aaceafa96ffa6d2f86ed323))
# [2.0.0-rc.62](https://github.com/Redocly/redoc/compare/v2.0.0-rc.61...v2.0.0-rc.62) (2022-01-26)
### Bug Fixes
* fix field expand does not work ([#1875](https://github.com/Redocly/redoc/issues/1875))
# [2.0.0-rc.61](https://github.com/Redocly/redoc/compare/v2.0.0-rc.60...v2.0.0-rc.61) (2022-01-26)
### Bug Fixes
* fix crash in redoc-cli after migrating to esbuild ([#1872](https://github.com/Redocly/redoc/issues/1872))
# [2.0.0-rc.60](https://github.com/Redocly/redoc/compare/v2.0.0-rc.59...v2.0.0-rc.60) (2022-01-25)
### Bug Fixes
* add schema expansion level ([#1868](https://github.com/Redocly/redoc/issues/1868)) ([250d53a](https://github.com/Redocly/redoc/commit/250d53a59fb4bf881875ba466c5a7f3b55d80007))
* attachHeadingsDescriptions match headings incorrectly ([#1845](https://github.com/Redocly/redoc/issues/1845)) ([ea8573d](https://github.com/Redocly/redoc/commit/ea8573dbd78439be50aa2b38f1c83658c16783e3))
* definition name util ([#1865](https://github.com/Redocly/redoc/issues/1865)) ([95a7347](https://github.com/Redocly/redoc/commit/95a734793158d4749e98ee4a7e90e70713a04ced))
* No maxLength label is displayed for arrays of items [#1701](https://github.com/Redocly/redoc/issues/1701) ([#1765](https://github.com/Redocly/redoc/issues/1765)) ([6c7685e](https://github.com/Redocly/redoc/commit/6c7685e5fa04314328a445d7077600692c49489c))
* Response objects couldn't open ([#1867](https://github.com/Redocly/redoc/issues/1867)) ([18f943d](https://github.com/Redocly/redoc/commit/18f943d2b5668f1552d212dee1c3a2ed59054095))
* writeOnly params displaying in webhook ([#1866](https://github.com/Redocly/redoc/issues/1866)) ([5694913](https://github.com/Redocly/redoc/commit/5694913e71f0e8c3a5d9393f1b4ae92534127841))
### Features
* **#1251:** Add file selector to demo application ([#1859](https://github.com/Redocly/redoc/issues/1859)) ([b74dcde](https://github.com/Redocly/redoc/commit/b74dcde42b45ebe5ae617f1ec3cfea2ea1aff922)), closes [#1251](https://github.com/Redocly/redoc/issues/1251) [#1251](https://github.com/Redocly/redoc/issues/1251) [#1251](https://github.com/Redocly/redoc/issues/1251)
* redoc-cli add host option ([#1598](https://github.com/Redocly/redoc/issues/1598)) ([fb104e6](https://github.com/Redocly/redoc/commit/fb104e696618b0b81439da134887830a0f2439ea))
* support examples in object schema ([#1832](https://github.com/Redocly/redoc/issues/1832)) ([c986f0e](https://github.com/Redocly/redoc/commit/c986f0ef1a38bc1e61cae70830d84de03b684b89))
# [2.0.0-rc.59](https://github.com/Redocly/redoc/compare/v2.0.0-rc.58...v2.0.0-rc.59) (2021-12-09)
### Bug Fixes
* fix scroll in example dropdown ([#1803](https://github.com/Redocly/redoc/issues/1803)) ([bc2d9a7](https://github.com/Redocly/redoc/commit/bc2d9a7d9cd530274483fecd136db290a5b46ff7))
* x-examples for request body param does not display [#1743](https://github.com/Redocly/redoc/issues/1743) ([#1826](https://github.com/Redocly/redoc/issues/1826)) ([aaa3b32](https://github.com/Redocly/redoc/commit/aaa3b3280c8422d450e8849ae02135dde199d6d5))
### Features
* add option sideNavStyle ([#1805](https://github.com/Redocly/redoc/pull/1805)) ([2e4663b](https://github.com/Redocly/redoc/commit/2e4663b3b7022f25d3dc808afbcb3b3ad9483c41))
# [2.0.0-rc.58](https://github.com/Redocly/redoc/compare/v2.0.0-rc.57...v2.0.0-rc.58) (2021-11-29)
### Bug Fixes
* add browser build for webpack 5 ([#1796](https://github.com/Redocly/redoc/issues/1796)) ([0e43ad3](https://github.com/Redocly/redoc/commit/0e43ad3102cfba8c4b30e59500ad4efc53f01c2d))
* Default boolean property value not rendered [#1779](https://github.com/Redocly/redoc/issues/1779) ([#1781](https://github.com/Redocly/redoc/issues/1781)) ([734080c](https://github.com/Redocly/redoc/commit/734080c35471d16f87004f7f9a51dcdeee1278a6))
* exclusiveMin/Max shows incorect range ([#1799](https://github.com/Redocly/redoc/issues/1799)) ([b604bd8](https://github.com/Redocly/redoc/commit/b604bd8da874f07e9e9f8b193ad10117a5f5059c))
* mobile view in docker image ([#1795](https://github.com/Redocly/redoc/issues/1795)) ([ad652b9](https://github.com/Redocly/redoc/commit/ad652b9c7fbcd84a6e83397272de64e57213fe9a))
# [2.0.0-rc.57](https://github.com/Redocly/redoc/compare/v2.0.0-rc.56...v2.0.0-rc.57) (2021-10-11)
### Bug Fixes
* fix deref logic for oas3.1 ([#1767](https://github.com/Redocly/redoc/issues/1767)) ([4fb9c83](https://github.com/Redocly/redoc/commit/4fb9c835256b9e44bcecabde7baf0f0f3e5beb3f))
* improve publish action scripts ([#1729](https://github.com/Redocly/redoc/issues/1729)) ([952c05c](https://github.com/Redocly/redoc/commit/952c05c6b4b95fe6082611fed9e2f0913272b904))
* No match scenario in search ([#1667](https://github.com/Redocly/redoc/issues/1667)) ([352a851](https://github.com/Redocly/redoc/commit/352a8518576dfb6b240ec41212a64f1c7312ab67))
* OpenAPI 3.1: Missing description when $ref used [#1727](https://github.com/Redocly/redoc/issues/1727) ([fe6909e](https://github.com/Redocly/redoc/commit/fe6909ed80dd6053b48c30f63a2460614bf957a9))
* OpenAPI 3.1: Missing description when $ref used [#1727](https://github.com/Redocly/redoc/issues/1727) ([35f7787](https://github.com/Redocly/redoc/commit/35f77878de7d1dd250040771f17757a5a6ce85f9))
* Redoc spelling ([c87600d](https://github.com/Redocly/redoc/commit/c87600d520f037d291169b44b5803a35af16b5a5))
* Schema for events incorrectly omits readOnly and includes writeOnly ([#1720](https://github.com/Redocly/redoc/issues/1720) [#1540](https://github.com/Redocly/redoc/issues/1540)) ([a8e0c29](https://github.com/Redocly/redoc/commit/a8e0c296852661dec1dcad2388d7589f9e0d3609))
* scrolling to the first item ([#1753](https://github.com/Redocly/redoc/issues/1753)) ([bccd213](https://github.com/Redocly/redoc/commit/bccd21394ef79940c2efbe24a0d866c7af103d94))
* The number of items in the array in the array is incorrect [#1762](https://github.com/Redocly/redoc/issues/1762) ([#1763](https://github.com/Redocly/redoc/issues/1763)) ([3b8d644](https://github.com/Redocly/redoc/commit/3b8d6441bd9978b849a53021d40fd4fe150272ea))
### Features
* add q/kdb+ syntax highlighting ([#1605](https://github.com/Redocly/redoc/issues/1605)) ([43451ba](https://github.com/Redocly/redoc/commit/43451ba4cd24270b8629a967d3fd2ce2eed8912e))
* new option generatedPayloadSamplesMaxDepth ([#1642](https://github.com/Redocly/redoc/issues/1642)) ([bd9390a](https://github.com/Redocly/redoc/commit/bd9390a5bfc5458c06121110db33968a20fcebe4))
# [2.0.0-rc.56](https://github.com/Redocly/redoc/compare/v2.0.0-rc.53...v2.0.0-rc.56) (2021-08-11)
### Bug Fixes
* handle empty object in security array ([#1678](https://github.com/Redocly/redoc/issues/1678)) ([9e1ea70](https://github.com/Redocly/redoc/commit/9e1ea703e56a71567b13d0d22e2d69945a22de4d))
* hideLoading options in redoc standalone ([#1709](https://github.com/Redocly/redoc/issues/1709)) ([6a52a16](https://github.com/Redocly/redoc/commit/6a52a16d5b75a2955da7217c4a264f0fa8e98c89))
* improve openapi 3.1 ([#1700](https://github.com/Redocly/redoc/issues/1700)) ([cd2d6f7](https://github.com/Redocly/redoc/commit/cd2d6f76e87c8385786a9c8e51c0d11c79d9707c))
- show contentEncoding on fields
- crash with OpenAPI 3.1 type as array of strings in requestBody
- nullable label not shown
* nullable object's fields were missing ([#1721](https://github.com/Redocly/redoc/issues/1721)) ([ddf297b](https://github.com/Redocly/redoc/commit/ddf297b11269ef515bd62771912a5609721d5e39))
### Features
* add github action to build docker images and push to ghcr.io on release ([#1614](https://github.com/Redocly/redoc/issues/1614)) ([919a5f0](https://github.com/Redocly/redoc/commit/919a5f02fb94ca869011d5eaf63ee71b61b60150))
* add yaml highlight ([#1684](https://github.com/Redocly/redoc/issues/1684)) ([d724440](https://github.com/Redocly/redoc/commit/d72444008533623c87f238fe8758b1dd518b89eb))
* added localization for some labels ([#1675](https://github.com/Redocly/redoc/issues/1675)) ([ec50858](https://github.com/Redocly/redoc/commit/ec50858ec47af08c5fe553266fe3c209fba97eae))
# [2.0.0-rc.55](https://github.com/Redocly/redoc/compare/v2.0.0-rc.54...v2.0.0-rc.55) (2021-07-01)
### Bug Fixes
* broken linkify ([3df72fb](https://github.com/Redocly/redoc/commit/3df72fb99ff24fb9a551565b7568d96f8614ed6f)), closes [#1655](https://github.com/Redocly/redoc/issues/1655)
* fix accidentally removed onLoaded ([b41a8b4](https://github.com/Redocly/redoc/commit/b41a8b4ac714084dc25de7914fa1f99386e907e2)), closes [#1656](https://github.com/Redocly/redoc/issues/1656)
### Features
* added git folder sync config ([a69f0fb](https://github.com/Redocly/redoc/commit/a69f0fb00986a04c812ab273711e8f3501b98139))
# [2.0.0-rc.54](https://github.com/Redocly/redoc/compare/v2.0.0-rc.53...v2.0.0-rc.54) (2021-06-09)
### Bug Fixes
* added missing semicolon to styling ([#1578](https://github.com/Redocly/redoc/issues/1578)) ([dfc4cf1](https://github.com/Redocly/redoc/commit/dfc4cf1caa131aa7bc6da6d489e3a8425d800326))
* parse json theme string for standalone tag ([#1492](https://github.com/Redocly/redoc/issues/1492)) ([d7a0a4d](https://github.com/Redocly/redoc/commit/d7a0a4da17241dd9c089202dba76a8312248616e))
* right absolute path for load and bundle definition ([#1579](https://github.com/Redocly/redoc/issues/1579)) ([ab2d57a](https://github.com/Redocly/redoc/commit/ab2d57a5a2ac5df007d76be0d664f3fb5f909566))
* use operation path if operation summary/description is not provided ([#1596](https://github.com/Redocly/redoc/issues/1596)) ([4b072be](https://github.com/Redocly/redoc/commit/4b072be8d1c0dc4f1fa627168eebaed0a0213e08)), closes [#1270](https://github.com/Redocly/redoc/issues/1270)
### Features
* add basic support OpenAPI 3.1 ([#1622](https://github.com/Redocly/redoc/issues/1622)) ([823be24](https://github.com/Redocly/redoc/commit/823be24b313c3a2445df7e0801a0cc79c20bacd1))
* merge refs oas 3.1 ([#1640](https://github.com/Redocly/redoc/issues/1640)) ([f4ea368](https://github.com/Redocly/redoc/commit/f4ea368f78a693fd70d48b5e0e5ffce3560432f4))
# [2.0.0-rc.51](https://github.com/Redocly/redoc/compare/v2.0.0-rc.50...v2.0.0-rc.51) (2021-04-08)
### Bug Fixes
* use openapi-core to bundle definition instead of json-schema-ref-parser ([5033946](https://github.com/Redocly/redoc/commit/503394655da2aac544e278796098cba93d9194b9)),
closes: [#1506](https://github.com/Redocly/redoc/issues/1506), [#1478](https://github.com/Redocly/redoc/issues/1478)
* add disable-google-font parameter to serve command in cli ([c7bbef5](https://github.com/Redocly/redoc/commit/c7bbef515524095e957729eac35a5b7a97619b55)), closes [#1501](https://github.com/Redocly/redoc/issues/1501)
# [2.0.0-rc.50](https://github.com/Redocly/redoc/compare/v2.0.0-rc.49...v2.0.0-rc.50) (2021-02-15)
### Bug Fixes
* add includes polyfill ([3ba622f](https://github.com/Redocly/redoc/commit/3ba622f3ab9e28c954fe05f42e7b90862fc3d544)), closes [#1530](https://github.com/Redocly/redoc/issues/1530)
* background-color in search results ([#1531](https://github.com/Redocly/redoc/issues/1531)) ([d288165](https://github.com/Redocly/redoc/commit/d288165a4ea04aedc23dba12020a73e86f20755b))
* false-positive recursive tag case when using oneOf + allOf ([#1534](https://github.com/Redocly/redoc/issues/1534)) ([8270481](https://github.com/Redocly/redoc/commit/8270481e9f0f381b392f7921d21cb06e0e673b6d))
# [2.0.0-rc.49](https://github.com/Redocly/redoc/compare/v2.0.0-rc.48...v2.0.0-rc.49) (2021-01-30)
### Bug Fixes
* crash on multiple examples on parameter object ([0dce880](https://github.com/Redocly/redoc/commit/0dce880dce1e489c7e8963e352d97603262f4b86)), closes [#1485](https://github.com/Redocly/redoc/issues/1485)
* fix SourceCodeWithCopy component to be non-pure ([040ce72](https://github.com/Redocly/redoc/commit/040ce72a8ae0c1ca7504e10e44d0b2ac7ba04977))
* pass boolean and number values as a string in highlight function ([#1512](https://github.com/Redocly/redoc/issues/1512)) ([c874a59](https://github.com/Redocly/redoc/commit/c874a5942c3bf9f6a2dc5909e31d57925d40aa86))
# [2.0.0-rc.48](https://github.com/Redocly/redoc/compare/v2.0.0-rc.47...v2.0.0-rc.48) (2020-11-30)
### Bug Fixes
* add missed labels to elements ([#1445](https://github.com/Redocly/redoc/issues/1445)) ([8c559bc](https://github.com/Redocly/redoc/commit/8c559bcbcde39efee7f1570b88840468bfdfb17c))
### Features
* add new option hideSchemaPattern ([#1475](https://github.com/Redocly/redoc/issues/1475)) ([bb4594e](https://github.com/Redocly/redoc/commit/bb4594ee58d89819c975bdb575083c0667e3d940))
* support multiple examples for parameters ([#1470](https://github.com/Redocly/redoc/issues/1470)) ([d12e410](https://github.com/Redocly/redoc/commit/d12e410d99a988948b359093159df79572bc78ab))
# [2.0.0-rc.46](https://github.com/Redocly/redoc/compare/v2.0.0-rc.45...v2.0.0-rc.46) (2020-11-05)
### Bug Fixes
* fix arrow color in responses ([#1452](https://github.com/Redocly/redoc/issues/1452)) ([6bedcf9](https://github.com/Redocly/redoc/commit/6bedcf94b26d820101ab510b28d2b76a38999eea))
* remove duplicated slash if hideHostname option enabled ([#1448](https://github.com/Redocly/redoc/issues/1448)) ([4729fc3](https://github.com/Redocly/redoc/commit/4729fc3d8fc83f4af087cd7932adf500b45bab4e))
* use shrinkwrap for cli package ([#1446](https://github.com/Redocly/redoc/issues/1446)) ([4567534](https://github.com/Redocly/redoc/commit/4567534cbb26f13a72a64d49faca64fc992d6dd8))
### Features
* add tabTextColor option for responses ([#1451](https://github.com/Redocly/redoc/issues/1451)) ([702fea0](https://github.com/Redocly/redoc/commit/702fea0f410499101efc554983c6db58acc84889))
# [2.0.0-rc.45](https://github.com/Redocly/redoc/compare/v2.0.0-rc.43...v2.0.0-rc.45) (2020-10-27)
### Bug Fixes
* fix the name of OpenID Connect security scheme ([#1425](https://github.com/Redocly/redoc/issues/1425)) ([c11f679](https://github.com/Redocly/redoc/commit/c11f679f82586a96225488c8a96d0c908bfd2e09))
* increase colors contrast to make them more accessible ([#1433](https://github.com/Redocly/redoc/issues/1433)) ([e2de5b0](https://github.com/Redocly/redoc/commit/e2de5b065eabd00d301ea61106ddafc65bd83afa))
### Features
* add field constraint indicator for uniqueItems ([#1423](https://github.com/Redocly/redoc/issues/1423)) ([c0ae9de](https://github.com/Redocly/redoc/commit/c0ae9de60758aa7561ce8a04b6e0060d0bc4a258)), closes [#1353](https://github.com/Redocly/redoc/issues/1353)
* new extensions hook PropertyDetailsCell + wrap property name into span ([0703f73](https://github.com/Redocly/redoc/commit/0703f73f79a1cabafdc1a908ebb0c5ab142ca825))
# [2.0.0-rc.44](https://github.com/Redocly/redoc/compare/v2.0.0-rc.43...v2.0.0-rc.44) (2020-10-16)
### Features
* new extensions hook PropertyDetailsCell + wrap property name into span ([0fae030](https://github.com/Redocly/redoc/commit/0fae03099645bd9d3795709175640583b08dfc3d))
# [2.0.0-rc.43](https://github.com/Redocly/redoc/compare/v2.0.0-rc.42...v2.0.0-rc.43) (2020-10-13)
### Bug Fixes
* fix broken observable after mobx upgrade ([#1415](https://github.com/Redocly/redoc/issues/1415)) ([26c407b](https://github.com/Redocly/redoc/commit/26c407bd0f2bc1ec9881e0a3668e09e645fc0cc0))
# [2.0.0-rc.42](https://github.com/Redocly/redoc/compare/v2.0.0-rc.41...v2.0.0-rc.42) (2020-10-13)
### Bug Fixes
* hide dropdown input on IE 11 ([#1403](https://github.com/Redocly/redoc/issues/1403)) ([6632d84](https://github.com/Redocly/redoc/commit/6632d844536532227cb92290f9fc2b6b2f913270))
* make samples accessible by keyboard ([#1401](https://github.com/Redocly/redoc/issues/1401)) ([146b38c](https://github.com/Redocly/redoc/commit/146b38c9d0b926765d8e00dd37204c30bf3ac4e0))
* make schema layout more responsive on small screen ([#1411](https://github.com/Redocly/redoc/issues/1411)) ([84ab95d](https://github.com/Redocly/redoc/commit/84ab95ddc7b5dc159098aecf82ad922ffd4a3093))
# [2.0.0-rc.41](https://github.com/Redocly/redoc/compare/v2.0.0-rc.40...v2.0.0-rc.41) (2020-09-24)
### Bug Fixes
* display response code at the top after adding a line break ([#1374](https://github.com/Redocly/redoc/issues/1374)) ([c801b87](https://github.com/Redocly/redoc/commit/c801b87d2aea5e17d35093e2548e1f51f42b1ee3))
* fix displaying response title ([#1376](https://github.com/Redocly/redoc/issues/1376)) ([f3e8ab4](https://github.com/Redocly/redoc/commit/f3e8ab4f8e5522c9ea1ddedb143e23c7d62f5807))
* fix displaying top-level object without any properties ([a5468fb](https://github.com/Redocly/redoc/commit/a5468fb7bb99fcfe33724af939b1a589c1219052))
* show long pattern and add toggle button ([#1375](https://github.com/Redocly/redoc/issues/1375)) ([a6b41aa](https://github.com/Redocly/redoc/commit/a6b41aa00b7592512fdaa7532d9f5d85238db29b))
### Features
* load external search index ([346b10f](https://github.com/Redocly/redoc/commit/346b10f1739d6b44066bdf1f6aac39d5ee3567d2))
* support for ignoring specified named schemas ([9730c4e](https://github.com/Redocly/redoc/commit/9730c4ee1c274c5775966959b69c209c40034b11))
# [2.0.0-rc.40](https://github.com/Redocly/redoc/compare/v2.0.0-rc.39...v2.0.0-rc.40) (2020-08-24) # [2.0.0-rc.40](https://github.com/Redocly/redoc/compare/v2.0.0-rc.39...v2.0.0-rc.40) (2020-08-24)

365
README.md
View File

@ -1,93 +1,161 @@
<div align="center"> <div align="center">
<img alt="ReDoc logo" src="https://raw.githubusercontent.com/Redocly/redoc/master/docs/images/redoc-logo.png" width="400px" /> <img alt="Redoc logo" src="https://raw.githubusercontent.com/Redocly/redoc/master//docs/images/redoc.png" width="400px" />
**OpenAPI/Swagger-generated API Reference Documentation** # Generate interactive API documentation from OpenAPI definitions
[![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) [![Build Status](https://travis-ci.com/Redocly/redoc.svg?branch=master)](https://travis-ci.com/Redocly/redoc) [![Coverage Status](https://coveralls.io/repos/Redocly/redoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Redocly/redoc?branch=master) [![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/) [![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> </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/Redocly/redoc/tree/v1.x)** **This is the README for the `2.x` version of Redoc (React-based).**
**The README for the `1.x` version is on the [v1.x](https://github.com/Redocly/redoc/tree/v1.x) branch**
## About Redoc
![ReDoc demo](https://raw.githubusercontent.com/Redocly/redoc/master/demo/redoc-demo.png) Redoc is an open-source tool for generating documentation from OpenAPI (fka Swagger) definitions.
## [Live demo](http://redocly.github.io/redoc/) By default Redoc offers a three-panel, responsive layout:
[<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) - The left panel contains a search bar and navigation menu.
- The central panel contains the documentation.
- The right panel contains request and response examples.
![Redoc demo](https://raw.githubusercontent.com/Redocly/redoc/master/demo/redoc-demo.png)
## Live demo
If you want to see how Redoc will render your OpenAPI definition,
you can try it out online at https://redocly.github.io/redoc/.
A version of the Swagger Petstore API is displayed by default.
To test it with your own OpenAPI definition,
enter the URL for your definition and select **TRY IT**.
## Redoc vs. Reference vs. Portals
Redoc is Redocly's community-edition product. Looking for something more?
Checkout the following feature comparison of Redocly's premium products versus Redoc:
| Features | Redoc | Reference | Portals |
|------------------------------|:---------:|:---------:|:-----------:|
| **Specs** | | | |
| Swagger 2.0 | √ | √ | √ |
| OpenAPI 3.0 | √ | √ | √ |
| OpenAPI 3.1 | √ (basic) | √ | √ |
| | | | |
| **Theming** | | | |
| Fonts/colors | √ | √ | √ |
| Extra theme options | | √ | √ |
| | | | |
| **Performance** | | | |
| Pagination | | √ | √ |
| Search (enhanced) | | √ | √ |
| Search (server-side) | | | √ |
| | | | |
| **Multiple APIs** | | | |
| Multiple versions | | √ | √ |
| Multiple APIs | | | √ |
| API catalog | | | √ |
| | | | |
| **Additional features** | | | |
| Try-it console | | √ | √ |
| Automated code samples | | √ | √ |
| Deep links | | √ | √ |
| More SEO control | | | √ |
| Contextual docs | | | √ |
| Landing pages | | | √ |
| React hooks for more control | | | √ |
| Personalization | | | √ |
| Analytics integrations | | | √ |
| Feedback | | | Coming Soon |
Refer to the Redocly's documentation for more information on these products:
- [Portals](https://redoc.ly/docs/developer-portal/introduction/)
- [Reference](https://redoc.ly/docs/api-reference-docs/getting-started/)
- [Redoc](https://redoc.ly/docs/redoc/quickstart/intro/)
## Features ## Features
- Extremely easy deployment
- [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)
- OpenAPI 3.0 support
- Neat **interactive** documentation for nested objects <br>
![](docs/images/nested-demo.gif)
- Code samples support (via vendor extension) <br>
![](docs/images/code-samples-demo.gif)
- Responsive three-panel design with menu/scrolling synchronization - Responsive three-panel design with menu/scrolling synchronization
- Integrate API Introduction into side menu - ReDoc takes advantage of markdown headings from OpenAPI description field. It pulls them into side menu and also supports deep linking. - [Multiple deployment options](https://redoc.ly/docs/redoc/quickstart/intro/)
- High-level grouping in side-menu via [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) vendor extension - [Server-side rendering (SSR) ready](https://redoc.ly/docs/redoc/quickstart/cli/#redoc-cli-commands)
- Simple integration with `create-react-app` ([sample](https://github.com/APIs-guru/create-react-app-redoc)) - Ability to integrate your API introduction into the side menu
- Branding/customizations via [`theme` option](#redoc-options-object) - [Simple integration with `create-react-app`](https://redoc.ly/docs/redoc/quickstart/react/)
## Roadmap [Example repo](https://github.com/APIs-guru/create-react-app-redoc)
- [x] ~~[OpenAPI v3.0 support](https://github.com/Redocly/redoc/issues/312)~~ - [Command-line interface to bundle your docs into a **zero-dependency** HTML file](https://redoc.ly/docs/redoc/quickstart/cli/)
- [x] ~~performance optimizations~~ - Neat **interactive** documentation for nested objects <br>
- [x] ~~better navigation (menu improvements + search)~~ ![](docs/images/nested-demo.gif)
- [x] ~~React rewrite~~
- [x] ~~docs pre-rendering (performance and SEO)~~ ## Customization options
- [ ] ability to simple branding/styling [<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
- [ ] built-in API Console - High-level grouping in side-menu with the [`x-tagGroups`](https://redoc.ly/docs/api-reference-docs/specification-extensions/x-tag-groups/) specification extension
- Branding/customizations using the [`theme` option](https://redoc.ly/docs/api-reference-docs/configuration/theming/)
## Support
- OpenAPI v3.0 support
- Basic OpenAPI v3.1 support
- Broad OpenAPI v2.0 feature support (yes, it supports even `discriminator`) <br>
![](docs/images/discriminator-demo.gif)
- Code samples support (via vendor extension) <br>
![](docs/images/code-samples-demo.gif)
## Releases ## Releases
**Important:** all the 2.x releases are deployed to npm and can be used via jsdeliver: **Important:** all the 2.x releases are deployed to npm and can be used with jsdeliver:
- particular release, e.g. `v2.0.0-alpha.15`: https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.17/bundles/redoc.standalone.js - particular release, for example, `v2.0.0-alpha.15`: https://cdn.jsdelivr.net/npm/redoc@2.0.0-alpha.17/bundles/redoc.standalone.js
- `next` release: https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js - `next` release: https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js
Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(deprecated)**: Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(deprecated)**:
- particular release, e.g. `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js - particular release, for example `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
- `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js - `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js - it will point to latest 1.x.x release since 2.x releases are not hosted on this CDN but on unpkg. - `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js - it will point to latest 1.x.x release since 2.x releases are not hosted on this CDN but on unpkg.
## Version Guidance ## Version Guidance
| ReDoc Release | OpenAPI Specification | | Redoc Release | OpenAPI Specification |
|:--------------|:----------------------| |:--------------|:----------------------|
| 2.0.0-alpha.54| 3.1, 3.0.x, 2.0 |
| 2.0.0-alpha.x | 3.0, 2.0 | | 2.0.0-alpha.x | 3.0, 2.0 |
| 1.19.x | 2.0 | | 1.19.x | 2.0 |
| 1.18.x | 2.0 | | 1.18.x | 2.0 |
| 1.17.x | 2.0 | | 1.17.x | 2.0 |
## Some Real-life usages ## Showcase
- [Rebilly](https://rebilly-api.redoc.ly/) - [Rebilly](https://api-reference.rebilly.com/)
- [Docker Engine](https://docs.docker.com/engine/api/v1.25/) - [Docker Engine](https://docs.docker.com/engine/api/v1.25/)
- [Zuora](https://www.zuora.com/developer/api-reference/) - [Zuora](https://www.zuora.com/developer/api-reference/)
- [Discourse](http://docs.discourse.org) - [Discourse](http://docs.discourse.org)
- [Commbox](https://www.commbox.io/api/) - [Commbox](https://www.commbox.io/api/)
- [APIs.guru](https://apis.guru/api-doc/) - [APIs.guru](https://apis.guru/api-doc/)
- [FastAPI](https://github.com/tiangolo/fastapi) - [BoxKnight](https://www.docs.boxknight.com/)
## Lint OpenAPI definitions
Redocly's OpenAPI CLI is an open source command-line tool that you can use to lint
your OpenAPI definition. Linting helps you to catch errors and inconsistencies in your
OpenAPI definition before publishing.
Refer to [Lint configuration](https://redoc.ly/docs/cli/guides/lint/) in the OpenAPI documentation for more information.
## Deployment ## Deployment
### TL;DR ### TL;DR final code example
To render your OpenAPI definition using Redoc, use the following HTML code sample and
replace the `spec-url` attribute with the url or local file address to your definition.
```html ```html
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>ReDoc</title> <title>Redoc</title>
<!-- needed for adaptive design --> <!-- needed for adaptive design -->
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!-- <!--
ReDoc doesn't change outer page styles Redoc doesn't change outer page styles
--> -->
<style> <style>
body { body {
@ -98,117 +166,29 @@ Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(d
</head> </head>
<body> <body>
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc> <redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script> <script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
</body> </body>
</html> </html>
```
That's all folks!
**IMPORTANT NOTE:** if you work with untrusted user spec, use `untrusted-spec` [option](#redoc-options-object) to prevent XSS security risks.
### 1. Install ReDoc (skip this step for CDN)
Install using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
npm i redoc
or using [yarn](https://yarnpkg.com):
yarn add redoc
### 2. Reference redoc script in HTML
For **CDN**:
```html
<script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"> </script>
``` ```
For npm: For step-by-step instructions for how to get started using Redoc
```html to render your OpenAPI definition, refer to the
<script src="node_modules/redoc/bundles/redoc.standalone.js"> </script> [**Redoc quickstart guide**](https://redoc.ly/docs/redoc/quickstart/intro/).
```
### 3. Add `<redoc>` element to your page ## Redoc CLI
```html For more information on Redoc's commmand-line interface, refer to
<redoc spec-url="url/to/your/spec"></redoc> [**Using the Redoc CLI**](https://redoc.ly/docs/redoc/quickstart/cli/).
```
### 4. Enjoy :smile:
## Usage as a React component
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 core-js
Import `RedocStandalone` component from 'redoc' module:
```js
import { RedocStandalone } from 'redoc';
```
and use it somewhere in your component:
```js
<RedocStandalone specUrl="url/to/your/spec"/>
```
or
```js
<RedocStandalone spec={/* spec as an object */}/>
```
Also you can pass options:
```js
<RedocStandalone
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
options={{
nativeScrollbars: true,
theme: { colors: { primary: { main: '#dd5522' } } },
}}
/>
```
Here are detailed [options docs](#redoc-options-object).
You can also specify `onLoaded` callback which will be called each time Redoc has been fully rendered or when error occurs (with an error as the first argument). *NOTE*: It may be called multiply times if you change component properties
```js
<RedocStandalone
specUrl="http://rebilly.github.io/RebillyAPI/openapi.json"
onLoaded={error => {
if (!error) {
console.log('Yay!');
}
}}
/>
```
[**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:
docker pull redocly/redoc
docker run -p 8080:80 redocly/redoc
Also you may rewrite some predefined environment variables defined in [Dockerfile](./config/docker/Dockerfile). By default ReDoc starts with demo Petstore spec located at `http://petstore.swagger.io/v2/swagger.json`, but you may change this URL using environment variable `SPEC_URL`:
docker run -p 8080:80 -e SPEC_URL=https://api.example.com/openapi.json redocly/redoc
## ReDoc CLI
[See here](https://github.com/Redocly/redoc/blob/master/cli/README.md)
## Configuration ## Configuration
### Security Definition location ### Security Definition location
You can inject Security Definitions widget into any place of your specification `description`. Check out details [here](docs/security-definitions-injection.md). You can inject the Security Definitions widget into any place in your definition `description`.
For more information, refer to [Security definitions injection](docs/security-definitions-injection.md).
### Swagger vendor extensions ### OpenAPI specification extensions
ReDoc makes use of the following [vendor extensions](https://swagger.io/specification/#specificationExtensions): Redoc uses the following [specification extensions](https://swagger.io/specification/#specificationExtensions):
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo * [`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-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
* [`x-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples * [`x-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples
@ -219,23 +199,35 @@ ReDoc makes use of the following [vendor extensions](https://swagger.io/specific
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0) * [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
* [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore * [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore
* [`x-additionalPropertiesName`](docs/redoc-vendor-extensions.md#x-additionalPropertiesName) - ability to supply a descriptive name for the additional property keys * [`x-additionalPropertiesName`](docs/redoc-vendor-extensions.md#x-additionalPropertiesName) - ability to supply a descriptive name for the additional property keys
* [`x-summary`](docs/redoc-vendor-extensions.md#x-summary) - For Response object, use as the response button text, with description rendered under the button
* [`x-extendedDiscriminator`](docs/redoc-vendor-extensions.md#x-extendedDiscriminator) - In Schemas, uses this to solve name-clash issues with the standard discriminator
* [`x-explicitMappingOnly`](docs/redoc-vendor-extensions.md#x-explicitMappingOnly) - In Schemas, display a more descriptive property name in objects with additionalProperties when viewing the property list with an object
### `<redoc>` options object ### `<redoc>` options object
You can use all of the following options with standalone version on <redoc> tag by kebab-casing them, e.g. `scrollYOffset` becomes `scroll-y-offset` and `expandResponses` becomes `expand-responses`. You can use all of the following options with the standalone version of the <redoc> tag by kebab-casing them. For example, `scrollYOffset` becomes `scroll-y-offset`, and `expandResponses` becomes `expand-responses`.
* `disableSearch` - disable search indexing and search box. * `disableSearch` - disable search indexing and search box.
* `expandDefaultServerVariables` - enable expanding default server variables, default `false`. * `expandDefaultServerVariables` - enable expanding default server variables, default `false`.
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time. * `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.
* `generatedPayloadSamplesMaxDepth` - set the maximum render depth for JSON payload samples (responses and request body). The default value is `10`.
* `maxDisplayedEnumValues` - display only specified number of enum values. hide rest values under spoiler.
* `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button. * `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition. * `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
* `hideLoading` - do not show loading animation. Useful for small docs. * `hideLoading` - do not show loading animation. Useful for small docs.
* `hideFab` - do not show FAB in mobile view. Useful for implementing a custom floating action button.
* `hideSchemaPattern` - if set, the pattern is not shown in the schema.
* `hideSingleRequestSampleTab` - do not show the request sample tab for requests with only one sample. * `hideSingleRequestSampleTab` - do not show the request sample tab for requests with only one sample.
* `showObjectSchemaExamples` - show object schema example in the properties, default `false`.
* `expandSingleSchemaField` - automatically expand single field in a schema * `expandSingleSchemaField` - automatically expand single field in a schema
* `jsonSampleExpandLevel` - set the default expand level for JSON payload samples (responses and request body). Special value 'all' expands all levels. The default value is `2`. * `schemaExpansionLevel` - specifies whether to automatically expand schemas. Special value `"all"` expands all levels. The default value is `0`.
* `jsonSampleExpandLevel` - set the default expand level for JSON payload samples (responses and request body). Special value `"all"` expands all levels. The default value is `2`.
* `hideSchemaTitles` - do not display schema `title` next to to the type * `hideSchemaTitles` - do not display schema `title` next to to the type
* `simpleOneOfTypeLabel` - show only unique oneOf types in the label without titles * `simpleOneOfTypeLabel` - show only unique oneOf types in the label without titles
* `sortEnumValuesAlphabetically` - set to true, sorts all enum values in all schemas alphabetically
* `sortOperationsAlphabetically` - set to true, sorts operations in the navigation sidebar and in the middle panel alphabetically
* `sortTagsAlphabetically` - set to true, sorts tags in the navigation sidebar and in the middle panel alphabetically
* `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.~~ * `lazyRendering` - _Not implemented yet_ ~~if set, enables lazy rendering mode in ReDoc. This mode is useful for APIs with big number of operations (e.g. > 50). In this mode ReDoc shows initial screen ASAP and then renders the rest operations asynchronously while showing progress bar on the top. Check out the [demo](\\redocly.github.io/redoc) for the example.~~
* `menuToggle` - if true clicking second time on expanded menu item will collapse it, default `false`. * `menuToggle` - if true clicking second time on expanded menu item will collapse it, default `true`.
* `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs). * `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs).
* `noAutoAuth` - do not inject Authentication section automatically. * `noAutoAuth` - do not inject Authentication section automatically.
* `onlyRequiredInSamples` - shows only required fields in request samples. * `onlyRequiredInSamples` - shows only required fields in request samples.
@ -248,27 +240,78 @@ You can use all of the following options with standalone version on <redoc> tag
* **function**: A getter function. Must return a number representing the offset (in pixels). * **function**: A getter function. Must return a number representing the offset (in pixels).
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display. * `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display.
* `sortPropsAlphabetically` - sort properties alphabetically. * `sortPropsAlphabetically` - sort properties alphabetically.
* `suppressWarnings` - if set, warnings are not rendered at the top of documentation (they still are logged to the console).
* `payloadSampleIdx` - if set, payload sample will be inserted at this index or last. Indexes start from 0. * `payloadSampleIdx` - if set, payload sample will be inserted at this index or last. Indexes start from 0.
* `theme` - ReDoc theme. Not documented yet. For details check source code: [theme.ts](https://github.com/Redocly/redoc/blob/master/src/theme.ts). * `theme` - ReDoc theme. For details check [theme docs](#redoc-theme-object).
* `untrustedSpec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!** * `untrustedSpec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!**
* `nonce` - if set, the provided value will be injected in every injected HTML element in the `nonce` attribute. Useful when using CSP, see https://webpack.js.org/guides/csp/.
* `sideNavStyle` - can be specified in various ways:
* **summary-only**: displays a summary in the sidebar navigation item. (**default**)
* **path-only**: displays a path in the sidebar navigation item.
* **id-only**: displays the operation id with a fallback to the path in the sidebar navigation item.
## Advanced usage of standalone version ### `<redoc>` theme object
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object: * `spacing`
```js * `unit`: 5 # main spacing unit used in autocomputed theme values later
Redoc.init(specOrSpecUrl, options, element, callback?) * `sectionHorizontal`: 40 # Horizontal section padding. COMPUTED: spacing.unit * 8
``` * `sectionVertical`: 40 # Horizontal section padding. COMPUTED: spacing.unit * 8
* `breakpoints` # breakpoints for switching three/two and mobile view layouts
- `specOrSpecUrl` is either JSON object with specification or an URL to the spec in `JSON` or `YAML` format * `small`: '50rem'
- `options` [options object](#redoc-options-object) * `medium`: '85rem'
- `element` DOM element to put ReDoc into * `large`: '105rem'
- `callback` (optional) - callback to be called after Redoc has been fully rendered. It is also called also on errors with error as the first argument * `colors`
* `tonalOffset`: 0.3 # default tonal offset used in computations
```js * `typography`
Redoc.init('http://petstore.swagger.io/v2/swagger.json', { * `fontSize`: '14px'
scrollYOffset: 50 * `lineHeight`: '1.5em'
}, document.getElementById('redoc-container')) * `fontWeightRegular`: '400'
``` * `fontWeightBold`: '600'
* `fontWeightLight`: '300'
* `fontFamily`: 'Roboto, sans-serif'
* `smoothing`: 'antialiased'
* `optimizeSpeed`: true
* `headings`
* `fontFamily`: 'Montserrat, sans-serif'
* `fontWeight`: '400'
* `lineHeight`: '1.6em'
* `code` # inline code styling
* `fontSize`: '13px'
* `fontFamily`: 'Courier, monospace'
* `lineHeight`: # COMPUTED: typography.lineHeight
* `fontWeight`: # COMPUTED: typography.fontWeightRegular
* `color`: '#e53935'
* `backgroundColor`: 'rgba(38, 50, 56, 0.05)'
* `wrap`: false # whether to break word for inline blocks (otherwise they can overflow)
* `links`
* `color`: # COMPUTED: colors.primary.main
* `visited`: # COMPUTED: typography.links.color
* `hover`: # COMPUTED: lighten(0.2 typography.links.color)
* `sidebar`
* `width`: '260px'
* `backgroundColor`: '#fafafa'
* `textColor`: '#333333'
* `activeTextColor`: # COMPUTED: theme.sidebar.textColor (if set by user) or theme.colors.primary.main
* `groupItems` # Group headings
* `activeBackgroundColor`: # COMPUTED: theme.sidebar.backgroundColor
* `activeTextColor`: # COMPUTED: theme.sidebar.activeTextColor
* `textTransform`: 'uppercase'
* `level1Items` # Level 1 items like tags or section 1st level items
* `activeBackgroundColor`: # COMPUTED: theme.sidebar.backgroundColor
* `activeTextColor`: # COMPUTED: theme.sidebar.activeTextColor
* `textTransform`: 'none'
* `arrow` # sidebar arrow
* `size`: '1.5em'
* `color`: # COMPUTED: theme.sidebar.textColor
* `logo`
* `maxHeight`: # COMPUTED: sidebar.width
* `maxWidth`: # COMPUTED: sidebar.width
* `gutter`: '2px' # logo image padding
* `rightPanel`
* `backgroundColor`: '#263238'
* `width`: '40%'
* `textColor`: '#ffffff'
* `fab`
* `backgroundColor`: '#263238'
* `color`: '#ffffff'
----------- -----------
## Development ## Development

View File

@ -3,20 +3,32 @@
**[ReDoc](https://github.com/Redocly/redoc)'s Command Line Interface** **[ReDoc](https://github.com/Redocly/redoc)'s Command Line Interface**
## Installation ## Installation
You can use redoc cli by installing `redoc-cli` globally or using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b).
You can use `redoc-cli` by installing [the package](https://www.npmjs.com/package/redoc-cli) globally,
or using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b).
## Usage ## Usage
Two following commands are available: The two following commands are available:
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc. Supports SSR mode (`--ssr`) and can watch the spec (`--watch`) - `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc.
- `redoc-cli bundle [spec]` - bundles spec and ReDoc into **zero-dependency** HTML file. Supports a server-side rendering mode (`--ssr`)
and can watch the spec (`--watch`) to automatically reload the page whenever it changes.\
Deprecated. Use `npx @redocly/openapi-cli preview-docs [spec]`
- `redoc-cli bundle [spec]` - bundles `spec` and Redoc into a **zero-dependency** HTML file.\
Deprecated. Use Use "build" command instead.
- `redoc-cli build [spec]` - build `spec` and Redoc into a **zero-dependency** HTML file.
Some examples: Some examples:
- Bundle with main color changed to `orange`: <br> `$ redoc-cli bundle [spec] --options.theme.colors.primary.main=orange` - Bundle with the main color changed to `orange`:<br/>
- Serve with `nativeScrollbars` option set to true: <br> `$ redoc-cli serve [spec] --options.nativeScrollbars` `$ redoc-cli build [spec] --options.theme.colors.primary.main=orange`
- 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` - Serve with the `nativeScrollbars` option set to true:<br/>
- Bundle using custom template and add custom `templateOptions`: <br> `$ redoc-cli bundle [spec] -t custom.hbs --templateOptions.metaDescription "Page meta description"` `$ redoc-cli serve [spec] --options.nativeScrollbars`
- Bundle using a custom [Handlebars](https://handlebarsjs.com/) template
(check the [default template](https://github.com/Redocly/redoc/blob/master/cli/template.hbs) for an example):<br/>
`$ redoc-cli build [spec] -t custom.hbs`
- Bundle using a custom template and add custom `templateOptions`:<br/>
`$ redoc-cli build [spec] -t custom.hbs --templateOptions.metaDescription "Page meta description"`
For more details run `redoc-cli --help`. For more details, run `redoc-cli --help`.

View File

@ -6,7 +6,7 @@ import { ServerStyleSheet } from 'styled-components';
import { compile } from 'handlebars'; import { compile } from 'handlebars';
import { createServer, IncomingMessage, ServerResponse } from 'http'; import { createServer, IncomingMessage, ServerResponse } from 'http';
import { dirname, join, resolve } from 'path'; import { dirname, join, resolve, extname as getExtName } from 'path';
import * as zlib from 'zlib'; import * as zlib from 'zlib';
@ -39,9 +39,78 @@ interface Options {
redocOptions?: any; redocOptions?: any;
} }
export const mimeTypes = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.wav': 'audio/wav',
'.mp4': 'video/mp4',
'.woff': 'application/font-woff',
'.ttf': 'application/font-ttf',
'.eot': 'application/vnd.ms-fontobject',
'.otf': 'application/font-otf',
'.wasm': 'application/wasm',
};
const BUNDLES_DIR = dirname(require.resolve('redoc')); const BUNDLES_DIR = dirname(require.resolve('redoc'));
/* tslint:disable-next-line */ const builderForBuildCommand = yargs => {
yargs.positional('spec', {
describe: 'path or URL to your spec',
});
yargs.option('o', {
describe: 'Output file',
alias: 'output',
type: 'string',
default: 'redoc-static.html',
});
yargs.options('title', {
describe: 'Page Title',
type: 'string',
});
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',
default: false,
});
yargs.demandOption('spec');
return yargs;
};
const handlerForBuildCommand = async (argv: any) => {
const config = {
ssr: true,
output: argv.o as string,
cdn: argv.cdn as boolean,
title: argv.title as string,
disableGoogleFont: argv.disableGoogleFont as boolean,
templateFileName: argv.template as string,
templateOptions: argv.templateOptions || {},
redocOptions: getObjectOrJSON(argv.options),
};
try {
await bundle(argv.spec, config);
} catch (e) {
handleError(e);
}
};
YargsParser.command( YargsParser.command(
'serve <spec>', 'serve <spec>',
'start the server', 'start the server',
@ -61,6 +130,12 @@ YargsParser.command(
type: 'boolean', type: 'boolean',
}); });
yargs.option('h', {
alias: 'host',
type: 'string',
default: '127.0.0.1',
});
yargs.option('p', { yargs.option('p', {
alias: 'port', alias: 'port',
type: 'number', type: 'number',
@ -72,6 +147,12 @@ YargsParser.command(
type: 'boolean', type: 'boolean',
}); });
yargs.options('disable-google-font', {
describe: 'Disable Google Font',
type: 'boolean',
default: false,
});
yargs.demandOption('spec'); yargs.demandOption('spec');
return yargs; return yargs;
}, },
@ -80,73 +161,44 @@ YargsParser.command(
ssr: argv.ssr as boolean, ssr: argv.ssr as boolean,
title: argv.title as string, title: argv.title as string,
watch: argv.watch as boolean, watch: argv.watch as boolean,
disableGoogleFont: argv.disableGoogleFont as boolean,
templateFileName: argv.template as string, templateFileName: argv.template as string,
templateOptions: argv.templateOptions || {}, templateOptions: argv.templateOptions || {},
redocOptions: getObjectOrJSON(argv.options), redocOptions: getObjectOrJSON(argv.options),
}; };
console.log(config);
try { try {
await serve(argv.port as number, argv.spec as string, config); await serve(argv.host as string, argv.port as number, argv.spec as string, config);
} catch (e) { } catch (e) {
handleError(e); handleError(e);
} }
}, },
[
res => {
console.log(
`\n⚠ This command is deprecated. Use "npx @redocly/openapi-cli preview-docs petstore.yaml"\n`,
);
return res;
},
],
) )
.command(
'build <spec>',
'build definition into zero-dependency HTML-file',
builderForBuildCommand,
handlerForBuildCommand,
)
.command( .command(
'bundle <spec>', 'bundle <spec>',
'bundle spec into zero-dependency HTML-file', 'bundle spec into zero-dependency HTML-file [deprecated]',
yargs => { builderForBuildCommand,
yargs.positional('spec', { handlerForBuildCommand,
describe: 'path or URL to your spec', [
}); res => {
console.log(`\n⚠ This command is deprecated. Use "build" command instead.\n`);
yargs.option('o', { return res;
describe: 'Output file', },
alias: 'output', ],
type: 'string',
default: 'redoc-static.html',
});
yargs.options('title', {
describe: 'Page Title',
type: 'string',
});
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',
default: false,
});
yargs.demandOption('spec');
return yargs;
},
async (argv: any) => {
const config = {
ssr: true,
output: argv.o as string,
cdn: argv.cdn as boolean,
title: argv.title as string,
disableGoogleFont: argv.disableGoogleFont as boolean,
templateFileName: argv.template as string,
templateOptions: argv.templateOptions || {},
redocOptions: getObjectOrJSON(argv.options),
};
try {
await bundle(argv.spec, config);
} catch (e) {
handleError(e);
}
},
) )
.demandCommand() .demandCommand()
.options('t', { .options('t', {
@ -162,10 +214,9 @@ YargsParser.command(
describe: 'ReDoc options, use dot notation, e.g. options.nativeScrollbars', describe: 'ReDoc options, use dot notation, e.g. options.nativeScrollbars',
}).argv; }).argv;
async function serve(port: number, pathToSpec: string, options: Options = {}) { async function serve(host: string, port: number, pathToSpec: string, options: Options = {}) {
let spec = await loadAndBundleSpec(pathToSpec); let spec = await loadAndBundleSpec(isURL(pathToSpec) ? pathToSpec : resolve(pathToSpec));
let pageHTML = await getPageHTML(spec, pathToSpec, options); let pageHTML = await getPageHTML(spec, pathToSpec, options);
const server = createServer((request, response) => { const server = createServer((request, response) => {
console.time('GET ' + request.url); console.time('GET ' + request.url);
if (request.url === '/redoc.standalone.js') { if (request.url === '/redoc.standalone.js') {
@ -187,9 +238,19 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}); });
} else { } else {
response.writeHead(404); try {
response.write('Not found'); const filePath = join(dirname(pathToSpec), request.url || '');
response.end(); const extname = String(getExtName(filePath)).toLowerCase() as keyof typeof mimeTypes;
const contentType = mimeTypes[extname] || 'application/octet-stream';
respondWithGzip(createReadStream(filePath), request, response, {
'Content-Type': contentType,
});
} catch (e) {
response.writeHead(404);
response.write('Not found');
response.end();
}
} }
console.timeEnd('GET ' + request.url); console.timeEnd('GET ' + request.url);
@ -197,7 +258,7 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
console.log(); console.log();
server.listen(port, () => console.log(`Server started: http://127.0.0.1:${port}`)); server.listen(port, host, () => console.log(`Server started: http://${host}:${port}`));
if (options.watch && existsSync(pathToSpec)) { if (options.watch && existsSync(pathToSpec)) {
const pathToSpecDirectory = resolve(dirname(pathToSpec)); const pathToSpecDirectory = resolve(dirname(pathToSpec));
@ -211,7 +272,7 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
const handlePath = async _path => { const handlePath = async _path => {
try { try {
spec = await loadAndBundleSpec(pathToSpec); spec = await loadAndBundleSpec(resolve(pathToSpec));
pageHTML = await getPageHTML(spec, pathToSpec, options); pageHTML = await getPageHTML(spec, pathToSpec, options);
log('Updated successfully'); log('Updated successfully');
} catch (e) { } catch (e) {
@ -238,7 +299,7 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
async function bundle(pathToSpec, options: Options = {}) { async function bundle(pathToSpec, options: Options = {}) {
const start = Date.now(); const start = Date.now();
const spec = await loadAndBundleSpec(pathToSpec); const spec = await loadAndBundleSpec(isURL(pathToSpec) ? pathToSpec : resolve(pathToSpec));
const pageHTML = await getPageHTML(spec, pathToSpec, { ...options, ssr: true }); const pageHTML = await getPageHTML(spec, pathToSpec, { ...options, ssr: true });
mkdirp.sync(dirname(options.output!)); mkdirp.sync(dirname(options.output!));
@ -293,7 +354,7 @@ async function getPageHTML(
var container = document.getElementById('redoc'); var container = document.getElementById('redoc');
Redoc.${ Redoc.${
ssr ssr
? 'hydrate(__redoc_state, container);' ? 'hydrate(__redoc_state, container)'
: `init("spec.json", ${JSON.stringify(redocOptions)}, container)` : `init("spec.json", ${JSON.stringify(redocOptions)}, container)`
}; };

5398
cli/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

1946
cli/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "redoc-cli", "name": "redoc-cli",
"version": "0.9.12", "version": "0.13.10",
"description": "ReDoc's Command Line Interface", "description": "ReDoc's Command Line Interface",
"main": "index.js", "main": "index.js",
"bin": "index.js", "bin": "index.js",
@ -8,21 +8,20 @@
"author": "Roman Hotsiy <gotsijroman@gmail.com>", "author": "Roman Hotsiy <gotsijroman@gmail.com>",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 8" "node": ">=12.0.0"
}, },
"dependencies": { "dependencies": {
"chokidar": "^3.4.1", "chokidar": "^3.5.1",
"handlebars": "^4.7.6", "handlebars": "^4.7.7",
"isarray": "^2.0.5", "isarray": "^2.0.5",
"mkdirp": "^1.0.4", "mkdirp": "^1.0.4",
"mobx": "^4.2.0", "mobx": "^6.3.2",
"node-libs-browser": "^2.2.1", "node-libs-browser": "^2.2.1",
"react": "^16.13.1", "react": "^17.0.1",
"react-dom": "^16.13.1", "react-dom": "^17.0.1",
"redoc": "^2.0.0-rc.40", "redoc": "2.0.0-rc.66",
"styled-components": "^5.1.1", "styled-components": "^5.3.0",
"tslib": "^2.0.0", "yargs": "^17.3.1"
"yargs": "^15.4.1"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -16,7 +16,8 @@ RUN npm ci --no-optional --ignore-scripts
# copy only required for the build files # copy only required for the build files
COPY src /build/src COPY src /build/src
COPY webpack.config.ts tsconfig.json custom.d.ts /build/ COPY webpack.config.ts tsconfig.json custom.d.ts /build/
COPY config/webpack-utils.ts /build/config/
COPY typings/styled-patch.d.ts /build/typings/styled-patch.d.ts COPY typings/styled-patch.d.ts /build/typings/styled-patch.d.ts
RUN npm run bundle:standalone RUN npm run bundle:standalone
@ -37,6 +38,18 @@ COPY demo/favicon.png /usr/share/nginx/html/
COPY config/docker/nginx.conf /etc/nginx/ COPY config/docker/nginx.conf /etc/nginx/
COPY config/docker/docker-run.sh /usr/local/bin COPY config/docker/docker-run.sh /usr/local/bin
# Provide rights to the root group to write to nginx repositories (needed to run in OpenShift)
RUN chgrp -R 0 /etc/nginx && \
chgrp -R 0 /usr/share/nginx/html && \
chgrp -R 0 /var/cache/nginx && \
chgrp -R 0 /var/log/nginx && \
chgrp -R 0 /var/run && \
chmod -R g+rwX /etc/nginx && \
chmod -R g+rwX /usr/share/nginx/html && \
chmod -R g+rwX /var/cache/nginx && \
chmod -R g+rwX /var/log/nginx && \
chmod -R g+rwX /var/run
EXPOSE 80 EXPOSE 80
CMD ["sh", "/usr/local/bin/docker-run.sh"] CMD ["sh", "/usr/local/bin/docker-run.sh"]

View File

@ -2,6 +2,8 @@
## Usage ## Usage
### Docker
Serve remote spec by URL: Serve remote spec by URL:
docker run -it --rm -p 80:80 \ docker run -it --rm -p 80:80 \
@ -12,13 +14,33 @@ Serve local file:
docker run -it --rm -p 80:80 \ docker run -it --rm -p 80:80 \
-v $(pwd)/demo/swagger.yaml:/usr/share/nginx/html/swagger.yaml \ -v $(pwd)/demo/swagger.yaml:/usr/share/nginx/html/swagger.yaml \
-e SPEC_URL=swagger.yaml redocly/redoc -e SPEC_URL=swagger.yaml redocly/redoc
Serve local file and watch for updates: Serve local file and watch for updates:
docker run -it --rm -p 80:80 \ docker run -it --rm -p 80:80 \
-v $(pwd)/demo/:/usr/share/nginx/html/swagger/ \ -v $(pwd)/demo/:/usr/share/nginx/html/swagger/ \
-e SPEC_URL=swagger/swagger.yaml redocly/redoc -e SPEC_URL=swagger/swagger.yaml redocly/redoc
### OpenShift
To quote [OpenShift Container Platform-Specific Guidelines](https://docs.openshift.com/container-platform/3.11/creating_images/guidelines.html#openshift-specific-guidelines):
> Support Arbitrary User IDs
>
> By default, OpenShift Container Platform runs containers using an arbitrarily assigned user ID. This provides additional security against processes escaping the container due to a container engine vulnerability and thereby achieving escalated permissions on the host node.
>
> For an image to support running as an arbitrary user, directories and files that may be written to by processes in the image should be owned by the root group and be read/writable by that group. Files to be executed should also have group execute permissions.
To comply with those requirements the `Dockerfile` contains instructions to adapt the rights for the folders:
- `/etc/nginx` because the `docker-run.sh` script modifies it at startup time
- `/usr/share/nginx/html` because the `docker-run.sh` script modifies it at startup time
- `/var/cache/nginx` because the Nginx process writes to it
- `/var/log/nginx` because the Nginx process writes to it
- `/var/run` because the Nginx process writes to it
Another issue with OpenShift is that the default exposed port `80` cannot be used as it is restricted. So one needs to use another port like `8080` (using the `PORT` configuration as described below), and then to configure the `container spec` accordingly.
## Runtime configuration options ## Runtime configuration options
- `PAGE_TITLE` (default `"ReDoc"`) - page title - `PAGE_TITLE` (default `"ReDoc"`) - page title

View File

@ -1,26 +1,28 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>%PAGE_TITLE%</title>
<link rel="icon" href="%BASE_PATH%%PAGE_FAVICON%" />
<style>
body {
margin: 0;
padding: 0;
}
<head> redoc {
<meta charset="UTF-8" /> display: block;
<title>%PAGE_TITLE%</title> }
<link rel="icon" href="%BASE_PATH%%PAGE_FAVICON%"> </style>
<style> <link
body { href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
margin: 0; rel="stylesheet"
padding: 0; />
} </head>
redoc {
display: block;
}
</style>
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
</head>
<body>
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
<script src="%BASE_PATH%redoc.standalone.js"></script>
</body>
<body>
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
<script src="%BASE_PATH%redoc.standalone.js"></script>
</body>
</html> </html>

5
config/webpack-utils.ts Normal file
View File

@ -0,0 +1,5 @@
import * as webpack from 'webpack';
export function webpackIgnore(regexp) {
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash.noop'));
}

2
custom.d.ts vendored
View File

@ -23,3 +23,5 @@ declare var reactHotLoaderGlobal: any;
interface Element { interface Element {
scrollIntoViewIfNeeded(centerIfNeeded?: boolean): void; scrollIntoViewIfNeeded(centerIfNeeded?: boolean): void;
} }
type GenericObject = Record<string, any>;

View File

@ -31,7 +31,7 @@ const DropDownList = styled.ul`
list-style: none; list-style: none;
margin: 4px 0 0 0; margin: 4px 0 0 0;
padding: 5px 0; padding: 5px 0;
font-family: 'Lato'; font-family: Roboto,sans-serif;
overflow: hidden; overflow: hidden;
`; `;
@ -211,6 +211,7 @@ export default class ComboBox extends React.Component<ComboBoxProps, ComboBoxSta
onFocus={this.open} onFocus={this.open}
onBlur={this.handleBlur} onBlur={this.handleBlur}
onKeyDown={this.handleKeyPress} onKeyDown={this.handleKeyPress}
aria-label="URL to an OpenAPI definition to try"
/> />
<Button onClick={this.handleTryItClick}> TRY IT </Button> <Button onClick={this.handleTryItClick}> TRY IT </Button>
{open && <DropDownList>{options.map(this.renderOption)}</DropDownList>} {open && <DropDownList>{options.map(this.renderOption)}</DropDownList>}

View File

@ -0,0 +1,52 @@
import * as yaml from 'js-yaml';
import * as React from 'react';
import { ChangeEvent, RefObject, useRef } from 'react';
import styled from '../../src/styled-components';
const Button = styled.button`
background-color: #fff;
color: #333;
padding: 2px 10px;
touch-action: manipulation;
cursor: pointer;
user-select: none;
border: 1px solid #ccc;
font-size: 16px;
height: 28px;
box-sizing: border-box;
vertical-align: middle;
line-height: 1;
outline: none;
white-space: nowrap;
@media (max-width: 699px) {
display: none;
}
`;
function FileInput(props: { onUpload }) {
const hiddenFileInput: RefObject<HTMLInputElement> = useRef<HTMLInputElement>(null);
const handleClick = () => {
if (hiddenFileInput && hiddenFileInput.current) {
hiddenFileInput.current.click();
}
};
const uploadFile = (event: ChangeEvent<HTMLInputElement>) => {
const file = (event.target as HTMLInputElement).files![0];
const reader = new FileReader();
reader.onload = () => {
props.onUpload(yaml.load(reader.result));
};
reader.readAsText(file);
};
return (
<span>
<Button onClick={handleClick}>Upload a file</Button>
<input type="file" style={{ display: 'none' }} onChange={uploadFile} ref={hiddenFileInput} />
</span>
);
}
export default FileInput;

View File

@ -4,23 +4,26 @@ import styled from 'styled-components';
import { resolve as urlResolve } from 'url'; import { resolve as urlResolve } from 'url';
import { RedocStandalone } from '../src'; import { RedocStandalone } from '../src';
import ComboBox from './ComboBox'; import ComboBox from './ComboBox';
import FileInput from './components/FileInput';
const DEFAULT_SPEC = 'openapi.yaml';
const NEW_VERSION_SPEC = 'openapi-3-1.yaml';
const demos = [ const demos = [
{ value: NEW_VERSION_SPEC, label: 'Petstore OpenAPI 3.1' },
{ value: 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml', label: 'Instagram' }, { value: 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml', label: 'Instagram' },
{ {
value: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml', value: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/openapi.yaml',
label: 'Google Calendar', label: 'Google Calendar',
}, },
{ value: 'https://api.apis.guru/v2/specs/slack.com/1.2.0/swagger.yaml', label: 'Slack' }, { value: 'https://api.apis.guru/v2/specs/slack.com/1.7.0/openapi.yaml', label: 'Slack' },
{ value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/swagger.yaml', label: 'Zoom.us' }, { value: 'https://api.apis.guru/v2/specs/zoom.us/2.0.0/openapi.yaml', label: 'Zoom.us' },
{ value: 'https://docs.graphhopper.com/openapi.json', label: 'GraphHopper' }, { value: 'https://docs.graphhopper.com/openapi.json', label: 'GraphHopper' },
]; ];
const DEFAULT_SPEC = 'openapi.yaml';
class DemoApp extends React.Component< class DemoApp extends React.Component<
{}, {},
{ specUrl: string; dropdownOpen: boolean; cors: boolean } { spec: object | undefined; specUrl: string; dropdownOpen: boolean; cors: boolean }
> { > {
constructor(props) { constructor(props) {
super(props); super(props);
@ -38,13 +41,25 @@ class DemoApp extends React.Component<
} }
this.state = { this.state = {
spec: undefined,
specUrl: url, specUrl: url,
dropdownOpen: false, dropdownOpen: false,
cors, cors,
}; };
} }
handleUploadFile = (spec: object) => {
this.setState({
spec,
specUrl: '',
});
};
handleChange = (url: string) => { handleChange = (url: string) => {
if (url === NEW_VERSION_SPEC) {
this.setState({ cors: false });
0;
}
this.setState({ this.setState({
specUrl: url, specUrl: url,
}); });
@ -72,16 +87,20 @@ class DemoApp extends React.Component<
let proxiedUrl = specUrl; let proxiedUrl = specUrl;
if (specUrl !== DEFAULT_SPEC) { if (specUrl !== DEFAULT_SPEC) {
proxiedUrl = cors proxiedUrl = cors
? '\\\\cors.apis.guru/' + urlResolve(window.location.href, specUrl) ? '\\\\cors.redoc.ly/' + urlResolve(window.location.href, specUrl)
: specUrl; : specUrl;
} }
return ( return (
<> <>
<Heading> <Heading>
<a href="."> <a href=".">
<Logo src="https://github.com/Redocly/redoc/raw/master/docs/images/redoc-logo.png" /> <Logo
src="https://github.com/Redocly/redoc/raw/master/docs/images/redoc-logo.png"
alt="Redoc logo"
/>
</a> </a>
<ControlsContainer> <ControlsContainer>
<FileInput onUpload={this.handleUploadFile} />
<ComboBox <ComboBox
placeholder={'URL to a spec to try'} placeholder={'URL to a spec to try'}
options={demos} options={demos}
@ -102,6 +121,7 @@ class DemoApp extends React.Component<
/> />
</Heading> </Heading>
<RedocStandalone <RedocStandalone
spec={this.state.spec}
specUrl={proxiedUrl} specUrl={proxiedUrl}
options={{ scrollYOffset: 'nav', untrustedSpec: true }} options={{ scrollYOffset: 'nav', untrustedSpec: true }}
/> />
@ -146,7 +166,7 @@ const Heading = styled.nav`
display: flex; display: flex;
align-items: center; align-items: center;
font-family: 'Lato'; font-family: Roboto, sans-serif;
`; `;
const Logo = styled.img` const Logo = styled.img`

1271
demo/openapi-3-1.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -377,7 +377,9 @@ paths:
application/xml: application/xml:
schema: schema:
type: array type: array
maxItems: 999
items: items:
maxItems: 111
$ref: '#/components/schemas/Pet' $ref: '#/components/schemas/Pet'
'400': '400':
description: Invalid tag value description: Invalid tag value
@ -1193,7 +1195,7 @@ x-webhooks:
summary: New pet summary: New pet
description: Information about a new pet in the systems description: Information about a new pet in the systems
operationId: newPet operationId: newPet
tags: tags:
- pet - pet
requestBody: requestBody:
content: content:
@ -1202,4 +1204,4 @@ x-webhooks:
$ref: "#/components/schemas/Pet" $ref: "#/components/schemas/Pet"
responses: responses:
"200": "200":
description: Return a 200 status to indicate that the data was received successfully description: Return a 200 status to indicate that the data was received successfully

View File

@ -1,21 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
// tslint:disable-next-line import type { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
import { AppContainer } from 'react-hot-loader'; import RedocStandalone from './hot';
// import DevTools from 'mobx-react-devtools';
import { Redoc, RedocProps } from '../../src/components/Redoc/Redoc';
import { AppStore } from '../../src/services/AppStore';
import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec';
const renderRoot = (props: RedocProps) =>
render(
<AppContainer>
<Redoc {...props} />
</AppContainer>,
document.getElementById('example'),
);
const big = window.location.search.indexOf('big') > -1; const big = window.location.search.indexOf('big') > -1;
const swagger = window.location.search.indexOf('swagger') > -1; const swagger = window.location.search.indexOf('swagger') > -1;
@ -25,30 +11,6 @@ const userUrl = window.location.search.match(/url=(.*)$/);
const specUrl = const specUrl =
(userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml'); (userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml');
let store;
const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 }; const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 };
async function init() { render(<RedocStandalone specUrl={specUrl} options={options} />, document.getElementById('example'));
const spec = await loadAndBundleSpec(specUrl);
store = new AppStore(spec, specUrl, options);
renderRoot({ store });
}
init();
if (module.hot) {
const reload = (reloadStore = false) => async () => {
if (reloadStore) {
// create a new Store
store.dispose();
const state = await store.toJS();
store = AppStore.fromJS(state);
}
renderRoot({ store });
};
module.hot.accept(['../../src/components/Redoc/Redoc'], reload());
module.hot.accept(['../../src/services/AppStore'], reload(true));
}

10
demo/playground/hot.tsx Normal file
View File

@ -0,0 +1,10 @@
import * as React from 'react';
// eslint-disable-next-line import/no-internal-modules
import { hot } from 'react-hot-loader/root';
import { RedocStandalone as RedocStandaloneOrig, RedocStandaloneProps } from '../../src';
const RedocStandalone = function (props: RedocStandaloneProps) {
return <RedocStandaloneOrig {...props} />;
}
export default hot(RedocStandalone);

View File

@ -1,14 +1,12 @@
import { renderToString } from 'react-dom/server'; import { renderToString } from 'react-dom/server';
import * as React from 'react'; import * as React from 'react';
import { ServerStyleSheet } from 'styled-components'; import { ServerStyleSheet } from 'styled-components';
// @ts-ignore
import { Redoc, createStore } from '../../'; import { Redoc, createStore } from '../../';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
const yaml = require('yaml-js'); const yaml = require('js-yaml');
const http = require('http'); const http = require('http');
const url = require('url');
const fs = require('fs'); const fs = require('fs');
const PORT = 9999; const PORT = 9999;
@ -18,8 +16,8 @@ const server = http.createServer(async (request, response) => {
if (request.url === '/redoc.standalone.js') { if (request.url === '/redoc.standalone.js') {
fs.createReadStream('bundles/redoc.standalone.js', 'utf8').pipe(response); fs.createReadStream('bundles/redoc.standalone.js', 'utf8').pipe(response);
} else if (request.url === '/') { } else if (request.url === '/') {
const spec = yaml.load(readFileSync(resolve(__dirname, '../openapi.yaml'))); const spec = yaml.load(readFileSync(resolve(__dirname, '../openapi.yaml'), 'utf-8'));
let store = await createStore(spec, 'path/to/spec.yaml'); const store = await createStore(spec, 'path/to/spec.yaml');
const sheet = new ServerStyleSheet(); const sheet = new ServerStyleSheet();

View File

@ -1,9 +1,9 @@
import * as CopyWebpackPlugin from 'copy-webpack-plugin'; import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
import * as HtmlWebpackPlugin from 'html-webpack-plugin'; import * as HtmlWebpackPlugin from 'html-webpack-plugin';
import { compact } from 'lodash';
import { resolve } from 'path'; import { resolve } from 'path';
import * as webpack from 'webpack'; import * as webpack from 'webpack';
import { webpackIgnore } from '../config/webpack-utils';
const VERSION = JSON.stringify(require('../package.json').version); const VERSION = JSON.stringify(require('../package.json').version);
const REVISION = JSON.stringify( const REVISION = JSON.stringify(
@ -14,38 +14,6 @@ function root(filename) {
return resolve(__dirname + '/' + filename); return resolve(__dirname + '/' + filename);
} }
const tsLoader = (env) => ({
loader: 'ts-loader',
options: {
compilerOptions: {
module: env.bench ? 'esnext' : 'es2015',
declaration: false,
},
},
});
const babelLoader = () => ({
loader: 'babel-loader',
options: {
generatorOpts: {
decoratorsBeforeExport: true,
},
plugins: compact([
['@babel/plugin-syntax-typescript', { isTSX: true }],
['@babel/plugin-syntax-decorators', { legacy: true }],
'@babel/plugin-syntax-dynamic-import',
'@babel/plugin-syntax-jsx',
]),
},
});
const babelHotLoader = {
loader: 'babel-loader',
options: {
plugins: ['react-hot-loader/babel'],
},
};
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
entry: [ entry: [
root('../src/polyfills.ts'), root('../src/polyfills.ts'),
@ -57,6 +25,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
: 'index.tsx', : 'index.tsx',
), ),
], ],
target: 'web',
output: { output: {
filename: 'redoc-demo.bundle.js', filename: 'redoc-demo.bundle.js',
path: root('dist'), path: root('dist'),
@ -64,15 +33,24 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
}, },
devServer: { devServer: {
contentBase: __dirname, static: __dirname,
watchContentBase: true,
port: 9090, port: 9090,
disableHostCheck: true, hot: true,
stats: 'minimal', historyApiFallback: true,
open: true,
},
stats: {
children: true,
}, },
resolve: { resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'], extensions: ['.ts', '.tsx', '.js', '.json'],
fallback: {
path: require.resolve('path-browserify'),
buffer: require.resolve('buffer'),
http: false,
fs: false,
os: false,
},
alias: alias:
mode !== 'production' mode !== 'production'
? { ? {
@ -81,10 +59,6 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
: {}, : {},
}, },
node: {
fs: 'empty',
},
performance: false, performance: false,
externals: { externals: {
@ -97,39 +71,30 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
module: { module: {
rules: [ rules: [
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' }, { test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
{ {
test: /\.tsx?$/, test: /\.(tsx?|[cm]?js)$/,
use: compact([ loader: 'esbuild-loader',
mode !== 'production' ? babelHotLoader : undefined, options: {
tsLoader(env), loader: 'tsx',
babelLoader(), target: 'es2015',
]), tsconfigRaw: require('../tsconfig.json'),
},
exclude: [/node_modules/], exclude: [/node_modules/],
}, },
{ {
test: /\.css$/, test: /\.css$/,
use: { use: [
loader: 'css-loader', 'style-loader',
options: { 'css-loader',
sourceMap: true, {
}, loader: 'esbuild-loader',
}, options: {
}, loader: 'css',
{ minify: true,
test: /node_modules\/(swagger2openapi|reftools|oas-resolver|oas-kit-common|oas-schema-walker)\/.*\.js$/,
use: {
loader: 'ts-loader',
options: {
transpileOnly: true,
instance: 'ts2js-transpiler-only',
compilerOptions: {
allowJs: true,
declaration: false,
}, },
}, },
}, ],
}, },
], ],
}, },
@ -137,9 +102,12 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
new webpack.DefinePlugin({ new webpack.DefinePlugin({
__REDOC_VERSION__: VERSION, __REDOC_VERSION__: VERSION,
__REDOC_REVISION__: REVISION, __REDOC_REVISION__: REVISION,
'process.env': '{}',
'process.platform': '"browser"',
'process.stdout': 'null',
}), }),
new webpack.NamedModulesPlugin(), // new webpack.NamedModulesPlugin(),
new webpack.optimize.ModuleConcatenationPlugin(), // new webpack.optimize.ModuleConcatenationPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: env.playground template: env.playground
? 'demo/playground/index.html' ? 'demo/playground/index.html'
@ -147,16 +115,15 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
? 'benchmark/index.html' ? 'benchmark/index.html'
: 'demo/index.html', : 'demo/index.html',
}), }),
new ForkTsCheckerWebpackPlugin(), new webpack.ProvidePlugin({
ignore(/js-yaml\/dumper\.js$/), Buffer: ['buffer', 'Buffer'],
ignore(/json-schema-ref-parser\/lib\/dereference\.js/), }),
ignore(/^\.\/SearchWorker\.worker$/), new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
webpackIgnore(/js-yaml\/dumper\.js$/),
webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
webpackIgnore(/^\.\/SearchWorker\.worker$/),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: ['demo/openapi.yaml'], patterns: ['demo/openapi.yaml'],
}), }),
], ],
}); });
function ignore(regexp) {
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash/noop.js'));
}

114
docs/deployment/cli.md Normal file
View File

@ -0,0 +1,114 @@
---
title: Use the Redoc CLI
redirectFrom:
- /docs/quickstart/cli/
---
# How to use the Redoc CLI
With Redoc's command-line interface you can bundle your OpenAPI definition and API documentation
(made with Redoc) into a zero-dependency HTML file and locally render your
OpenAPI definition with Redoc.
## Step 1 - Install Redoc CLI
You can install the `redoc-cli` package globally using one of the following package managers:
- [npm](https://docs.npmjs.com/about-npm)
- [yarn](https://classic.yarnpkg.com/en/docs/getting-started)
Or you can install `redoc-cli` using [npx](https://www.freecodecamp.org/news/npm-vs-npx-whats-the-difference/).
### Install Redoc CLI with yarn
To install the `redoc-cli` package globally with yarn:
```bash
yarn global add redoc-cli
```
### Install Redoc with npm
To install the `redoc-cli` package globally with npm:
```bash
npm i -g redoc-cli
```
### Install with `npx`
To install the `redoc-cli` package locally with `npx`, navigate to your project
directory in your terminal, then use the following command:
```bash
npx redoc-cli
```
## Step 2 - Use the CLI
### Redoc CLI commands
The CLI includes the following commands:
- **`redoc-cli serve [spec]`:** Starts a local server with Redoc. You must include the required parameter, spec, which is
a reference to an OpenAPI definition. Options include:
- `--ssr`: Implements a server-side rendering model.
- `--watch`: Automatically reloads the server while you edit your OpenAPI definition.
- `--options`: Customizes your output using [Redoc options](https://redocly.com/docs/api-reference-docs/configuration/).
To add nested options, use dot notation.
- **`redoc-cli bundle [spec]`:** Bundles `spec` and Redoc into a zero-dependency HTML file. Options include:
- `-t` or `--template`: Uses custom [Handlebars](https://handlebarsjs.com/) templates to render your OpenAPI definition.
- `--templateOptions`: Adds template options you want to pass to your
custom Handlebars template. To add options, use dot notation.
- **`--help`:** Prints help text for the Redoc CLI commands and options.
- **`--version`:** Prints the version of the `redoc-cli` package you have installed.
### Redoc CLI examples
#### Bundle
Bundle with the main color changed to `orange`:
```bash
redoc-cli bundle openapi.yaml --options.theme.colors.primary.main=orange
```
Bundle using a custom Handlebars template and add custom `templateOptions`:
```bash
redoc-cli bundle http://petstore.swagger.io/v2/swagger.json -t custom.hbs --templateOptions.metaDescription "Page meta description"
```
Sample Handlebars template:
```handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8" />
<title>{{title}}</title>
<!-- needed for adaptive design -->
<meta description="{{{templateOptions.metaDescription}}}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
padding: 0;
margin: 0;
}
</style>
{{{redocHead}}}
{{#unless disableGoogleFont}}<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">{{/unless}}
</head>
<body>
{{{redocHTML}}}
</body>
</html>
```
#### Serve
Serve with the `nativeScrollbars` option set to `true`:
```bash
redoc-cli serve openapi/dist.yaml --options.nativeScrollbars
```

41
docs/deployment/docker.md Normal file
View File

@ -0,0 +1,41 @@
---
title: Use the Redoc Docker image
redirectFrom:
- /docs/quickstart/docker/
---
# How to use the Redoc Docker image
Redoc is available as a pre-built Docker image in [Docker Hub](https://hub.docker.com/r/redocly/redoc/).
If you have [Docker](https://docs.docker.com/get-docker/) installed, pull the image with the following command:
```docker
docker pull redocly/redoc
```
Then run the image with the following command:
```docker
docker run -p 8080:80 redocly/redoc
```
The preview starts on port 8080, based on the port used in the command,
and can be accessed at `http://localhost:8080`.
To exit the preview, use `control+C`.
By default Redoc starts with a demo Swagger Petstore OpenAPI definition located at
http://petstore.swagger.io/v2/swagger.json. You can update this URL using
the environment variable `SPEC_URL`.
For example:
```bash
docker run -p 8080:80 -e SPEC_URL=https://api.example.com/openapi.json redocly/redoc
```
## Create a Dockerfile
You can also create a Dockerfile with some predefined environment variables. Check out
a sample [Dockerfile](https://github.com/Redocly/redoc/blob/master/config/docker/Dockerfile)
in our code repo.

123
docs/deployment/html.md Normal file
View File

@ -0,0 +1,123 @@
---
title: Use the Redoc HTML element
redirectFrom:
- /docs/quickstart/html/
---
# How to use the Redoc HTML element
## Step 1 - Install Redoc
You can install Redoc using one of the following package managers:
- [npm](https://docs.npmjs.com/about-npm)
- [yarn](https://classic.yarnpkg.com/en/docs/getting-started)
:::attention Initialize your package manager
If you do not have a `package.json` file in your project directory,
you need to add one by initializing npm or yarn in your project. Use the command `npm init` for npm,
or `yarn init` for yarn. These initialization commands will lead you through the process
of creating a `package.json` file in your project.
For more information, see
[Creating a package.json file](https://docs.npmjs.com/creating-a-package-json-file)
in the npm documentation or [Yarn init](https://classic.yarnpkg.com/en/docs/cli/init/)
in the yarn documentation.
:::
### Install Redoc with yarn
After navigating to your project directory in your terminal, use the following command:
```bash
yarn add redoc
```
### Install Redoc with npm
After navigating to your project directory in your terminal, use the following command:
```bash
npm i redoc
```
## Step 2 - Reference the Redoc script
You can reference the Redoc script using either a link to the files hosted on a CDN
or the files located in your `node modules` folder.
### CDN link
To reference the Redoc script with a CDN link:
```html
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
```
### Node modules link
To reference the Redoc script with a node modules link:
```html
<script src="node_modules/redoc/bundles/redoc.standalone.js"> </script>
```
## Step 3 - Add the <redoc> element
You can add the <redoc> element to your HTML page and reference your OpenAPI
definition using the `spec-url` attribute, or you can initialize Redoc using
a globally exposed Redoc object.
### The `spec-url` attribute
To add the <redoc> element with the `spec-url` attribute:
```html
<redoc spec-url="url/to/your/spec"></redoc>
```
#### Examples
```html
<redoc spec-url="http://petstore.swagger.io/v2/swagger.json"></redoc>
```
You can also use a local file (JSON or YAML) in your project, for instance:
```html
<redoc spec-url="dist.json"></redoc>
```
### The Redoc object
To add the <redoc> element with a globally exposed Redoc object:
```js
Redoc.init(specOrSpecUrl, options, element, callback)
```
- `specOrSpecUrl`: Either a JSON object with the OpenAPI definition or a URL to the
definition in JSON or YAML format.
- `options`: See [options object](https://redocly.com/docs/api-reference-docs/configuration/) reference.
- `element`: DOM element Redoc will be inserted into.
- `callback`(optional): Callback to be called after Redoc has been fully rendered.
It is also called on errors with `error` as the first argument.
#### Examples
```html
<script>
Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
scrollYOffset: 50
}, document.getElementById('redoc-container'))
</script>
```
You can also use a local file (JSON or YAML) in your project, for instance:
```html
<script>
Redoc.init('dist.yaml', {
scrollYOffset: 50
}, document.getElementById('redoc-container'))
</script>
```

112
docs/deployment/intro.md Normal file
View File

@ -0,0 +1,112 @@
---
title: Redoc deployment guide
redirectFrom:
- /docs/quickstart/intro/
---
# Redoc deployment guide
Redoc offers multiple options for rendering your OpenAPI definition.
You should select the option that best fits your needs.
The following options are supported:
- **[Live demo](https://redocly.github.io/redoc/):**
The live demo offers a fast way to see how your OpenAPI will render with Redoc.
A version of the Swagger Petstore API is displayed by default. To test it with your own OpenAPI definition, enter the URL for your
definition and select **TRY IT**.
- **[HTML element](./html.md):**
Using the HTML element works well for typical website deployments.
- **[React component](./react.md):**
Using the React component is an option for users with a React-based application.
- **[Docker image](./docker.md):**
Using the Docker image works in a container-based deployment.
- **[CLI](./cli.md):**
Using the CLI is an option for users who prefer to use a command-line interface.
## Before you start
### OpenAPI definition
You will need an OpenAPI definition. For testing purposes, you can use one of the following sample OpenAPI definitions:
- OpenAPI 3.0
- [Rebilly Users OpenAPI Definition](https://raw.githubusercontent.com/Rebilly/api-definitions/main/openapi/users.yaml)
- [Swagger Petstore Sample OpenAPI Definition](https://petstore3.swagger.io/api/v3/openapi.json)
- OpenAPI 2.0
- [Thingful OpenAPI Definition](https://raw.githubusercontent.com/thingful/openapi-spec/master/spec/swagger.yaml)
- [Fitbit Plus OpenAPI Definition](https://raw.githubusercontent.com/TwineHealth/TwineDeveloperDocs/master/spec/swagger.yaml)
:::info OpenAPI specification
For more information on the OpenAPI specification, refer to the [Learning OpenAPI 3](https://redocly.com/docs/resources/learning-openapi/)
section in the documentation.
:::
### How to run Redoc locally
If you want to view your Redoc output locally, you can simulate an HTTP server.
#### Redocly OpenAPI CLI
Redocly OpenAPI CLI is an open source command-line tool that includes a command
for simulating an HTTP server to provide a preview of your OpenAPI definition locally.
If you have [OpenAPI CLI](https://redocly.com/docs/cli/#installation-and-usage) installed, `cd` into your
project directory and run the following command:
```bash
openapi preview-docs openapi.yaml
```
Replace `openapi.yaml` in the example command with the file path to your OpenAPI definition.
By default, without providing a port, the preview starts on port 8080, and can be accessed at `http://localhost:8080`.
To exit the preview, use `control+C`.
You can alter the port if you are using 8080 already, for example:
```bash
openapi preview-docs -p 8888 openapi.yaml
```
Replace `openapi.yaml` in the example command with the file path to your OpenAPI definition.
For more information about the `preview-docs` command, refer to
[OpenAPI CLI commands](https://redocly.com/docs/cli/commands/preview-docs/#preview-docs) in the OpenAPI CLI documentation.
#### Python
If you have [Python 3](https://www.python.org/downloads/) installed, `cd` into your
project directory and run the following command:
```python
python3 -m http.server
```
If you have [Python 2](https://www.python.org/downloads/) installed, `cd` into your
project directory and run the following command:
```python
python -m SimpleHTTPServer 8000
```
The output after entering the command provides the local URL where the preview can be accessed.
To exit the preview, use `control-C`.
#### Node.js
If you have [Node.js](https://nodejs.org/en/download/) installed, install `http-server`
using the following npm command:
```bash
npm install -g http-server
```
Then, `cd` into your project directory and run the following command:
```node
http-server
```
The output after entering the command provides the local URL where the preview can be accessed.
To exit the preview, use `control-C`.

80
docs/deployment/react.md Normal file
View File

@ -0,0 +1,80 @@
---
title: Use the Redoc React component
redirectFrom:
- /docs/quickstart/react/
---
# How to use the Redoc React component
## Before you start
Install the following dependencies required by Redoc if you do not already have them installed:
- `react`
- `react-dom`
- `mobx`
- `styled-components`
- `core-js`
If you have npm installed, you can install these dependencies using the following command:
```js
npm i react react-dom mobx styled-components core-js
```
## Step 1 - Import the `RedocStandalone` component
```js
import { RedocStandalone } from 'redoc';
```
## Step 2 - Use the component
You can either link to your OpenAPI definition with a URL, using the following format:
```react
<RedocStandalone specUrl="url/to/your/spec"/>
```
Or you can pass your OpenAPI definition as an object, using the following format:
```js
<RedocStandalone spec={/* spec as an object */}/>
```
## Optional - Pass options
Options can be passed into the RedocStandalone component to alter how it renders.
For example:
```js
<RedocStandalone
specUrl="http://petstore.swagger.io/v2/swagger.json"
options={{
nativeScrollbars: true,
theme: { colors: { primary: { main: '#dd5522' } } },
}}
/>
```
For more information on configuration options, refer to the
[Configuration options for Reference docs](https://redocly.com/docs/api-reference-docs/configuration/)
section of the documentation. Options available for Redoc are noted,
"Supported in Redoc CE".
## Optional - Specify `onLoaded` callback
You can also specify the `onLoaded` callback, which is called each time Redoc
is fully rendered or when an error occurs (with an error as the first argument).
```js
<RedocStandalone
specUrl="http://petstore.swagger.io/v2/swagger.json"
onLoaded={error => {
if (!error) {
console.log('Yay!');
}
}}
/>
```

BIN
docs/images/redoc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

54
docs/quickstart.md Normal file
View File

@ -0,0 +1,54 @@
---
title: Redoc quickstart guide
---
# Redoc quickstart guide
To render your OpenAPI definition using Redoc, use the following HTML code sample and
replace the `spec-url` attribute with the URL or local file address to your definition.
```html
<!DOCTYPE html>
<html>
<head>
<title>Redoc</title>
<!-- needed for adaptive design -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
rel="stylesheet"
/>
<!--
Redoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!--
Redoc element with link to your OpenAPI definition
-->
<redoc spec-url="http://petstore.swagger.io/v2/swagger.json"></redoc>
<!--
Link to Redoc JavaScript on CDN for rendering standalone element
-->
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"></script>
</body>
</html>
```
:::attention Redoc requires an HTTP server to run locally
Loading local OpenAPI definitions is impossible without running a web server because of issues with
[same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) and
other security reasons. Refer to [Running Redoc locally](./deployment/intro.md#how-to-run-redoc-locally) for more information.
:::
For a more detailed explanation with step-by-step instructions and additional options for using Redoc, refer to the [Redoc deployment guide](./deployment/intro.md).

112
docs/quickstart/cli.md Normal file
View File

@ -0,0 +1,112 @@
---
title: Using the Redoc CLI
---
# Using the Redoc CLI
With Redoc's command-line interface you can bundle your OpenAPI definition and API documentation
(made with Redoc) into a zero-dependency HTML file and locally render your
OpenAPI definition with Redoc.
## Step 1 - Install Redoc CLI
You can install the `redoc-cli` package globally using one of the following package managers:
- [npm](https://docs.npmjs.com/about-npm)
- [yarn](https://classic.yarnpkg.com/en/docs/getting-started)
Or you can install `redoc-cli` using [npx](https://www.freecodecamp.org/news/npm-vs-npx-whats-the-difference/).
### Install Redoc CLI with yarn
To install the `redoc-cli` package globally with yarn:
```bash
yarn global add redoc-cli
```
### Install Redoc with npm
To install the `redoc-cli` package globally with npm:
```bash
npm i -g redoc-cli
```
### Install with `npx`
To install the `redoc-cli` package locally with `npx`, navigate to your project
directory in your terminal, then use the following command:
```bash
npx redoc-cli
```
## Step 2 - Use the CLI
### Redoc CLI commands
The CLI includes the following commands:
- **`redoc-cli serve [spec]`:** Starts a local server with Redoc. You must include the required parameter, spec, which is
a reference to an OpenAPI definition. Options include:
- `--ssr`: Implements a server-side rendering model.
- `--watch`: Automatically reloads the server while you edit your OpenAPI definition.
- `--options`: Customizes your output using [Redoc options](https://redocly.com/docs/api-reference-docs/configuration/).
To add nested options, use dot notation.
- **`redoc-cli bundle [spec]`:** Bundles `spec` and Redoc into a zero-dependency HTML file. Options include:
- `-t` or `--template`: Uses custom [Handlebars](https://handlebarsjs.com/) templates to render your OpenAPI definition.
- `--templateOptions`: Adds template options you want to pass to your
custom Handlebars template. To add options, use dot notation.
- **`--help`:** Prints help text for the Redoc CLI commands and options.
- **`--version`:** Prints the version of the `redoc-cli` package you have installed.
### Redoc CLI examples
#### Bundle
Bundle with the main color changed to `orange`:
```bash
redoc-cli bundle openapi.yaml --options.theme.colors.primary.main=orange
```
Bundle using a custom Handlebars template and add custom `templateOptions`:
```bash
redoc-cli bundle http://petstore.swagger.io/v2/swagger.json -t custom.hbs --templateOptions.metaDescription "Page meta description"
```
Sample Handlebars template:
```handlebars
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8" />
<title>{{title}}</title>
<!-- needed for adaptive design -->
<meta description="{{{templateOptions.metaDescription}}}">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
padding: 0;
margin: 0;
}
</style>
{{{redocHead}}}
{{#unless disableGoogleFont}}<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">{{/unless}}
</head>
<body>
{{{redocHTML}}}
</body>
</html>
```
#### Serve
Serve with the `nativeScrollbars` option set to `true`:
```bash
redoc-cli serve openapi/dist.yaml --options.nativeScrollbars
```

39
docs/quickstart/docker.md Normal file
View File

@ -0,0 +1,39 @@
---
title: Using the Redoc Docker image
---
# Using the Redoc Docker image
Redoc is available as a pre-built Docker image in [Docker Hub](https://hub.docker.com/r/redocly/redoc/).
If you have [Docker](https://docs.docker.com/get-docker/) installed, pull the image with the following command:
```docker
docker pull redocly/redoc
```
Then run the image with the following command:
```docker
docker run -p 8080:80 redocly/redoc
```
The preview starts on port 8080, based on the port used in the command,
and can be accessed at `http://localhost:8080`.
To exit the preview, use `control+C`.
By default Redoc starts with a demo Swagger Petstore OpenAPI definition located at
http://petstore.swagger.io/v2/swagger.json. You can update this URL using
the environment variable `SPEC_URL`.
For example:
```bash
docker run -p 8080:80 -e SPEC_URL=https://api.example.com/openapi.json redocly/redoc
```
## Using a Dockerfile
You can also create a Dockerfile with some predefined environment variables. Check out
a sample [Dockerfile](https://github.com/Redocly/redoc/blob/master/config/docker/Dockerfile)
in our code repo.

214
docs/quickstart/html.md Normal file
View File

@ -0,0 +1,214 @@
---
title: Using the Redoc HTML element
---
# Using the Redoc HTML element
## TL;DR final code example
```html
<!DOCTYPE html>
<html>
<head>
<title>Redoc</title>
<!-- needed for adaptive design -->
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<!--
Redoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
</body>
</html>
```
:::attention Running Redoc locally requires an HTTP server
Loading local OpenAPI definitions is impossible without running a web server because of issues with
[same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) and
other security reasons.
:::
### Running Redoc locally
If you want to view your Redoc output locally, you can simulate an HTTP server.
#### Using Redocly OpenAPI CLI
Redocly OpenAPI CLI is an open source command-line tool that includes a command
for simulating an HTTP server to provide a preview of your OpenAPI definition locally.
If you have [OpenAPI CLI](https://redocly.com/docs/cli/#installation-and-usage) installed, `cd` into your
project directory and run the following command:
```bash
openapi preview-docs openapi.yaml
```
By default, without providing a port, the preview starts on port 8080, and can be accessed at `http://localhost:8080`.
To exit the preview, use `control+C`.
#### Using Python
If you have [Python 3](https://www.python.org/downloads/) installed, `cd` into your
project directory and run the following command:
```python
python3 -m http.server
```
If you have [Python 2](https://www.python.org/downloads/) installed, `cd` into your
project directory and run the following command:
```python
python -m SimpleHTTPServer 8000
```
The output after entering the command provides the local URL where the preview can be accessed.
To exit the preview, use `control-C`.
#### Using Node.js
If you have [Node.js](https://nodejs.org/en/download/) installed, install `http-server`
using the following npm command:
```bash
npm install -g http-server
```
Then, `cd` into your project directory and run the following command:
```node
http-server
```
The output after entering the command provides the local URL where the preview can be accessed.
To exit the preview, use `control-C`.
## Step 1 - Install Redoc
You can install Redoc using one of the following package managers:
- [npm](https://docs.npmjs.com/about-npm)
- [yarn](https://classic.yarnpkg.com/en/docs/getting-started)
:::attention Initialize your package manager
If you do not have a `package.json` file in your project directory,
you need to add one by initializing npm or yarn in your project. Use the command `npm init` for npm,
or `yarn init` for yarn. These initialization commands will lead you through the process
of creating a `package.json` file in your project.
For more information, see
[Creating a package.json file](https://docs.npmjs.com/creating-a-package-json-file)
in the npm documentation or [Yarn init](https://classic.yarnpkg.com/en/docs/cli/init/)
in the yarn documentation.
:::
### Install Redoc with yarn
After navigating to your project directory in your terminal, use the following command:
```bash
yarn add redoc
```
### Install Redoc with npm
After navigating to your project directory in your terminal, use the following command:
```bash
npm i redoc
```
## Step 2 - Reference the Redoc script
You can reference the Redoc script using either a link to the files hosted on a CDN
or the files located in your `node modules` folder.
### CDN link
To reference the Redoc script with a CDN link:
```html
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
```
### Node modules link
To reference the Redoc script with a node modules link:
```html
<script src="node_modules/redoc/bundles/redoc.standalone.js"> </script>
```
## Step 3 - Add the <redoc> element
You can add the <redoc> element to your HTML page and reference your OpenAPI
definition using the `spec-url` attribute, or you can initialize Redoc using
a globally exposed Redoc object.
### Using the `spec-url` attribute
To add the <redoc> element with the `spec-url` attribute:
```html
<redoc spec-url="url/to/your/spec"></redoc>
```
#### Examples
```html
<redoc spec-url="http://petstore.swagger.io/v2/swagger.json"></redoc>
```
You can also use a local file (JSON or YAML) in your project, for instance:
```html
<redoc spec-url="dist.json"></redoc>
```
### Using a Redoc object
To add the <redoc> element with a globally exposed Redoc object:
```js
Redoc.init(specOrSpecUrl, options, element, callback)
```
- `specOrSpecUrl`: Either a JSON object with the OpenAPI definition or a URL to the
definition in JSON or YAML format.
- `options`: See [options object](https://redocly.com/docs/api-reference-docs/configuration/) reference.
- `element`: DOM element Redoc will be inserted into.
- `callback`(optional): Callback to be called after Redoc has been fully rendered.
It is also called on errors with `error` as the first argument.
#### Examples
```html
<script>
Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
scrollYOffset: 50
}, document.getElementById('redoc-container'))
</script>
```
You can also use a local file (JSON or YAML) in your project, for instance:
```html
<script>
Redoc.init('dist.yaml', {
scrollYOffset: 50
}, document.getElementById('redoc-container'))
</script>
```

44
docs/quickstart/intro.md Normal file
View File

@ -0,0 +1,44 @@
---
title: Redoc quickstart guide
---
# Redoc quickstart guide
This guide includes step-by-step instructions for how to get started using
Redoc to render your OpenAPI definition.
Redoc offers multiple options for rendering your OpenAPI definition.
You should select the option that best fits your needs.
The following options are supported:
- **[Live demo](https://redocly.github.io/redoc/):**
The live demo offers a fast way to see how your OpenAPI will render with Redoc.
- **[HTML element](./html.md):**
Using the HTML element works well for typical website deployments.
- **[React component](./react.md):**
Using the React component is an option for users with a React-based application.
- **[Docker image](./docker.md):**
Using the Docker image works in a container-based deployment.
- **[CLI](./cli.md):**
Using the CLI is an option for users who prefer to use a command-line interface.
## Before you start
You will need an OpenAPI definition. For testing purposes, you can use one of the following sample OpenAPI definitions:
- OpenAPI 3.0
- [Rebilly Users OpenAPI Definition](https://raw.githubusercontent.com/Rebilly/api-definitions/main/openapi/users.yaml)
- [Swagger Petstore Sample OpenAPI Definition](https://petstore3.swagger.io/api/v3/openapi.json)
- OpenAPI 2.0
- [Thingful OpenAPI Definition](https://raw.githubusercontent.com/thingful/openapi-spec/master/spec/swagger.yaml)
- [Fitbit Plus OpenAPI Definition](https://raw.githubusercontent.com/TwineHealth/TwineDeveloperDocs/master/spec/swagger.yaml)
For more information on the OpenAPI specification, refer to the [Learning OpenAPI 3](https://redocly.com/docs/resources/learning-openapi/)
section in the documentation.
## Live demo online
If you want to see how ReDoc will render your OpenAPI definition, you can try it out online at https://redocly.github.io/redoc/.
A version of the Swagger Petstore API is displayed by default. To test it with your own OpenAPI definition, enter the URL for your
definition and select the **TRY IT** button.

78
docs/quickstart/react.md Normal file
View File

@ -0,0 +1,78 @@
---
title: Using the Redoc React component
---
# Using the Redoc React component
## Before you start
Install the following dependencies required by Redoc if you do not already have them installed:
- `react`
- `react-dom`
- `mobx`
- `styled-components`
- `core-js`
If you have npm installed, you can install these dependencies using the following command:
```js
npm i react react-dom mobx styled-components core-js
```
## Step 1 - Import the `RedocStandalone` component
```js
import { RedocStandalone } from 'redoc';
```
## Step 2 - Use the component
You can either link to your OpenAPI definition with a URL, using the following format:
```react
<RedocStandalone specUrl="url/to/your/spec"/>
```
Or you can pass your OpenAPI definition as an object, using the following format:
```js
<RedocStandalone spec={/* spec as an object */}/>
```
## Optional - Pass options
Options can be passed into the RedocStandalone component to alter how it renders.
For example:
```js
<RedocStandalone
specUrl="http://petstore.swagger.io/v2/swagger.json"
options={{
nativeScrollbars: true,
theme: { colors: { primary: { main: '#dd5522' } } },
}}
/>
```
For more information on configuration options, refer to the
[Configuration options for Reference docs](https://redocly.com/docs/api-reference-docs/configuration/)
section of the documentation. Options available for Redoc are noted,
"Supported in Redoc CE".
## Optional - Specify `onLoaded` callback
You can also specify the `onLoaded` callback, which is called each time Redoc
is fully rendered or when an error occurs (with an error as the first argument).
```js
<RedocStandalone
specUrl="http://petstore.swagger.io/v2/swagger.json"
onLoaded={error => {
if (!error) {
console.log('Yay!');
}
}}
/>
```

View File

@ -1,8 +1,34 @@
# ReDoc vendor extensions # Redoc vendor extensions
ReDoc makes use of the following [vendor extensions](https://swagger.io/specification/#specificationExtensions)
You can use the following [vendor extensions](https://swagger.io/specification/#specificationExtensions) with Redoc.
- [Swagger Object](#swagger-object)
- [x-servers](#x-servers)
- [x-tagGroups](#x-taggroups)
- [Tag Group Object](#a-nametaggroupobjectatag-group-object)
- [x-ignoredHeaderParameters](#x-ignoredheaderparameters)
- [Info Object](#info-object)
- [x-logo](#x-logo)
- [Logo Object](#a-namelogoobjectalogo-object)
- [Tag Object](#tag-object)
- [x-traitTag](#x-traittag)
- [x-displayName](#x-displayname)
- [Operation Object](#operation-object-vendor-extensions)
- [x-codeSamples](#x-codesamples)
- [Code Sample Object](#a-namecodesampleobjectacode-sample-object)
- [Parameter Object](#parameter-object)
- [x-examples](#x-examples)
- [Response Object vendor extensions](#response-object-vendor-extensions)
- [x-summary](#x-summary)
- [Schema Object](#schema-object)
- [x-nullable](#x-nullable)
- [x-extendedDiscriminator](#x-extendeddiscriminator)
- [x-additionalPropertiesName](#x-additionalpropertiesname)
- [x-explicitMappingOnly](#x-explicitmappingonly)
### Swagger Object
Extends the OpenAPI root [OpenAPI Object](https://swagger.io/specification/#oasObject)
### Swagger Object vendor extensions
Extend OpenAPI root [Swagger Object](https://swagger.io/specification/#oasObject)
#### x-servers #### x-servers
Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#serverObject). Currently doesn't support templates. Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#serverObject). Currently doesn't support templates.
@ -12,9 +38,9 @@ Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specifica
| :------------- | :-----------: | :---------- | | :------------- | :-----------: | :---------- |
| x-tagGroups | [ [Tag Group Object](#tagGroupObject) ] | A list of tag groups | | x-tagGroups | [ [Tag Group Object](#tagGroupObject) ] | A list of tag groups |
###### Usage in Redoc ###### How to use with Redoc
`x-tagGroups` is used to group tags in the side menu. `x-tagGroups` is used to group tags in the side menu.
If you are going to use `x-tagGroups`, please make sure you **add all tags to a group**, since a tag that is not in a group, **will not be displayed** at all! Before you use `x-tagGroups`, make sure you **add all tags to a group**, since a tag that is not in a group, **will not be displayed** at all!
#### <a name="tagGroupObject"></a>Tag Group Object #### <a name="tagGroupObject"></a>Tag Group Object
Information about tags group Information about tags group
@ -62,8 +88,8 @@ x-tagGroups:
| x-ignoredHeaderParameters | [ string ] | A list of ignored headers | | x-ignoredHeaderParameters | [ string ] | A list of ignored headers |
###### Usage in Redoc ###### How to use with Redoc
`x-ignoredHeaderParameters` is used to specify header parameter names which are ignored by ReDoc Use `x-ignoredHeaderParameters` to specify header parameter names which are ignored by ReDoc.
###### x-ignoredHeaderParameters example ###### x-ignoredHeaderParameters example
```yaml ```yaml
@ -77,19 +103,20 @@ x-ignoredHeaderParameters:
- X-Test-Header - X-Test-Header
``` ```
### Info Object vendor extensions ### Info Object
Extends OpenAPI [Info Object](http://swagger.io/specification/#infoObject) Extends the OpenAPI [Info Object](http://swagger.io/specification/#infoObject)
#### x-logo #### x-logo
| Field Name | Type | Description | | Field Name | Type | Description |
| :------------- | :-----------: | :---------- | | :------------- | :-----------: | :---------- |
| x-logo | [Logo Object](#logoObject) | The information about API logo | | x-logo | [Logo Object](#logoObject) | The information about API logo |
###### Usage in Redoc ###### How to use with Redoc
`x-logo` is used to specify API logo. The corresponding image are displayed just above side-menu. `x-logo` is used to specify API logo. The corresponding image is displayed just above the side-menu.
#### <a name="logoObject"></a>Logo Object #### <a name="logoObject"></a>Logo Object
The information about API logo The information about API logo
###### Fixed fields ###### Fixed fields
| Field Name | Type | Description | | Field Name | Type | Description |
| :-------------- | :------: | :---------- | | :-------------- | :------: | :---------- |
@ -125,17 +152,16 @@ info:
altText: "Petstore logo" altText: "Petstore logo"
``` ```
### Tag Object
Extends the OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
### Tag Object vendor extensions
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
#### x-traitTag #### x-traitTag
| Field Name | Type | Description | | Field Name | Type | Description |
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| 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) | | 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 ###### How to use with 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. Tags that have `x-traitTag` set to `true` are listed in the side-menu but don't have any subitems (operations). It also renders the `description` tag.
This is useful for handling out common things like Pagination, Rate-Limits, etc. This is useful for handling out common things like Pagination, Rate-Limits, etc.
###### x-traitTag example ###### x-traitTag example
@ -161,17 +187,19 @@ x-traitTag: true
| x-displayName | string | Defines the text that is used for this tag in the menu and in section headings | | x-displayName | string | Defines the text that is used for this tag in the menu and in section headings |
### Operation Object vendor extensions ### Operation Object vendor extensions
Extends OpenAPI [Operation Object](http://swagger.io/specification/#operationObject) Extends the OpenAPI [Operation Object](http://swagger.io/specification/#operationObject)
#### x-codeSamples #### x-codeSamples
| Field Name | Type | Description | | Field Name | Type | Description |
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-codeSamples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation | | x-codeSamples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation |
###### Usage in ReDoc ###### How to use with Redoc
`x-codeSamples` are rendered on the right panel of ReDoc `x-codeSamples` are rendered on the right panel in Redoc.
#### <a name="codeSampleObject"></a>Code Sample Object #### <a name="codeSampleObject"></a>Code Sample Object
Operation code sample Operation code sample
###### Fixed fields ###### Fixed fields
| Field Name | Type | Description | | Field Name | Type | Description |
| :---------- | :------: | :----------- | | :---------- | :------: | :----------- |
@ -194,49 +222,51 @@ lang: JavaScript
source: console.log('Hello World'); source: console.log('Hello World');
``` ```
### Parameter Object vendor extensions ### Parameter Object
Extends OpenAPI [Parameter Object](http://swagger.io/specification/#parameterObject) Extends the OpenAPI [Parameter Object](http://swagger.io/specification/#parameterObject)
#### x-examples #### x-examples
| Field Name | Type | Description | | Field Name | Type | Description |
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-examples | [Example Object](http://swagger.io/specification/#exampleObject) | Object that contains examples for the request. Applies when `in` is `body` and mime-type is `application/json` | | x-examples | [Example Object](http://swagger.io/specification/#exampleObject) | Object that contains examples for the request. Applies when `in` is `body` and mime-type is `application/json` |
###### Usage in ReDoc ###### How to use with Redoc
`x-examples` are rendered in the JSON tab on the right panel of ReDoc. `x-examples` are rendered in the JSON tab on the right panel in Redoc.
### Response Object vendor extensions ### Response Object vendor extensions
Extends OpenAPI [Response Object](https://swagger.io/specification/#responseObject) Extends the OpenAPI [Response Object](https://swagger.io/specification/#responseObject)
#### x-summary #### x-summary
| Field Name | Type | Description | | Field Name | Type | Description |
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-summary | string | a short summary of the response | | x-summary | string | a short summary of the response |
###### Usage in ReDoc ###### How to use with Redoc
If specified, `x-summary` is used as the response button text. Description is rendered under the button. If specified, you can use `x-summary` as the response button text, with description rendered under the button.
### Schema Object
Extends the OpenAPI [Schema Object](http://swagger.io/specification/#schemaObject)
### Schema Object vendor extensions
Extends OpenAPI [Schema Object](http://swagger.io/specification/#schemaObject)
#### x-nullable #### x-nullable
| Field Name | Type | Description | | Field Name | Type | Description |
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-nullable | boolean | marks schema as a nullable | | x-nullable | boolean | marks schema as a nullable |
###### Usage in ReDoc ###### How to use with Redoc
Schemas marked as `x-nullable` are marked in ReDoc with the label Nullable Schemas marked as `x-nullable` are marked in Redoc with the label Nullable
#### x-extendedDiscriminator #### x-extendedDiscriminator
**ATTENTION**: This is ReDoc-specific vendor extension. It won't be supported by other tools. **ATTENTION**: This is a Redoc-specific vendor extension, and is not supported by other tools.
| Field Name | Type | Description | | Field Name | Type | Description |
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-extendedDiscriminator | string | specifies extended discriminator | | x-extendedDiscriminator | string | specifies extended discriminator |
###### Usage in ReDoc ###### How to use with Redoc
ReDoc uses this vendor extension to solve name-clash issues with the standard `discriminator`. Redoc uses this vendor extension to solve name-clash issues with the standard `discriminator`.
Value of this field specifies the field which will be used as a extended discriminator. Value of this field specifies the field which will be used as a extended discriminator.
ReDoc displays definition with selectpicker using which user can select value of the `x-extendedDiscriminator`-marked field. Redoc displays definition with selectpicker using which user can select value of the `x-extendedDiscriminator`-marked field.
ReDoc displays the definition which is derived from the current (using `allOf`) and has `enum` with only one value which is the same as the selected value of the field specified as `x-extendedDiscriminator`. Redoc displays the definition derived from the current (using `allOf`) and has `enum` with only one value which is the same as the selected value of the field specified as `x-extendedDiscriminator`.
###### x-extendedDiscriminator example ###### x-extendedDiscriminator example
@ -276,11 +306,10 @@ PayPalPayment:
type: string type: string
``` ```
In the example above the names of definitions (`PayPalPayment`) are named differently than In the example above, the names of definitions (`PayPalPayment`) are named differently than names in the payload (`paypal`) which is not supported by default `discriminator`.
names in the payload (`paypal`) which is not supported by default `discriminator`.
#### x-additionalPropertiesName #### x-additionalPropertiesName
**ATTENTION**: This is ReDoc-specific vendor extension. It won't be supported by other tools. **ATTENTION**: This is a Redoc-specific vendor extension, and is not supported by other tools.
Extends the `additionalProperties` property of the schema object. Extends the `additionalProperties` property of the schema object.
@ -288,8 +317,8 @@ Extends the `additionalProperties` property of the schema object.
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-additionalPropertiesName | string | descriptive name of additional properties keys | | x-additionalPropertiesName | string | descriptive name of additional properties keys |
###### Usage in ReDoc ###### How to use with Redoc
ReDoc uses this extension to display a more descriptive property name in objects with `additionalProperties` when viewing the property list with an `object`. 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 ###### x-additionalPropertiesName example
@ -308,7 +337,7 @@ Player:
``` ```
#### x-explicitMappingOnly #### x-explicitMappingOnly
**ATTENTION**: This is ReDoc-specific vendor extension. It won't be supported by other tools. **ATTENTION**: This is Redoc-specific vendor extension, and is not supported by other tools.
Extends the `discriminator` property of the schema object. Extends the `discriminator` property of the schema object.
@ -316,10 +345,9 @@ Extends the `discriminator` property of the schema object.
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-explicitMappingOnly | boolean | limit the discriminator selectpicker to the explicit mappings only | | x-explicitMappingOnly | boolean | limit the discriminator selectpicker to the explicit mappings only |
###### Usage in ReDoc ###### How to use with Redoc
ReDoc uses this extension to filter the `discriminator` mappings shown in the selectpicker. Redoc uses this extension to filter the `discriminator` mappings shown in the selectpicker.
When set to `true`, the selectpicker will only list the the explicitly defined mappings. When `false`, When set to `true`, the selectpicker will only list the the explicitly defined mappings. When `false`, the default behavior is kept, i.e. explicit and implicit mappings will be shown.
the default behavior is kept, i.e. explicit and implicit mappings will be shown.
###### x-explicitMappingOnly example ###### x-explicitMappingOnly example
@ -338,5 +366,4 @@ Pet:
bee: "#/components/schemas/HoneyBee" bee: "#/components/schemas/HoneyBee"
``` ```
Will show in the selectpicker only the items `cat` and `bee`, even though the `Dog` class inherits from Will show in the selectpicker only the items `cat` and `bee`, even though the `Dog` class inherits from the `Pet` class.
the `Pet` class.

View File

@ -1,24 +0,0 @@
# 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';
```

View File

@ -4,9 +4,7 @@ describe('Menu', () => {
}); });
it('should have valid items count', () => { it('should have valid items count', () => {
cy.get('.menu-content') cy.get('.menu-content').find('li').should('have.length', 34);
.find('li')
.should('have.length', 34);
}); });
it('should sync active menu items while scroll', () => { it('should sync active menu items while scroll', () => {
@ -25,21 +23,73 @@ describe('Menu', () => {
.should('be.visible'); .should('be.visible');
}); });
it('should sync active menu items while scroll back and scroll again', () => {
cy.contains('h2', 'Add a new pet to the store')
.scrollIntoView()
.wait(100)
.get('[role=menuitem].active')
.children()
.last()
.should('have.text', 'Add a new pet to the store')
.should('be.visible');
cy.contains('h1', 'Swagger Petstore').scrollIntoView().wait(100);
cy.contains('h1', 'Introduction')
.scrollIntoView()
.wait(100)
.get('[role=menuitem].active')
.should('have.text', 'Introduction');
cy.url().should('include', '#section/Introduction');
});
it('should update URL hash when clicking on menu items', () => { it('should update URL hash when clicking on menu items', () => {
cy.contains('[role=menuitem].-depth1', 'pet').click({ force: true }); cy.contains('[role=menuitem].-depth1', 'pet').click({ force: true });
cy.location('hash').should('equal', '#tag/pet'); cy.location('hash').should('equal', '#tag/pet');
cy.contains('[role=menuitem]', 'Find pet by ID').click({ force: true }); cy.contains('[role=menuitem]', 'Find pet by ID').click({ force: true });
cy.location('hash').should('equal', '#operation/getPetById'); cy.location('hash').should('equal', '#tag/pet/operation/getPetById');
}); });
it('should deactivate tag when other is activated', () => { it('should deactivate tag when other is activated', () => {
const petItem = () => cy.contains('[role=menuitem].-depth1', 'pet'); const petItem = () => cy.contains('[role=menuitem].-depth1', 'pet');
petItem() petItem().click({ force: true }).should('have.class', 'active');
.click({ force: true })
.should('have.class', 'active');
cy.contains('[role=menuitem].-depth1', 'store').click({ force: true }); cy.contains('[role=menuitem].-depth1', 'store').click({ force: true });
petItem().should('not.have.class', 'active'); petItem().should('not.have.class', 'active');
}); });
it('should be able to open a response object to see more details', () => {
cy.contains('h2', 'Find pet by ID')
.scrollIntoView()
.wait(100)
.parent()
.find('div h3')
.should('have.text', 'Responses')
.parent()
.find('div:first button')
.click()
.should('have.attr', 'aria-expanded', 'true')
.parent()
.find('div h5')
.then($h5 => $h5[0].firstChild!.nodeValue!.trim())
.should('eq', 'Response Schema:');
});
it('should be able to open the operation details when the operation IDs have quotes', () => {
cy.visit('e2e/standalone-3-1.html');
cy.get('label span[title="pet"]').click({ multiple: true, force: true });
cy.get('li').contains('OperationId with quotes').click({ multiple: true, force: true });
cy.get('h2').contains('OperationId with quotes').should('be.visible');
cy.url().should('include', 'deletePetBy%22Id');
});
it.only('should encode URL when the operation IDs have backslashes', () => {
cy.visit('e2e/standalone-3-1.html');
cy.get('label span[title="pet"]').click({ multiple: true, force: true });
cy.get('li').contains('OperationId with backslash').click({ multiple: true, force: true });
cy.get('h2').contains('OperationId with backslash').should('be.visible');
cy.url().should('include', 'delete%5CPetById');
});
}); });

View File

@ -1,5 +1,5 @@
// tslint:disable:no-implicit-dependencies // tslint:disable:no-implicit-dependencies
import * as yaml from 'yaml-js'; import * as yaml from 'js-yaml';
async function loadSpec(url: string): Promise<any> { async function loadSpec(url: string): Promise<any> {
const spec = await (await fetch(url)).text(); const spec = await (await fetch(url)).text();
@ -21,12 +21,12 @@ describe('Servers', () => {
initReDoc(win, spec, {}); initReDoc(win, spec, {});
// TODO add cy-data attributes // TODO add cy-data attributes
cy.get('[data-section-id="operation/addPet"]').should( cy.get('[data-section-id="tag/pet/operation/addPet"]').should(
'contain', 'contain',
'http://petstore.swagger.io/v2/pet', 'http://petstore.swagger.io/v2/pet',
); );
cy.get('[data-section-id="operation/addPet"]').should( cy.get('[data-section-id="tag/pet/operation/addPet"]').should(
'contain', 'contain',
'http://petstore.swagger.io/sandbox/pet', 'http://petstore.swagger.io/sandbox/pet',
); );
@ -40,7 +40,7 @@ describe('Servers', () => {
initReDoc(win, spec, {}); initReDoc(win, spec, {});
// TODO add cy-data attributes // TODO add cy-data attributes
cy.get('[data-section-id="operation/addPet"]').should( cy.get('[data-section-id="tag/pet/operation/addPet"]').should(
'contain', 'contain',
'http://localhost:' + win.location.port + '/pet', 'http://localhost:' + win.location.port + '/pet',
); );
@ -55,7 +55,7 @@ describe('Servers', () => {
initReDoc(win, spec, {}); initReDoc(win, spec, {});
// TODO add cy-data attributes // TODO add cy-data attributes
cy.get('[data-section-id="operation/addPet"]').should( cy.get('[data-section-id="tag/pet/operation/addPet"]').should(
'contain', 'contain',
'http://localhost:' + win.location.port + '/pet', 'http://localhost:' + win.location.port + '/pet',
); );

View File

@ -53,4 +53,10 @@ describe('Search', () => {
getSearchInput().type('int', { force: true }); getSearchInput().type('int', { force: true });
cy.get('[data-markjs]').should('exist'); cy.get('[data-markjs]').should('exist');
}); });
it('should show proper message when no search results are found', () => {
getSearchResults().should('not.exist');
getSearchInput().type('xzss', { force: true });
getSearchResults().should('exist').should('contain', 'No results found');
});
}); });

View File

@ -16,5 +16,6 @@ describe('Standalone bundle test', () => {
} }
baseCheck('OAS3 mode', 'e2e/standalone.html'); baseCheck('OAS3 mode', 'e2e/standalone.html');
baseCheck('OAS3.1 mode', 'e2e/standalone-3-1.html');
baseCheck('OAS2 compatibility mode', 'e2e/standalone-compatibility.html'); baseCheck('OAS2 compatibility mode', 'e2e/standalone-compatibility.html');
}); });

View File

@ -0,0 +1,19 @@
describe('Supporting both operation/* and parent/*/operation* urls', () => {
beforeEach(() => {
cy.visit('e2e/standalone.html');
});
it('should supporting operation/* url', () => {
cy.url().then(loc => {
cy.visit(loc + '#operation/updatePet');
cy.get('li[data-item-id="tag/pet/operation/updatePet"]').should('be.visible');
});
});
it('should supporting parent/*/operation url', () => {
cy.url().then(loc => {
cy.visit(loc + '#tag/pet/operation/addPet');
cy.get('li[data-item-id="tag/pet/operation/addPet"]').should('be.visible');
});
});
});

8
e2e/standalone-3-1.html Normal file
View File

@ -0,0 +1,8 @@
<html>
<body>
<redoc spec-url="../demo/openapi-3-1.yaml" native-scrollbars></redoc>
<script src="../bundles/redoc.standalone.js"></script>
</body>
</html>

36623
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,15 @@
{ {
"name": "redoc", "name": "redoc",
"version": "2.0.0-rc.40", "version": "2.0.0-rc.66",
"description": "ReDoc", "description": "ReDoc",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/Redocly/redoc" "url": "git://github.com/Redocly/redoc"
}, },
"browserslist": [
"defaults",
"ie 11"
],
"engines": { "engines": {
"node": ">=6.9", "node": ">=6.9",
"npm": ">=3.0.0" "npm": ">=3.0.0"
@ -23,150 +27,150 @@
"React.js" "React.js"
], ],
"main": "bundles/redoc.lib.js", "main": "bundles/redoc.lib.js",
"browser": "bundles/redoc.browser.lib.js",
"types": "typings/index.d.ts", "types": "typings/index.d.ts",
"scripts": { "scripts": {
"start": "webpack-dev-server --mode=development --env.playground --hot --config demo/webpack.config.ts", "start": "webpack serve --mode=development --env playground --hot --config demo/webpack.config.ts",
"start:prod": "webpack-dev-server --env.playground --mode=production --config demo/webpack.config.ts", "start:prod": "webpack serve --env playground --mode=production --config demo/webpack.config.ts",
"start:benchmark": "webpack-dev-server --mode=production --env.bench --config demo/webpack.config.ts", "start:benchmark": "webpack serve --mode=production --env.bench --config demo/webpack.config.ts",
"test": "npm run lint && npm run unit && npm run license-check", "test": "npm run unit && npm run license-check",
"unit": "jest --coverage", "unit": "jest --coverage",
"e2e": "cypress run", "e2e": "cypress run",
"e2e-ci": "cypress run --record", "e2e-ci": "cypress run --record",
"bundlesize": "bundlesize", "bundlesize": "size-limit",
"ts-check": "tsc --noEmit --skipLibCheck",
"cy:open": "cypress open", "cy:open": "cypress open",
"bundle:clean": "rimraf bundles", "bundle:clean": "rimraf bundles",
"bundle:standalone": "webpack --env.standalone --mode=production", "bundle:standalone": "webpack --env production --env standalone --mode=production",
"bundle:lib": "webpack --mode=production && npm run declarations", "bundle:lib": "webpack --mode=production && npm run declarations",
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone", "bundle:browser": "webpack --env production --env browser --mode=production",
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:browser && npm run bundle:standalone",
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/", "declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
"stats": "webpack --env.standalone --json --profile --mode=production > stats.json", "stats": "webpack --env production --env standalone --json --profile --mode=production > stats.json",
"prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"", "prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
"lint": "eslint 'src/**/*.{js,ts,tsx}'", "lint": "eslint --fix 'src/**/*.{js,ts,tsx}' --cache",
"benchmark": "node ./benchmark/benchmark.js", "benchmark": "node ./benchmark/benchmark.js",
"start:demo": "webpack-dev-server --hot --config demo/webpack.config.ts --mode=development", "start:demo": "webpack serve --hot --config demo/webpack.config.ts --mode=development",
"compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs", "compile:cli": "tsc custom.d.ts cli/index.ts --target es6 --module commonjs --types yargs",
"build:demo": "webpack --mode=production --config demo/webpack.config.ts", "build:demo": "webpack --mode=production --config demo/webpack.config.ts",
"deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read", "deploy:demo": "aws s3 sync demo/dist s3://production-redoc-demo --acl=public-read",
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause' --summary", "license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause;CC-BY-4.0;Python-2.0' --summary",
"docker:build": "docker build -f config/docker/Dockerfile -t redoc ." "docker:build": "docker build -f config/docker/Dockerfile -t redoc .",
"prepare": "husky install",
"pre-commit": "pretty-quick --staged && npm run lint"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.10.5", "@cypress/webpack-preprocessor": "^5.9.0",
"@babel/plugin-syntax-decorators": "^7.10.4", "@hot-loader/react-dom": "^17.0.1",
"@babel/plugin-syntax-dynamic-import": "^7.8.3", "@size-limit/preset-app": "^7.0.4",
"@babel/plugin-syntax-jsx": "^7.10.4", "@types/chai": "^4.2.18",
"@babel/plugin-syntax-typescript": "^7.10.4", "@types/dompurify": "^2.2.2",
"@cypress/webpack-preprocessor": "^5.4.2",
"@hot-loader/react-dom": "^16.12.0",
"@types/chai": "^4.2.12",
"@types/dompurify": "^2.0.2",
"@types/enzyme": "^3.10.5", "@types/enzyme": "^3.10.5",
"@types/enzyme-to-json": "^1.5.3", "@types/enzyme-to-json": "^1.5.3",
"@types/jest": "^26.0.7", "@types/jest": "^26.0.23",
"@types/json-pointer": "^1.0.30", "@types/json-pointer": "^1.0.30",
"@types/lodash": "^4.14.158",
"@types/lunr": "^2.3.3", "@types/lunr": "^2.3.3",
"@types/mark.js": "^8.11.5", "@types/mark.js": "^8.11.5",
"@types/marked": "^1.1.0", "@types/marked": "^4.0.1",
"@types/prismjs": "^1.16.1", "@types/node": "^15.6.1",
"@types/prismjs": "^1.16.5",
"@types/prop-types": "^15.7.3", "@types/prop-types": "^15.7.3",
"@types/react": "^16.9.43", "@types/react": "^17.0.8",
"@types/react-dom": "^16.9.8", "@types/react-dom": "^17.0.5",
"@types/react-tabs": "^2.3.2", "@types/react-tabs": "^2.3.2",
"@types/styled-components": "^5.1.1", "@types/styled-components": "^5.1.1",
"@types/tapable": "^1.0.6", "@types/tapable": "^2.2.2",
"@types/webpack": "^4.41.21", "@types/webpack": "^5.28.0",
"@types/webpack-env": "^1.15.2", "@types/webpack-env": "^1.16.0",
"@types/yargs": "^15.0.5", "@types/yargs": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^3.7.0", "@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^3.7.0", "@typescript-eslint/parser": "^4.26.0",
"babel-loader": "^8.1.0", "@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
"babel-plugin-styled-components": "^1.10.7",
"beautify-benchmark": "^0.2.4", "beautify-benchmark": "^0.2.4",
"bundlesize": "^0.18.0",
"conventional-changelog-cli": "^2.0.34", "conventional-changelog-cli": "^2.0.34",
"copy-webpack-plugin": "^6.0.3", "copy-webpack-plugin": "^9.0.0",
"core-js": "^3.6.5", "core-js": "^3.13.1",
"coveralls": "^3.1.0", "coveralls": "^3.1.0",
"css-loader": "^3.6.0", "css-loader": "^5.2.6",
"cypress": "^4.11.0", "cypress": "^7.4.0",
"deploy-to-gh-pages": "^1.3.7",
"enzyme": "^3.11.0", "enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2", "enzyme-to-json": "^3.6.2",
"enzyme-to-json": "^3.5.0", "esbuild-loader": "^2.18.0",
"eslint": "^7.5.0", "eslint": "^7.27.0",
"eslint-plugin-import": "^2.22.0", "eslint-plugin-import": "^2.23.4",
"eslint-plugin-react": "^7.20.3", "eslint-plugin-react": "^7.25.1",
"fork-ts-checker-webpack-plugin": "^5.0.11", "eslint-plugin-react-hooks": "^4.2.0",
"html-webpack-plugin": "^4.3.0", "fork-ts-checker-webpack-plugin": "^6.2.10",
"jest": "^26.1.0", "html-webpack-plugin": "^5.3.1",
"husky": "^7.0.0",
"jest": "^27.0.3",
"js-yaml": "^4.1.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"lodash": "^4.17.19", "lodash.noop": "^3.0.1",
"mobx": "^5.15.4", "mobx": "^6.3.2",
"prettier": "^2.0.5", "prettier": "^2.3.2",
"pretty-quick": "^3.0.0",
"raf": "^3.4.1", "raf": "^3.4.1",
"react": "^16.13.1", "react": "^17.0.2",
"react-dom": "^16.13.1", "react-dom": "^17.0.2",
"react-hot-loader": "^4.12.21", "react-hot-loader": "^4.13.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"shelljs": "^0.8.4", "shelljs": "^0.8.4",
"source-map-loader": "^1.0.1", "size-limit": "^7.0.4",
"style-loader": "^1.2.1", "styled-components": "^5.3.0",
"styled-components": "^5.1.1", "ts-jest": "^27.0.2",
"ts-jest": "^26.1.3", "ts-loader": "^9.2.6",
"ts-loader": "^8.0.1", "ts-node": "^10.0.0",
"ts-node": "^8.10.2", "typescript": "~4.1.0",
"typescript": "^3.9.7", "unfetch": "^4.2.0",
"unfetch": "^4.1.0", "url-polyfill": "^1.1.12",
"url-polyfill": "^1.1.10", "webpack": "^5.38.1",
"webpack": "^4.44.0", "webpack-cli": "^4.7.2",
"webpack-cli": "^3.3.12", "webpack-dev-server": "^4.6.0",
"webpack-dev-server": "^3.11.0", "webpack-node-externals": "^3.0.0",
"webpack-node-externals": "^2.5.0", "workerize-loader": "github:redocly/workerize-loader#webpack-5-dist"
"workerize-loader": "^1.3.0",
"yaml-js": "^0.2.3"
}, },
"peerDependencies": { "peerDependencies": {
"core-js": "^3.1.4", "core-js": "^3.1.4",
"mobx": "^4.2.0 || ^5.0.0", "mobx": "^6.0.4",
"react": "^16.8.4", "react": "^16.8.4 || ^17.0.0",
"react-dom": "^16.8.4", "react-dom": "^16.8.4 || ^17.0.0",
"styled-components": "^4.1.1" "styled-components": "^4.1.1 || ^5.1.1"
}, },
"dependencies": { "dependencies": {
"@redocly/openapi-core": "^1.0.0-beta.88",
"@redocly/react-dropdown-aria": "^2.0.11", "@redocly/react-dropdown-aria": "^2.0.11",
"@types/node": "^13.11.1", "classnames": "^2.3.1",
"classnames": "^2.2.6",
"decko": "^1.2.0", "decko": "^1.2.0",
"dompurify": "^2.0.12", "dompurify": "^2.2.8",
"eventemitter3": "^4.0.4", "eventemitter3": "^4.0.7",
"json-pointer": "^0.6.0", "json-pointer": "^0.6.2",
"json-schema-ref-parser": "^6.1.0", "lunr": "^2.3.9",
"lunr": "2.3.8",
"mark.js": "^8.11.1", "mark.js": "^8.11.1",
"marked": "^0.7.0", "marked": "^4.0.10",
"memoize-one": "~5.1.1", "mobx-react": "^7.2.0",
"mobx-react": "^6.2.2", "openapi-sampler": "^1.2.1",
"openapi-sampler": "^1.0.0-beta.16", "path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.4.0", "perfect-scrollbar": "^1.5.1",
"polished": "^3.6.5", "polished": "^4.1.3",
"prismjs": "^1.20.0", "prismjs": "^1.27.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-tabs": "^3.1.1", "react-tabs": "^3.2.2",
"slugify": "^1.4.4", "slugify": "~1.4.7",
"stickyfill": "^1.1.1", "stickyfill": "^1.1.1",
"swagger2openapi": "^6.2.1", "style-loader": "^3.3.1",
"tslib": "^2.0.0", "swagger2openapi": "^7.0.6",
"url-template": "^2.0.8" "url-template": "^2.0.8"
}, },
"bundlesize": [ "size-limit": [
{ {
"path": "./bundles/redoc.standalone.js", "path": "./bundles/redoc.standalone.js",
"maxSize": "300 kB" "limit": "350 kB"
} }
], ],
"jest": { "jest": {
"testEnvironment": "jsdom",
"setupFilesAfterEnv": [ "setupFilesAfterEnv": [
"<rootDir>/src/setupTests.ts" "<rootDir>/src/setupTests.ts"
], ],
@ -187,6 +191,9 @@
"modulePathIgnorePatterns": [ "modulePathIgnorePatterns": [
"/benchmark/" "/benchmark/"
], ],
"snapshotSerializers": [
"enzyme-to-json/serializer"
],
"moduleNameMapper": { "moduleNameMapper": {
"\\.(css|less)$": "<rootDir>/src/empty.js" "\\.(css|less)$": "<rootDir>/src/empty.js"
} }
@ -195,6 +202,6 @@
"singleQuote": true, "singleQuote": true,
"trailingComma": "all", "trailingComma": "all",
"printWidth": 100, "printWidth": 100,
"parser": "typescript" "arrowParens": "avoid"
} }
} }

View File

@ -2,7 +2,7 @@
import * as React from 'react'; import * as React from 'react';
import { renderToString } from 'react-dom/server'; import { renderToString } from 'react-dom/server';
import * as yaml from 'yaml-js'; import * as yaml from 'js-yaml';
import { createStore, Redoc } from '../'; import { createStore, Redoc } from '../';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
@ -10,7 +10,7 @@ import { resolve } from 'path';
describe('SSR', () => { describe('SSR', () => {
it('should render in SSR mode', async () => { it('should render in SSR mode', async () => {
const spec = yaml.load(readFileSync(resolve(__dirname, '../../demo/openapi.yaml'))); const spec = yaml.load(readFileSync(resolve(__dirname, '../../demo/openapi.yaml'), 'utf-8'));
const store = await createStore(spec, ''); const store = await createStore(spec, '');
expect(() => { expect(() => {
renderToString(<Redoc store={store} />); renderToString(<Redoc store={store} />);

View File

@ -1,7 +1,7 @@
/* tslint:disable:no-implicit-dependencies */ /* tslint:disable:no-implicit-dependencies */
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import * as yaml from 'yaml-js'; import * as yaml from 'js-yaml';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { resolve } from 'path'; import { resolve } from 'path';
@ -11,7 +11,7 @@ import { Loading, RedocStandalone } from '../components/';
describe('Components', () => { describe('Components', () => {
describe('RedocStandalone', () => { describe('RedocStandalone', () => {
test('should show loading first', () => { test('should show loading first', () => {
const spec = yaml.load(readFileSync(resolve(__dirname, '../../demo/openapi.yaml'))); const spec = yaml.load(readFileSync(resolve(__dirname, '../../demo/openapi.yaml'), 'utf-8'));
const inst = mount(<RedocStandalone spec={spec} options={{}} />); const inst = mount(<RedocStandalone spec={spec} options={{}} />);
expect(inst.find(Loading)).toHaveLength(1); expect(inst.find(Loading)).toHaveLength(1);

View File

@ -65,7 +65,7 @@ export const PrismDiv = styled.div`
} }
.token.boolean { .token.boolean {
color: firebrick; color: #e64441;
} }
.token.selector, .token.selector,

View File

@ -28,16 +28,16 @@ export const StyledDropdown = styled(Dropdown)`
width: auto; width: auto;
background: white; background: white;
color: #263238; color: #263238;
font-family: ${(props) => props.theme.typography.headings.fontFamily}; font-family: ${props => props.theme.typography.headings.fontFamily};
font-size: 0.929em; font-size: 0.929em;
line-height: 1.5em; line-height: 1.5em;
cursor: pointer; cursor: pointer;
transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease; transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
&:hover, &:hover,
&:focus-within { &:focus-within {
border: 1px solid ${(props) => props.theme.colors.primary.main}; border: 1px solid ${props => props.theme.colors.primary.main};
color: ${(props) => props.theme.colors.primary.main}; color: ${props => props.theme.colors.primary.main};
box-shadow: 0px 0px 0px 1px ${(props) => props.theme.colors.primary.main}; box-shadow: 0px 0px 0px 1px ${props => props.theme.colors.primary.main};
} }
.dropdown-selector { .dropdown-selector {
display: inline-flex; display: inline-flex;
@ -48,7 +48,7 @@ export const StyledDropdown = styled(Dropdown)`
margin-bottom: 5px; margin-bottom: 5px;
} }
.dropdown-selector-value { .dropdown-selector-value {
font-family: ${(props) => props.theme.typography.headings.fontFamily}; font-family: ${props => props.theme.typography.headings.fontFamily};
position: relative; position: relative;
font-size: 0.929em; font-size: 0.929em;
width: 100%; width: 100%;
@ -63,7 +63,7 @@ export const StyledDropdown = styled(Dropdown)`
right: 3px; right: 3px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
border-color: ${(props) => props.theme.colors.primary.main} transparent transparent; border-color: ${props => props.theme.colors.primary.main} transparent transparent;
border-style: solid; border-style: solid;
border-width: 0.35em 0.35em 0; border-width: 0.35em 0.35em 0;
width: 0; width: 0;
@ -107,6 +107,7 @@ export const StyledDropdown = styled(Dropdown)`
input { input {
cursor: pointer; cursor: pointer;
height: 1px; height: 1px;
background-color: transparent;
} }
} }
`; `;
@ -127,8 +128,8 @@ export const SimpleDropdown = styled(StyledDropdown)`
border: none; border: none;
box-shadow: none; box-shadow: none;
.dropdown-selector-value { .dropdown-selector-value {
color: ${(props) => props.theme.colors.primary.main}; color: ${props => props.theme.colors.primary.main};
text-shadow: 0px 0px 0px ${(props) => props.theme.colors.primary.main}; text-shadow: 0px 0px 0px ${props => props.theme.colors.primary.main};
} }
} }
} }

View File

@ -1,6 +1,4 @@
// import { transparentize } from 'polished'; import styled, { extensionsHook, media } from '../styled-components';
import styled, { extensionsHook } from '../styled-components';
import { deprecatedCss } from './mixins'; import { deprecatedCss } from './mixins';
export const PropertiesTableCaption = styled.caption` export const PropertiesTableCaption = styled.caption`
@ -16,6 +14,11 @@ export const PropertyCell = styled.td<{ kind?: string }>`
position: relative; position: relative;
padding: 10px 10px 10px 0; padding: 10px 10px 10px 0;
${media.lessThan('small')`
display: block;
overflow: hidden;
`}
tr:first-of-type > &, tr:first-of-type > &,
tr.last > & { tr.last > & {
border-left-width: 0; border-left-width: 0;
@ -83,6 +86,18 @@ export const PropertyDetailsCell = styled.td`
tr.expanded & { tr.expanded & {
border-bottom: none; border-bottom: none;
} }
${media.lessThan('small')`
padding: 0 20px;
border-bottom: none;
border-left: 1px solid ${props => props.theme.schema.linesColor};
tr.last > & {
border-left: none;
}
`}
${extensionsHook('PropertyDetailsCell')};
`; `;
export const PropertyBullet = styled.span` export const PropertyBullet = styled.span`
@ -125,6 +140,20 @@ export const PropertiesTable = styled.table`
vertical-align: middle; vertical-align: middle;
} }
${media.lessThan('small')`
display: block;
> tr, > tbody > tr {
display: block;
}
`}
${media.lessThan('small', false, ' and (-ms-high-contrast:none)')`
td {
float: left;
width: 100%;
}
`}
& &
${InnerPropertiesWrap}, ${InnerPropertiesWrap},
& &

View File

@ -34,7 +34,7 @@ export const FieldLabel = styled.span`
`; `;
export const TypePrefix = styled(FieldLabel)` export const TypePrefix = styled(FieldLabel)`
color: ${props => transparentize(0.2, props.theme.schema.typeNameColor)}; color: ${props => transparentize(0.1, props.theme.schema.typeNameColor)};
`; `;
export const TypeName = styled(FieldLabel)` export const TypeName = styled(FieldLabel)`
@ -61,13 +61,8 @@ export const RecursiveLabel = styled(FieldLabel)`
font-size: 13px; font-size: 13px;
`; `;
export const NullableLabel = styled(FieldLabel)`
color: #3195a6;
font-size: 13px;
`;
export const PatternLabel = styled(FieldLabel)` export const PatternLabel = styled(FieldLabel)`
color: #3195a6; color: #0e7c86;
&::before, &::before,
&::after { &::after {
font-weight: bold; font-weight: bold;
@ -101,7 +96,6 @@ export const ConstraintItem = styled(FieldLabel)`
margin: 0 ${theme.spacing.unit}px; margin: 0 ${theme.spacing.unit}px;
padding: 0 ${theme.spacing.unit}px; padding: 0 ${theme.spacing.unit}px;
border: 1px solid ${transparentize(0.9, theme.colors.primary.main)}; border: 1px solid ${transparentize(0.9, theme.colors.primary.main)};
font-family: ${theme.typography.code.fontFamily};
}`}; }`};
& + & { & + & {
margin-left: 0; margin-left: 0;

View File

@ -15,21 +15,22 @@ export const headerCommonMixin = level => css`
export const H1 = styled.h1` export const H1 = styled.h1`
${headerCommonMixin(1)}; ${headerCommonMixin(1)};
color: ${({ theme }) => theme.colors.primary.main}; color: ${({ theme }) => theme.colors.text.primary};
${extensionsHook('H1')}; ${extensionsHook('H1')};
`; `;
export const H2 = styled.h2` export const H2 = styled.h2`
${headerCommonMixin(2)}; ${headerCommonMixin(2)};
color: black; color: ${({ theme }) => theme.colors.text.primary};
margin: 0 0 20px;
${extensionsHook('H2')}; ${extensionsHook('H2')};
`; `;
export const H3 = styled.h2` export const H3 = styled.h2`
${headerCommonMixin(3)}; ${headerCommonMixin(3)};
color: black; color: ${({ theme }) => theme.colors.text.primary};
${extensionsHook('H3')}; ${extensionsHook('H3')};
`; `;

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { StoreConsumer } from '../components/StoreBuilder'; import { StoreContext } from '../components/StoreBuilder';
import styled, { css } from '../styled-components'; import styled, { css } from '../styled-components';
import { HistoryService } from '../services'; import { HistoryService } from '../services';
@ -36,32 +36,38 @@ export const linkifyMixin = className => css`
const isModifiedEvent = event => const isModifiedEvent = event =>
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
export class Link extends React.Component<{ to: string; className?: string; children?: any }> { export function Link(props: { to: string; className?: string; children?: any }) {
navigate = (history: HistoryService, event) => { const store = React.useContext(StoreContext);
if ( const clickHandler = React.useCallback(
!event.defaultPrevented && // onClick prevented default (event: React.MouseEvent<HTMLAnchorElement>) => {
event.button === 0 && // ignore everything but left clicks if (!store) return;
!isModifiedEvent(event) // ignore clicks with modifier keys navigate(store.menu.history, event, props.to);
) { },
event.preventDefault(); [store, props.to],
history.replace(this.props.to); );
}
};
render() { if (!store) return null;
return (
<StoreConsumer> return (
{store => ( <a
<a className={props.className}
className={this.props.className} href={store!.menu.history.linkForId(props.to)}
href={store!.menu.history.linkForId(this.props.to)} onClick={clickHandler}
onClick={this.navigate.bind(this, store!.menu.history)} aria-label={props.to}
> >
{this.props.children} {props.children}
</a> </a>
)} );
</StoreConsumer> }
);
function navigate(history: HistoryService, event: React.MouseEvent<HTMLAnchorElement>, to: string) {
if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
history.replace(encodeURI(to));
} }
} }

View File

@ -2,5 +2,5 @@ import { css } from '../styled-components';
export const deprecatedCss = css` export const deprecatedCss = css`
text-decoration: line-through; text-decoration: line-through;
color: #bdccd3; color: #707070;
`; `;

View File

@ -1,10 +1,10 @@
import * as React from 'react'; import * as React from 'react';
import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar'; import PerfectScrollbarType, * as PerfectScrollbarNamespace from 'perfect-scrollbar';
import psStyles from 'perfect-scrollbar/css/perfect-scrollbar.css';
import { OptionsContext } from '../components/OptionsProvider'; import { OptionsContext } from '../components/OptionsProvider';
import styled, { createGlobalStyle } from '../styled-components'; import styled, { createGlobalStyle } from '../styled-components';
import { IS_BROWSER } from '../utils';
/* /*
* perfect scrollbar umd bundle uses exports assignment while module uses default export * perfect scrollbar umd bundle uses exports assignment while module uses default export
@ -12,9 +12,16 @@ import styled, { createGlobalStyle } from '../styled-components';
* That's why the following ugly fix is required * That's why the following ugly fix is required
*/ */
const PerfectScrollbarConstructor = const PerfectScrollbarConstructor =
PerfectScrollbarNamespace.default || ((PerfectScrollbarNamespace as any) as PerfectScrollbarType); PerfectScrollbarNamespace.default || (PerfectScrollbarNamespace as any as PerfectScrollbarType);
const PSStyling = createGlobalStyle`${psStyles && psStyles.toString()}`; let psStyles = '';
if (IS_BROWSER) {
psStyles = require('perfect-scrollbar/css/perfect-scrollbar.css');
psStyles = (typeof psStyles.toString === 'function' && psStyles.toString()) || '';
psStyles = psStyles === '[object Object]' ? '' : psStyles;
}
const PSStyling = createGlobalStyle`${psStyles}`;
const StyledScrollWrapper = styled.div` const StyledScrollWrapper = styled.div`
position: relative; position: relative;
@ -59,7 +66,7 @@ export class PerfectScrollbar extends React.Component<PerfectScrollbarProps> {
return ( return (
<> <>
<PSStyling /> {psStyles && <PSStyling />}
<StyledScrollWrapper className={`scrollbar-container ${className}`} ref={this.handleRef}> <StyledScrollWrapper className={`scrollbar-container ${className}`} ref={this.handleRef}>
{children} {children}
</StyledScrollWrapper> </StyledScrollWrapper>

View File

@ -2,7 +2,7 @@ import styled from '../styled-components';
import { PrismDiv } from './PrismDiv'; import { PrismDiv } from './PrismDiv';
export const SampleControls = styled.div` export const SampleControls = styled.div`
opacity: 0.4; opacity: 0.7;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
text-align: right; text-align: right;
&:focus-within { &:focus-within {

View File

@ -1,5 +1,6 @@
import styled from '../styled-components'; import styled from '../styled-components';
import { darken } from 'polished'; import { darken } from 'polished';
import { deprecatedCss } from './mixins';
export const OneOfList = styled.div` export const OneOfList = styled.div`
margin: 0 0 3px 0; margin: 0 0 3px 0;
@ -14,7 +15,7 @@ export const OneOfLabel = styled.span`
} }
`; `;
export const OneOfButton = styled.button<{ active: boolean }>` export const OneOfButton = styled.button<{ active: boolean; deprecated: boolean }>`
display: inline-block; display: inline-block;
margin-right: 10px; margin-right: 10px;
margin-bottom: 5px; margin-bottom: 5px;
@ -28,6 +29,8 @@ export const OneOfButton = styled.button<{ active: boolean }>`
box-shadow: 0 0 0 1px ${props => props.theme.colors.primary.main}; box-shadow: 0 0 0 1px ${props => props.theme.colors.primary.main};
} }
${({ deprecated }) => (deprecated && deprecatedCss) || ''};
${props => { ${props => {
if (props.active) { if (props.active) {
return ` return `

View File

@ -43,9 +43,8 @@ export const ShelfIcon = styled(IntShelfIcon)`
transform: rotateZ(${props => directionMap[props.direction || 'down']}); transform: rotateZ(${props => directionMap[props.direction || 'down']});
polygon { polygon {
fill: ${props => fill: ${({ color, theme }) =>
(props.color && props.theme.colors[props.color] && props.theme.colors[props.color].main) || (color && theme.colors.responses[color] && theme.colors.responses[color].color) || color};
props.color};
} }
`; `;

View File

@ -44,19 +44,19 @@ export const Tabs = styled(ReactTabs)`
} }
&.tab-success { &.tab-success {
color: ${props => props.theme.colors.responses.success.color}; color: ${props => props.theme.colors.responses.success.tabTextColor};
} }
&.tab-redirect { &.tab-redirect {
color: ${props => props.theme.colors.responses.redirect.color}; color: ${props => props.theme.colors.responses.redirect.tabTextColor};
} }
&.tab-info { &.tab-info {
color: ${props => props.theme.colors.responses.info.color}; color: ${props => props.theme.colors.responses.info.tabTextColor};
} }
&.tab-error { &.tab-error {
color: ${props => props.theme.colors.responses.error.color}; color: ${props => props.theme.colors.responses.error.tabTextColor};
} }
} }
} }

View File

@ -14,6 +14,7 @@ import {
InfoSpanBox, InfoSpanBox,
InfoSpanBoxWrap, InfoSpanBoxWrap,
} from './styled.elements'; } from './styled.elements';
import { l } from '../../services/Labels';
export interface ApiInfoProps { export interface ApiInfoProps {
store: AppStore; store: AppStore;
@ -38,7 +39,12 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
const license = const license =
(info.license && ( (info.license && (
<InfoSpan> <InfoSpan>
License: <a href={info.license.url}>{info.license.name}</a> License:{' '}
{info.license.identifier ? (
info.license.identifier
) : (
<a href={info.license.url}>{info.license.name}</a>
)}
</InfoSpan> </InfoSpan>
)) || )) ||
null; null;
@ -79,14 +85,14 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
</ApiHeader> </ApiHeader>
{!hideDownloadButton && ( {!hideDownloadButton && (
<p> <p>
Download OpenAPI specification: {l('downloadSpecification')}:
<DownloadButton <DownloadButton
download={downloadFilename || true} download={downloadFilename || true}
target="_blank" target="_blank"
href={downloadLink} href={downloadLink}
onClick={this.handleDownloadClick} onClick={this.handleDownloadClick}
> >
Download {l('download')}
</DownloadButton> </DownloadButton>
</p> </p>
)} )}
@ -100,6 +106,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
)) || )) ||
null} null}
</StyledMarkdownBlock> </StyledMarkdownBlock>
<Markdown source={store.spec.info.summary} data-role="redoc-summary" />
<Markdown source={store.spec.info.description} data-role="redoc-description" /> <Markdown source={store.spec.info.description} data-role="redoc-description" />
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />} {externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
</MiddlePanel> </MiddlePanel>

View File

@ -17,4 +17,5 @@ const Link = styled.a`
display: inline-block; display: inline-block;
`; `;
// eslint-disable-next-line react/display-name
export const LinkWrap = url => Component => <Link href={url}>{Component}</Link>; export const LinkWrap = url => Component => <Link href={url}>{Component}</Link>;

View File

@ -48,10 +48,10 @@ const CallbackTitleWrapper = styled.button`
`; `;
const CallbackName = styled.span<{ deprecated?: boolean }>` const CallbackName = styled.span<{ deprecated?: boolean }>`
text-decoration: ${(props) => (props.deprecated ? 'line-through' : 'none')}; text-decoration: ${props => (props.deprecated ? 'line-through' : 'none')};
margin-right: 8px; margin-right: 8px;
`; `;
const OperationBadgeStyled = styled(OperationBadge)` const OperationBadgeStyled = styled(OperationBadge)`
margin: 0px 5px 0px 0px; margin: 0 5px 0 0;
`; `;

View File

@ -67,6 +67,7 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
const normalizedUrl = options.expandDefaultServerVariables const normalizedUrl = options.expandDefaultServerVariables
? expandDefaultServerVariables(server.url, server.variables) ? expandDefaultServerVariables(server.url, server.variables)
: server.url; : server.url;
const basePath = getBasePath(normalizedUrl);
return ( return (
<ServerItem key={normalizedUrl}> <ServerItem key={normalizedUrl}>
<Markdown source={server.description || ''} compact={true} /> <Markdown source={server.description || ''} compact={true} />
@ -74,7 +75,9 @@ export class Endpoint extends React.Component<EndpointProps, EndpointState> {
<ServerUrl> <ServerUrl>
<span> <span>
{hideHostname || options.hideHostname {hideHostname || options.hideHostname
? getBasePath(normalizedUrl) ? basePath === '/'
? ''
: basePath
: normalizedUrl} : normalizedUrl}
</span> </span>
{operation.path} {operation.path}

View File

@ -35,7 +35,7 @@ export const EndpointInfo = styled.button<{ expanded?: boolean; inverted?: boole
(props.expanded && !props.inverted && `border-color: ${props.theme.colors.border.dark};`) || ''} (props.expanded && !props.inverted && `border-color: ${props.theme.colors.border.dark};`) || ''}
.${ServerRelativeURL} { .${ServerRelativeURL} {
color: ${props => (props.inverted ? props.theme.colors.text.primary : '#ffffff')} color: ${props => (props.inverted ? props.theme.colors.text.primary : '#ffffff')};
} }
&:focus { &:focus {
box-shadow: inset 0 2px 2px rgba(0, 0, 0, 0.45), 0 2px 0 rgba(128, 128, 128, 0.25); box-shadow: inset 0 2px 2px rgba(0, 0, 0, 0.45), 0 2px 0 rgba(128, 128, 128, 0.25);

View File

@ -0,0 +1,25 @@
import * as React from 'react';
import { TypeFormat, TypePrefix } from '../../common-elements/fields';
import { ConstraintsView } from './FieldContstraints';
import { Pattern } from './Pattern';
import { SchemaModel } from '../../services';
import styled from '../../styled-components';
export function ArrayItemDetails({ schema }: { schema: SchemaModel }) {
if (!schema || (schema.type === 'string' && !schema.constraints.length)) return null;
return (
<Wrapper>
[ items
{schema.displayFormat && <TypeFormat> &lt;{schema.displayFormat} &gt;</TypeFormat>}
<ConstraintsView constraints={schema.constraints} />
<Pattern schema={schema} />
{schema.items && <ArrayItemDetails schema={schema.items} />} ]
</Wrapper>
);
}
const Wrapper = styled(TypePrefix)`
margin: 0 5px;
vertical-align: text-top;
`;

View File

@ -8,7 +8,7 @@ import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
export interface EnumValuesProps { export interface EnumValuesProps {
values: string[]; values: string[];
type: string; isArrayType: boolean;
} }
export interface EnumValuesState { export interface EnumValuesState {
@ -27,7 +27,7 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
} }
render() { render() {
const { values, type } = this.props; const { values, isArrayType } = this.props;
const { collapsed } = this.state; const { collapsed } = this.state;
// TODO: provide context interface in more elegant way // TODO: provide context interface in more elegant way
@ -55,11 +55,11 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
return ( return (
<div> <div>
<FieldLabel> <FieldLabel>
{type === 'array' ? l('enumArray') : ''}{' '} {isArrayType ? l('enumArray') : ''}{' '}
{values.length === 1 ? l('enumSingleValue') : l('enum')}: {values.length === 1 ? l('enumSingleValue') : l('enum')}:
</FieldLabel>{' '} </FieldLabel>{' '}
{displayedItems.map((value, idx) => { {displayedItems.map((value, idx) => {
const exampleValue = enumSkipQuotes ? value : JSON.stringify(value); const exampleValue = enumSkipQuotes ? String(value) : JSON.stringify(value);
return ( return (
<React.Fragment key={idx}> <React.Fragment key={idx}>
<ExampleValue>{exampleValue}</ExampleValue>{' '} <ExampleValue>{exampleValue}</ExampleValue>{' '}

View File

@ -0,0 +1,36 @@
import * as React from 'react';
import { FieldLabel, ExampleValue } from '../../common-elements/fields';
import { getSerializedValue } from '../../utils';
import { l } from '../../services/Labels';
import { FieldModel } from '../../services';
import styled from '../../styled-components';
export function Examples({ field }: { field: FieldModel }) {
if (!field.examples) {
return null;
}
return (
<>
<FieldLabel> {l('examples')}: </FieldLabel>
<ExamplesList>
{Object.values(field.examples).map((example, idx) => {
return (
<li key={idx}>
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> -{' '}
{example.summary || example.description}
</li>
);
})}
</ExamplesList>
</>
);
}
const ExamplesList = styled.ul`
margin-top: 1em;
padding-left: 0;
list-style-position: inside;
`;

View File

@ -32,7 +32,7 @@ export interface FieldProps extends SchemaOptions {
export class Field extends React.Component<FieldProps> { export class Field extends React.Component<FieldProps> {
toggle = () => { toggle = () => {
if (this.props.field.expanded === undefined && this.props.expandByDefault) { if (this.props.field.expanded === undefined && this.props.expandByDefault) {
this.props.field.expanded = false; this.props.field.collapse();
} else { } else {
this.props.field.toggle(); this.props.field.toggle();
} }
@ -64,7 +64,7 @@ export class Field extends React.Component<FieldProps> {
onKeyPress={this.handleKeyPress} onKeyPress={this.handleKeyPress}
aria-label="expand properties" aria-label="expand properties"
> >
{name} <span>{name}</span>
<ShelfIcon direction={expanded ? 'down' : 'right'} /> <ShelfIcon direction={expanded ? 'down' : 'right'} />
</button> </button>
{required && <RequiredLabel> required </RequiredLabel>} {required && <RequiredLabel> required </RequiredLabel>}
@ -72,7 +72,7 @@ export class Field extends React.Component<FieldProps> {
) : ( ) : (
<PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}> <PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}>
<PropertyBullet /> <PropertyBullet />
{name} <span>{name}</span>
{required && <RequiredLabel> required </RequiredLabel>} {required && <RequiredLabel> required </RequiredLabel>}
</PropertyNameCell> </PropertyNameCell>
); );
@ -94,6 +94,7 @@ export class Field extends React.Component<FieldProps> {
skipReadOnly={this.props.skipReadOnly} skipReadOnly={this.props.skipReadOnly}
skipWriteOnly={this.props.skipWriteOnly} skipWriteOnly={this.props.skipWriteOnly}
showTitle={this.props.showTitle} showTitle={this.props.showTitle}
level={this.props.level}
/> />
</InnerPropertiesWrap> </InnerPropertiesWrap>
</PropertyCellWithInner> </PropertyCellWithInner>

View File

@ -7,18 +7,18 @@ export interface FieldDetailProps {
raw?: boolean; raw?: boolean;
} }
export class FieldDetail extends React.PureComponent<FieldDetailProps> { function FieldDetailComponent({ value, label, raw }: FieldDetailProps) {
render() { if (value === undefined) {
if (this.props.value === undefined) { return null;
return null;
}
const value = this.props.raw ? this.props.value : JSON.stringify(this.props.value);
return (
<div>
<FieldLabel> {this.props.label} </FieldLabel> <ExampleValue>{value}</ExampleValue>
</div>
);
} }
const stringifyValue = raw ? String(value) : JSON.stringify(value);
return (
<div>
<FieldLabel> {label} </FieldLabel> <ExampleValue>{stringifyValue}</ExampleValue>
</div>
);
} }
export const FieldDetail = React.memo<FieldDetailProps>(FieldDetailComponent);

View File

@ -1,21 +1,19 @@
import * as React from 'react'; import * as React from 'react';
import { import {
NullableLabel,
PatternLabel,
RecursiveLabel, RecursiveLabel,
TypeFormat, TypeFormat,
TypeName, TypeName,
TypePrefix, TypePrefix,
TypeTitle, TypeTitle,
ToggleButton,
} from '../../common-elements/fields'; } from '../../common-elements/fields';
import { serializeParameterValue } from '../../utils/openapi'; import { getSerializedValue } from '../../utils';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation'; import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { EnumValues } from './EnumValues'; import { EnumValues } from './EnumValues';
import { Extensions } from './Extensions'; import { Extensions } from './Extensions';
import { FieldProps } from './Field'; import { FieldProps } from './Field';
import { Examples } from './Examples';
import { ConstraintsView } from './FieldContstraints'; import { ConstraintsView } from './FieldContstraints';
import { FieldDetail } from './FieldDetail'; import { FieldDetail } from './FieldDetail';
@ -23,93 +21,92 @@ import { Badge } from '../../common-elements/';
import { l } from '../../services/Labels'; import { l } from '../../services/Labels';
import { OptionsContext } from '../OptionsProvider'; import { OptionsContext } from '../OptionsProvider';
import { Pattern } from './Pattern';
import { ArrayItemDetails } from './ArrayItemDetails';
const MAX_PATTERN_LENGTH = 45; function FieldDetailsComponent(props: FieldProps) {
const { enumSkipQuotes, hideSchemaTitles } = React.useContext(OptionsContext);
export class FieldDetails extends React.PureComponent<FieldProps, { patternShown: boolean }> { const { showExamples, field, renderDiscriminatorSwitch } = props;
state = { const { schema, description, deprecated, extensions, in: _in, const: _const } = field;
patternShown: false, const isArrayType = schema.type === 'array';
};
static contextType = OptionsContext; const rawDefault = enumSkipQuotes || _in === 'header'; // having quotes around header field default values is confusing and inappropriate
togglePattern = () => { const renderedExamples = React.useMemo<JSX.Element | null>(() => {
this.setState({ if (showExamples && (field.example !== undefined || field.examples !== undefined)) {
patternShown: !this.state.patternShown, if (field.examples !== undefined) {
}); return <Examples field={field} />;
};
render() {
const { showExamples, field, renderDiscriminatorSwitch } = this.props;
const { patternShown } = this.state;
const { enumSkipQuotes, hideSchemaTitles } = this.context;
const { schema, description, example, deprecated } = field;
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
let exampleField: JSX.Element | null = null;
if (showExamples && example !== undefined) {
const label = l('example') + ':';
if (field.in && (field.style || field.serializationMime)) {
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
const serializedValue = decodeURIComponent(serializeParameterValue(field, example));
exampleField = <FieldDetail label={label} value={serializedValue} raw={true} />;
} else { } else {
exampleField = <FieldDetail label={label} value={example} />; return (
<FieldDetail
label={l('example') + ':'}
value={getSerializedValue(field, field.example)}
raw={Boolean(field.in)}
/>
);
} }
} }
return ( return null;
}, [field, showExamples]);
return (
<div>
<div> <div>
<div> <TypePrefix>{schema.typePrefix}</TypePrefix>
<TypePrefix>{schema.typePrefix}</TypePrefix> <TypeName>{schema.displayType}</TypeName>
<TypeName>{schema.displayType}</TypeName> {schema.displayFormat && (
{schema.displayFormat && ( <TypeFormat>
<TypeFormat> {' '}
{' '} &lt;
&lt; {schema.displayFormat}
{schema.displayFormat} &gt;{' '}
&gt;{' '} </TypeFormat>
</TypeFormat>
)}
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
<ConstraintsView constraints={schema.constraints} />
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>}
{schema.pattern && (
<>
<PatternLabel>
{patternShown || schema.pattern.length < MAX_PATTERN_LENGTH
? schema.pattern
: `${schema.pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
</PatternLabel>
{schema.pattern.length > MAX_PATTERN_LENGTH && (
<ToggleButton onClick={this.togglePattern}>
{patternShown ? 'Hide pattern' : 'Show pattern'}
</ToggleButton>
)}
</>
)}
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
</div>
{deprecated && (
<div>
<Badge type="warning"> {l('deprecated')} </Badge>
</div>
)} )}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} /> {schema.contentEncoding && (
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '} <TypeFormat>
{exampleField} {' '}
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />} &lt;
<div> {schema.contentEncoding}
<Markdown compact={true} source={description} /> &gt;{' '}
</div> </TypeFormat>
{schema.externalDocs && (
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
)} )}
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null} {schema.contentMediaType && (
<TypeFormat>
{' '}
&lt;
{schema.contentMediaType}
&gt;{' '}
</TypeFormat>
)}
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
<ConstraintsView constraints={schema.constraints} />
<Pattern schema={schema} />
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
{isArrayType && schema.items && <ArrayItemDetails schema={schema.items} />}
</div> </div>
); {deprecated && (
} <div>
<Badge type="warning"> {l('deprecated')} </Badge>
</div>
)}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
{!renderDiscriminatorSwitch && (
<EnumValues isArrayType={isArrayType} values={schema.enum} />
)}{' '}
{renderedExamples}
<Extensions extensions={{ ...extensions, ...schema.extensions }} />
<div>
<Markdown compact={true} source={description} />
</div>
{schema.externalDocs && (
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
)}
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(props)) || null}
{(_const && <FieldDetail label={l('const') + ':'} value={_const} />) || null}
</div>
);
} }
export const FieldDetails = React.memo<FieldProps>(FieldDetailsComponent);

View File

@ -0,0 +1,33 @@
import * as React from 'react';
import { PatternLabel, ToggleButton } from '../../common-elements/fields';
import { OptionsContext } from '../OptionsProvider';
import { SchemaModel } from '../../services';
const MAX_PATTERN_LENGTH = 45;
export function Pattern(props: { schema: SchemaModel }) {
const pattern = props.schema.pattern;
const { hideSchemaPattern } = React.useContext(OptionsContext);
const [isPatternShown, setIsPatternShown] = React.useState(false);
const togglePattern = React.useCallback(
() => setIsPatternShown(!isPatternShown),
[isPatternShown],
);
if (!pattern || hideSchemaPattern) return null;
return (
<>
<PatternLabel>
{isPatternShown || pattern.length < MAX_PATTERN_LENGTH
? pattern
: `${pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
</PatternLabel>
{pattern.length > MAX_PATTERN_LENGTH && (
<ToggleButton onClick={togglePattern}>
{isPatternShown ? 'Hide pattern' : 'Show pattern'}
</ToggleButton>
)}
</>
);
}

View File

@ -51,7 +51,9 @@ class Json extends React.PureComponent<JsonProps> {
expandAll = () => { expandAll = () => {
const elements = this.node.getElementsByClassName('collapsible'); const elements = this.node.getElementsByClassName('collapsible');
for (const collapsed of Array.prototype.slice.call(elements)) { for (const collapsed of Array.prototype.slice.call(elements)) {
(collapsed.parentNode as Element)!.classList.remove('collapsed'); const parentNode = collapsed.parentNode as Element;
parentNode.classList.remove('collapsed');
parentNode.querySelector('.collapser')!.setAttribute('aria-label', 'collapse');
} }
}; };
@ -61,29 +63,44 @@ class Json extends React.PureComponent<JsonProps> {
const elementsArr = Array.prototype.slice.call(elements, 1); const elementsArr = Array.prototype.slice.call(elements, 1);
for (const expanded of elementsArr) { for (const expanded of elementsArr) {
(expanded.parentNode as Element)!.classList.add('collapsed'); const parentNode = expanded.parentNode as Element;
parentNode.classList.add('collapsed');
parentNode.querySelector('.collapser')!.setAttribute('aria-label', 'expand');
} }
}; };
clickListener = (event: MouseEvent) => { collapseElement = (target: HTMLElement) => {
let collapsed; let collapsed;
const target = event.target as HTMLElement;
if (target.className === 'collapser') { if (target.className === 'collapser') {
collapsed = target.parentElement!.getElementsByClassName('collapsible')[0]; collapsed = target.parentElement!.getElementsByClassName('collapsible')[0];
if (collapsed.parentElement.classList.contains('collapsed')) { if (collapsed.parentElement.classList.contains('collapsed')) {
collapsed.parentElement.classList.remove('collapsed'); collapsed.parentElement.classList.remove('collapsed');
target.setAttribute('aria-label', 'collapse');
} else { } else {
collapsed.parentElement.classList.add('collapsed'); collapsed.parentElement.classList.add('collapsed');
target.setAttribute('aria-label', 'expand');
} }
} }
}; };
clickListener = (event: MouseEvent) => {
this.collapseElement(event.target as HTMLElement);
};
focusListener = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
this.collapseElement(event.target as HTMLElement);
}
};
componentDidMount() { componentDidMount() {
this.node!.addEventListener('click', this.clickListener); this.node!.addEventListener('click', this.clickListener);
this.node!.addEventListener('focus', this.focusListener);
} }
componentWillUnmount() { componentWillUnmount() {
this.node!.removeEventListener('click', this.clickListener); this.node!.removeEventListener('click', this.clickListener);
this.node!.removeEventListener('focus', this.focusListener);
} }
} }

View File

@ -1,8 +1,9 @@
import { css } from '../../styled-components'; import { css } from '../../styled-components';
export const jsonStyles = css` export const jsonStyles = css`
.redoc-json > .collapser { .redoc-json code > .collapser {
display: none; display: none;
pointer-events: none;
} }
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${props => props.theme.typography.code.fontFamily};
@ -47,8 +48,32 @@ export const jsonStyles = css`
} }
.collapser { .collapser {
background-color: transparent;
border: 0;
color: #fff;
font-family: ${props => props.theme.typography.code.fontFamily};
font-size: ${props => props.theme.typography.code.fontSize};
padding-right: 6px; padding-right: 6px;
padding-left: 6px; padding-left: 6px;
padding-top: 0;
padding-bottom: 0;
display: flex;
align-items: center;
justify-content: center;
width: 15px;
height: 15px;
position: absolute;
top: 4px;
left: -1.5em;
cursor: default;
user-select: none;
-webkit-user-select: none;
padding: 2px;
&:focus {
outline-color: #fff;
outline-style: dotted;
outline-width: 1px;
}
} }
ul { ul {
@ -83,13 +108,4 @@ export const jsonStyles = css`
.collapsed > .ellipsis { .collapsed > .ellipsis {
display: inherit; display: inherit;
} }
.collapser {
position: absolute;
top: 1px;
left: -1.5em;
cursor: default;
user-select: none;
-webkit-user-select: none;
}
`; `;

View File

@ -26,7 +26,6 @@ export const StyledMarkdownBlock = styled(
{ compact?: boolean; inline?: boolean } { compact?: boolean; inline?: boolean }
>, >,
)` )`
font-family: ${props => props.theme.typography.fontFamily}; font-family: ${props => props.theme.typography.fontFamily};
font-weight: ${props => props.theme.typography.fontWeightRegular}; font-weight: ${props => props.theme.typography.fontWeightRegular};
line-height: ${props => props.theme.typography.lineHeight}; line-height: ${props => props.theme.typography.lineHeight};
@ -81,13 +80,13 @@ export const StyledMarkdownBlock = styled(
pre { pre {
font-family: ${props => props.theme.typography.code.fontFamily}; font-family: ${props => props.theme.typography.code.fontFamily};
white-space:${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')}; white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
background-color: ${({ theme }) => theme.codeBlock.backgroundColor}; background-color: ${({ theme }) => theme.codeBlock.backgroundColor};
color: white; color: white;
padding: ${props => props.theme.spacing.unit * 4}px; padding: ${props => props.theme.spacing.unit * 4}px;
overflow-x: auto; overflow-x: auto;
line-height: normal; line-height: normal;
border-radius: 0px border-radius: 0px;
border: 1px solid rgba(38, 50, 56, 0.1); border: 1px solid rgba(38, 50, 56, 0.1);
code { code {
@ -121,7 +120,8 @@ export const StyledMarkdownBlock = styled(
margin: 0; margin: 0;
margin-bottom: 1em; margin-bottom: 1em;
ul, ol { ul,
ol {
margin-bottom: 0; margin-bottom: 0;
margin-top: 0; margin-top: 0;
} }

View File

@ -17,12 +17,7 @@ import { RequestSamples } from '../RequestSamples/RequestSamples';
import { ResponsesList } from '../Responses/ResponsesList'; import { ResponsesList } from '../Responses/ResponsesList';
import { ResponseSamples } from '../ResponseSamples/ResponseSamples'; import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement'; import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
import { SECTION_ATTR } from '../../services';
const OperationRow = styled(Row)`
backface-visibility: hidden;
contain: content;
overflow: hidden;
`;
const Description = styled.div` const Description = styled.div`
margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px; margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px;
@ -42,8 +37,8 @@ export class Operation extends React.Component<OperationProps> {
return ( return (
<OptionsContext.Consumer> <OptionsContext.Consumer>
{(options) => ( {options => (
<OperationRow> <Row {...{ [SECTION_ATTR]: operation.operationHash }} id={operation.operationHash}>
<MiddlePanel> <MiddlePanel>
<H2> <H2>
<ShareLink to={operation.id} /> <ShareLink to={operation.id} />
@ -71,7 +66,7 @@ export class Operation extends React.Component<OperationProps> {
<ResponseSamples operation={operation} /> <ResponseSamples operation={operation} />
<CallbackSamples callbacks={operation.callbacks} /> <CallbackSamples callbacks={operation.callbacks} />
</DarkRightPanel> </DarkRightPanel>
</OperationRow> </Row>
)} )}
</OptionsContext.Consumer> </OptionsContext.Consumer>
); );

View File

@ -67,15 +67,24 @@ function DropdownWithinHeader(props) {
); );
} }
export function BodyContent(props: { content: MediaContentModel; description?: string }): JSX.Element { export function BodyContent(props: {
content: MediaContentModel;
description?: string;
}): JSX.Element {
const { content, description } = props; const { content, description } = props;
const { isRequestType } = content;
return ( return (
<MediaTypesSwitch content={content} renderDropdown={DropdownWithinHeader}> <MediaTypesSwitch content={content} renderDropdown={DropdownWithinHeader}>
{({ schema }) => { {({ schema }) => {
return ( return (
<> <>
{description !== undefined && <Markdown source={description} />} {description !== undefined && <Markdown source={description} />}
<Schema skipReadOnly={true} key="schema" schema={schema} /> <Schema
skipReadOnly={isRequestType}
skipWriteOnly={!isRequestType}
key="schema"
schema={schema}
/>
</> </>
); );
}} }}

View File

@ -20,7 +20,7 @@ export const DropdownLabel = styled.span`
top: -11px; top: -11px;
left: 12px; left: 12px;
font-weight: ${({ theme }) => theme.typography.fontWeightBold}; font-weight: ${({ theme }) => theme.typography.fontWeightBold};
color: ${({ theme }) => transparentize(0.6, theme.rightPanel.textColor)}; color: ${({ theme }) => transparentize(0.3, theme.rightPanel.textColor)};
`; `;
export const DropdownWrapper = styled.div` export const DropdownWrapper = styled.div`

View File

@ -39,7 +39,7 @@ export class Redoc extends React.Component<RedocProps> {
const store = this.props.store; const store = this.props.store;
return ( return (
<ThemeProvider theme={options.theme}> <ThemeProvider theme={options.theme}>
<StoreProvider value={this.props.store}> <StoreProvider value={store}>
<OptionsProvider value={options}> <OptionsProvider value={options}>
<RedocWrap className="redoc-wrap"> <RedocWrap className="redoc-wrap">
<StickyResponsiveSidebar menu={menu} className="menu-content"> <StickyResponsiveSidebar menu={menu} className="menu-content">

View File

@ -1,7 +1,10 @@
import * as PropTypes from 'prop-types';
import * as React from 'react'; import * as React from 'react';
import { RedocNormalizedOptions, RedocRawOptions } from '../services/RedocNormalizedOptions'; import {
argValueToBoolean,
RedocNormalizedOptions,
RedocRawOptions,
} from '../services/RedocNormalizedOptions';
import { ErrorBoundary } from './ErrorBoundary'; import { ErrorBoundary } from './ErrorBoundary';
import { Loading } from './Loading/Loading'; import { Loading } from './Loading/Loading';
import { Redoc } from './Redoc/Redoc'; import { Redoc } from './Redoc/Redoc';
@ -14,47 +17,32 @@ export interface RedocStandaloneProps {
onLoaded?: (e?: Error) => any; onLoaded?: (e?: Error) => any;
} }
export class RedocStandalone extends React.PureComponent<RedocStandaloneProps> { declare let __webpack_nonce__: string;
static propTypes = {
spec: (props, _, componentName) => {
if (!props.spec && !props.specUrl) {
return new Error(
`One of props 'spec' or 'specUrl' was not specified in '${componentName}'.`,
);
}
return null;
},
specUrl: (props, _, componentName) => { export const RedocStandalone = function (props: RedocStandaloneProps) {
if (!props.spec && !props.specUrl) { const { spec, specUrl, options = {}, onLoaded } = props;
return new Error( const hideLoading = argValueToBoolean(options.hideLoading, false);
`One of props 'spec' or 'specUrl' was not specified in '${componentName}'.`,
);
}
return null;
},
options: PropTypes.any,
onLoaded: PropTypes.any,
};
render() { const normalizedOpts = new RedocNormalizedOptions(options);
const { spec, specUrl, options = {}, onLoaded } = this.props;
const hideLoading = options.hideLoading !== undefined;
const normalizedOpts = new RedocNormalizedOptions(options); if (normalizedOpts.nonce !== undefined) {
try {
return ( // eslint-disable-next-line @typescript-eslint/no-unused-vars
<ErrorBoundary> __webpack_nonce__ = normalizedOpts.nonce;
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}> } catch {} // If we have exception, Webpack was not used to run this.
{({ loading, store }) =>
!loading ? (
<Redoc store={store!} />
) : hideLoading ? null : (
<Loading color={normalizedOpts.theme.colors.primary.main} />
)
}
</StoreBuilder>
</ErrorBoundary>
);
} }
}
return (
<ErrorBoundary>
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}>
{({ loading, store }) =>
!loading ? (
<Redoc store={store!} />
) : hideLoading ? null : (
<Loading color={normalizedOpts.theme.colors.primary.main} />
)
}
</StoreBuilder>
</ErrorBoundary>
);
};

View File

@ -6,6 +6,7 @@ import { SourceCodeWithCopy } from '../SourceCode/SourceCode';
import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements'; import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements';
import { OptionsContext } from '../OptionsProvider'; import { OptionsContext } from '../OptionsProvider';
import { l } from '../../services/Labels';
export interface RequestSamplesProps { export interface RequestSamplesProps {
operation: OperationModel; operation: OperationModel;
@ -26,7 +27,7 @@ export class RequestSamples extends React.Component<RequestSamplesProps> {
return ( return (
(hasSamples && ( (hasSamples && (
<div> <div>
<RightPanelHeader> Request samples </RightPanelHeader> <RightPanelHeader> {l('requestSamples')} </RightPanelHeader>
<Tabs defaultIndex={0}> <Tabs defaultIndex={0}>
<TabList hidden={hideTabList}> <TabList hidden={hideTabList}>

View File

@ -5,6 +5,7 @@ import { OperationModel } from '../../services/models';
import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements'; import { RightPanelHeader, Tab, TabList, TabPanel, Tabs } from '../../common-elements';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
import { l } from '../../services/Labels';
export interface ResponseSamplesProps { export interface ResponseSamplesProps {
operation: OperationModel; operation: OperationModel;
@ -23,7 +24,7 @@ export class ResponseSamples extends React.Component<ResponseSamplesProps> {
return ( return (
(responses.length > 0 && ( (responses.length > 0 && (
<div> <div>
<RightPanelHeader> Response samples </RightPanelHeader> <RightPanelHeader> {l('responseSamples')} </RightPanelHeader>
<Tabs defaultIndex={0}> <Tabs defaultIndex={0}>
<TabList> <TabList>

View File

@ -1,39 +1,47 @@
import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { observer } from 'mobx-react';
import { ResponseModel } from '../../services/models'; import type { ResponseModel, MediaTypeModel } from '../../services/models';
import { ResponseDetails } from './ResponseDetails'; import { ResponseDetails } from './ResponseDetails';
import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements'; import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements';
@observer export interface ResponseViewProps {
export class ResponseView extends React.Component<{ response: ResponseModel }> { response: ResponseModel;
toggle = () => {
this.props.response.toggle();
};
render() {
const { headers, type, summary, description, code, expanded, content } = this.props.response;
const mimes =
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined);
const empty = headers.length === 0 && mimes.length === 0 && !description;
return (
<div>
<StyledResponseTitle
onClick={this.toggle}
type={type}
empty={empty}
title={summary || ''}
code={code}
opened={expanded}
/>
{expanded && !empty && (
<ResponseDetailsWrap>
<ResponseDetails response={this.props.response} />
</ResponseDetailsWrap>
)}
</div>
);
}
} }
export const ResponseView = observer(({ response }: ResponseViewProps): React.ReactElement => {
const { extensions, headers, type, summary, description, code, expanded, content } = response;
const mimes = React.useMemo<MediaTypeModel[]>(
() =>
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined),
[content],
);
const empty = React.useMemo<boolean>(
() =>
(!extensions || Object.keys(extensions).length === 0) &&
headers.length === 0 &&
mimes.length === 0 &&
!description,
[extensions, headers, mimes, description],
);
return (
<div>
<StyledResponseTitle
onClick={() => response.toggle()}
type={type}
empty={empty}
title={summary || ''}
code={code}
opened={expanded}
/>
{expanded && !empty && (
<ResponseDetailsWrap>
<ResponseDetails response={response} />
</ResponseDetailsWrap>
)}
</div>
);
});

View File

@ -7,15 +7,17 @@ import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema'; import { Schema } from '../Schema';
import { Extensions } from '../Fields/Extensions';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders'; import { ResponseHeaders } from './ResponseHeaders';
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> { export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
render() { render() {
const { description, headers, content } = this.props.response; const { description, extensions, headers, content } = this.props.response;
return ( return (
<> <>
{description && <Markdown source={description} />} {description && <Markdown source={description} />}
<Extensions extensions={extensions} />
<ResponseHeaders headers={headers} /> <ResponseHeaders headers={headers} />
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}> <MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
{({ schema }) => { {({ schema }) => {

View File

@ -14,27 +14,34 @@ export interface ResponseTitleProps {
onClick?: () => void; onClick?: () => void;
} }
export class ResponseTitle extends React.PureComponent<ResponseTitleProps> { function ResponseTitleComponent({
render() { title,
const { title, type, empty, code, opened, className, onClick } = this.props; type,
return ( empty,
<button code,
className={className} opened,
onClick={(!empty && onClick) || undefined} className,
aria-expanded={opened} onClick,
disabled={empty} }: ResponseTitleProps): React.ReactElement {
> return (
{!empty && ( <button
<ShelfIcon className={className}
size={'1.5em'} onClick={(!empty && onClick) || undefined}
color={type} aria-expanded={opened}
direction={opened ? 'down' : 'right'} disabled={empty}
float={'left'} >
/> {!empty && (
)} <ShelfIcon
<Code>{code} </Code> size={'1.5em'}
<Markdown compact={true} inline={true} source={title} /> color={type}
</button> direction={opened ? 'down' : 'right'}
); float={'left'}
} />
)}
<Code>{code} </Code>
<Markdown compact={true} inline={true} source={title} />
</button>
);
} }
export const ResponseTitle = React.memo<ResponseTitleProps>(ResponseTitleComponent);

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