Merge branch 'master' into folding-buttons-visibility

This commit is contained in:
anastasiia-developer 2022-04-20 18:01:42 +03:00
commit 59767ed2a0
178 changed files with 44090 additions and 14798 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

1
.gitignore vendored
View File

@ -37,3 +37,4 @@ stats.json
yarn.lock yarn.lock
.idea .idea
.vscode .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,3 +1,267 @@
# [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) # [2.0.0-rc.44](https://github.com/Redocly/redoc/compare/v2.0.0-rc.43...v2.0.0-rc.44) (2020-10-16)

361
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 - Responsive three-panel design with menu/scrolling synchronization
- [redoc-cli](https://github.com/Redocly/redoc/blob/master/cli/README.md) with ability to bundle your docs into **zero-dependency** HTML file - [Multiple deployment options](https://redoc.ly/docs/redoc/quickstart/intro/)
- Server Side Rendering ready - [Server-side rendering (SSR) ready](https://redoc.ly/docs/redoc/quickstart/cli/#redoc-cli-commands)
- The widest OpenAPI v2.0 features support (yes, it supports even `discriminator`) <br> - Ability to integrate your API introduction into the side menu
![](docs/images/discriminator-demo.gif) - [Simple integration with `create-react-app`](https://redoc.ly/docs/redoc/quickstart/react/)
- OpenAPI 3.0 support
[Example repo](https://github.com/APIs-guru/create-react-app-redoc)
- [Command-line interface to bundle your docs into a **zero-dependency** HTML file](https://redoc.ly/docs/redoc/quickstart/cli/)
- Neat **interactive** documentation for nested objects <br> - Neat **interactive** documentation for nested objects <br>
![](docs/images/nested-demo.gif) ![](docs/images/nested-demo.gif)
## Customization options
[<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
- 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> - Code samples support (via vendor extension) <br>
![](docs/images/code-samples-demo.gif) ![](docs/images/code-samples-demo.gif)
- 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.
- High-level grouping in side-menu via [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) vendor extension
- Simple integration with `create-react-app` ([sample](https://github.com/APIs-guru/create-react-app-redoc))
- Branding/customizations via [`theme` option](#redoc-options-object)
## Roadmap
- [x] ~~[OpenAPI v3.0 support](https://github.com/Redocly/redoc/issues/312)~~
- [x] ~~performance optimizations~~
- [x] ~~better navigation (menu improvements + search)~~
- [x] ~~React rewrite~~
- [x] ~~docs pre-rendering (performance and SEO)~~
- [ ] ability to simple branding/styling
- [ ] built-in API Console
## 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 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,65 +39,27 @@ interface Options {
redocOptions?: any; redocOptions?: any;
} }
const BUNDLES_DIR = dirname(require.resolve('redoc')); export const mimeTypes = {
'.html': 'text/html',
/* tslint:disable-next-line */ '.js': 'text/javascript',
YargsParser.command( '.css': 'text/css',
'serve <spec>', '.json': 'application/json',
'start the server', '.png': 'image/png',
yargs => { '.jpg': 'image/jpg',
yargs.positional('spec', { '.gif': 'image/gif',
describe: 'path or URL to your spec', '.svg': 'image/svg+xml',
}); '.wav': 'audio/wav',
'.mp4': 'video/mp4',
yargs.options('title', { '.woff': 'application/font-woff',
describe: 'Page Title', '.ttf': 'application/font-ttf',
type: 'string', '.eot': 'application/vnd.ms-fontobject',
}); '.otf': 'application/font-otf',
'.wasm': 'application/wasm',
yargs.option('s', {
alias: 'ssr',
describe: 'Enable server-side rendering',
type: 'boolean',
});
yargs.option('p', {
alias: 'port',
type: 'number',
default: 8080,
});
yargs.option('w', {
alias: 'watch',
type: 'boolean',
});
yargs.demandOption('spec');
return yargs;
},
async argv => {
const config: Options = {
ssr: argv.ssr as boolean,
title: argv.title as string,
watch: argv.watch as boolean,
templateFileName: argv.template as string,
templateOptions: argv.templateOptions || {},
redocOptions: getObjectOrJSON(argv.options),
}; };
console.log(config); const BUNDLES_DIR = dirname(require.resolve('redoc'));
try { const builderForBuildCommand = yargs => {
await serve(argv.port as number, argv.spec as string, config);
} catch (e) {
handleError(e);
}
},
)
.command(
'bundle <spec>',
'bundle spec into zero-dependency HTML-file',
yargs => {
yargs.positional('spec', { yargs.positional('spec', {
describe: 'path or URL to your spec', describe: 'path or URL to your spec',
}); });
@ -128,8 +90,9 @@ YargsParser.command(
yargs.demandOption('spec'); yargs.demandOption('spec');
return yargs; return yargs;
}, };
async (argv: any) => {
const handlerForBuildCommand = async (argv: any) => {
const config = { const config = {
ssr: true, ssr: true,
output: argv.o as string, output: argv.o as string,
@ -146,7 +109,96 @@ YargsParser.command(
} catch (e) { } catch (e) {
handleError(e); handleError(e);
} }
};
YargsParser.command(
'serve <spec>',
'start the server',
yargs => {
yargs.positional('spec', {
describe: 'path or URL to your spec',
});
yargs.options('title', {
describe: 'Page Title',
type: 'string',
});
yargs.option('s', {
alias: 'ssr',
describe: 'Enable server-side rendering',
type: 'boolean',
});
yargs.option('h', {
alias: 'host',
type: 'string',
default: '127.0.0.1',
});
yargs.option('p', {
alias: 'port',
type: 'number',
default: 8080,
});
yargs.option('w', {
alias: 'watch',
type: 'boolean',
});
yargs.options('disable-google-font', {
describe: 'Disable Google Font',
type: 'boolean',
default: false,
});
yargs.demandOption('spec');
return yargs;
}, },
async argv => {
const config: Options = {
ssr: argv.ssr as boolean,
title: argv.title as string,
watch: argv.watch as boolean,
disableGoogleFont: argv.disableGoogleFont as boolean,
templateFileName: argv.template as string,
templateOptions: argv.templateOptions || {},
redocOptions: getObjectOrJSON(argv.options),
};
try {
await serve(argv.host as string, argv.port as number, argv.spec as string, config);
} catch (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(
'bundle <spec>',
'bundle spec into zero-dependency HTML-file [deprecated]',
builderForBuildCommand,
handlerForBuildCommand,
[
res => {
console.log(`\n⚠ This command is deprecated. Use "build" command instead.\n`);
return res;
},
],
) )
.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,17 +238,27 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}); });
} else { } else {
try {
const filePath = join(dirname(pathToSpec), request.url || '');
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.writeHead(404);
response.write('Not found'); response.write('Not found');
response.end(); response.end();
} }
}
console.timeEnd('GET ' + request.url); console.timeEnd('GET ' + request.url);
}); });
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

@ -17,6 +17,7 @@ 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

View File

@ -1,10 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>%PAGE_TITLE%</title> <title>%PAGE_TITLE%</title>
<link rel="icon" href="%PAGE_FAVICON%"> <link rel="icon" href="%PAGE_FAVICON%" />
<style> <style>
body { body {
margin: 0; margin: 0;
@ -15,12 +15,14 @@
display: block; display: block;
} }
</style> </style>
<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"
/>
</head> </head>
<body> <body>
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc> <redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
<script src="redoc.standalone.js"></script> <script src="redoc.standalone.js"></script>
</body> </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/openapi.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.5.0/openapi.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

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,
},
},
},
{ {
test: /node_modules\/(swagger2openapi|reftools|oas-resolver|oas-kit-common|oas-schema-walker)\/.*\.js$/, loader: 'esbuild-loader',
use: {
loader: 'ts-loader',
options: { options: {
transpileOnly: true, loader: 'css',
instance: 'ts2js-transpiler-only', minify: true,
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,4 +1,4 @@
# ReDoc vendor extensions # Redoc vendor extensions
You can use the following [vendor extensions](https://swagger.io/specification/#specificationExtensions) with Redoc. You can use the following [vendor extensions](https://swagger.io/specification/#specificationExtensions) with Redoc.
@ -194,8 +194,8 @@ Extends the OpenAPI [Operation Object](http://swagger.io/specification/#operatio
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| 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 |
###### How to use with ReDoc ###### How to use with Redoc
`x-codeSamples` are rendered on the right panel in 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
@ -230,8 +230,8 @@ Extends the OpenAPI [Parameter Object](http://swagger.io/specification/#paramete
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| 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` |
###### How to use with ReDoc ###### How to use with Redoc
`x-examples` are rendered in the JSON tab on the right panel in ReDoc. `x-examples` are rendered in the JSON tab on the right panel in Redoc.
### Response Object vendor extensions ### Response Object vendor extensions
Extends the OpenAPI [Response Object](https://swagger.io/specification/#responseObject) Extends the OpenAPI [Response Object](https://swagger.io/specification/#responseObject)
@ -241,7 +241,7 @@ Extends the OpenAPI [Response Object](https://swagger.io/specification/#response
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-summary | string | a short summary of the response | | x-summary | string | a short summary of the response |
###### How to use with ReDoc ###### How to use with Redoc
If specified, you can use `x-summary` as the response button text, with description rendered under the button. If specified, you can use `x-summary` as the response button text, with description rendered under the button.
### Schema Object ### Schema Object
@ -252,21 +252,21 @@ Extends the OpenAPI [Schema Object](http://swagger.io/specification/#schemaObjec
| :------------- | :------: | :---------- | | :------------- | :------: | :---------- |
| x-nullable | boolean | marks schema as a nullable | | x-nullable | boolean | marks schema as a nullable |
###### How to use with 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 a ReDoc-specific vendor extension, and is not 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 |
###### How to use with 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 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
@ -309,7 +309,7 @@ PayPalPayment:
In the example above, the names of definitions (`PayPalPayment`) are named differently than names in the payload (`paypal`) which is not supported by default `discriminator`. In the example above, the names of definitions (`PayPalPayment`) are named differently than names in the payload (`paypal`) which is not supported by default `discriminator`.
#### x-additionalPropertiesName #### x-additionalPropertiesName
**ATTENTION**: This is a ReDoc-specific vendor extension, and is not 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.
@ -317,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 |
###### How to use with 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
@ -337,7 +337,7 @@ Player:
``` ```
#### x-explicitMappingOnly #### x-explicitMappingOnly
**ATTENTION**: This is ReDoc-specific vendor extension, and is not 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.
@ -345,8 +345,8 @@ 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 |
###### How to use with 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`, the default behavior is kept, i.e. explicit and implicit mappings will be shown. When set to `true`, the selectpicker will only list the the explicitly defined mappings. When `false`, the default behavior is kept, i.e. explicit and implicit mappings will be shown.
###### x-explicitMappingOnly example ###### x-explicitMappingOnly example

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>

36721
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.44", "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": "^6.0.1", "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": "^6.0.1", "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 || ^5.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;
@ -128,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,5 +1,3 @@
// import { transparentize } from 'polished';
import styled, { extensionsHook, media } from '../styled-components'; import styled, { extensionsHook, media } from '../styled-components';
import { deprecatedCss } from './mixins'; import { deprecatedCss } from './mixins';
@ -68,7 +66,7 @@ export const PropertyNameCell = styled(PropertyCell)`
line-height: 20px; line-height: 20px;
white-space: nowrap; white-space: nowrap;
font-size: 13px; font-size: 13px;
font-family: ${(props) => props.theme.typography.code.fontFamily}; font-family: ${props => props.theme.typography.code.fontFamily};
&.deprecated { &.deprecated {
${deprecatedCss}; ${deprecatedCss};
@ -82,7 +80,7 @@ export const PropertyNameCell = styled(PropertyCell)`
export const PropertyDetailsCell = styled.td` export const PropertyDetailsCell = styled.td`
border-bottom: 1px solid #9fb4be; border-bottom: 1px solid #9fb4be;
padding: 10px 0; padding: 10px 0;
width: ${(props) => props.theme.schema.defaultDetailsWidth}; width: ${props => props.theme.schema.defaultDetailsWidth};
box-sizing: border-box; box-sizing: border-box;
tr.expanded & { tr.expanded & {
@ -92,7 +90,7 @@ export const PropertyDetailsCell = styled.td`
${media.lessThan('small')` ${media.lessThan('small')`
padding: 0 20px; padding: 0 20px;
border-bottom: none; border-bottom: none;
border-left: 1px solid ${(props) => props.theme.schema.linesColor}; border-left: 1px solid ${props => props.theme.schema.linesColor};
tr.last > & { tr.last > & {
border-left: none; border-left: none;

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);
const clickHandler = React.useCallback(
(event: React.MouseEvent<HTMLAnchorElement>) => {
if (!store) return;
navigate(store.menu.history, event, props.to);
},
[store, props.to],
);
if (!store) return null;
return (
<a
className={props.className}
href={store!.menu.history.linkForId(props.to)}
onClick={clickHandler}
aria-label={props.to}
>
{props.children}
</a>
);
}
function navigate(history: HistoryService, event: React.MouseEvent<HTMLAnchorElement>, to: string) {
if ( if (
!event.defaultPrevented && // onClick prevented default !event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks event.button === 0 && // ignore everything but left clicks
!isModifiedEvent(event) // ignore clicks with modifier keys !isModifiedEvent(event) // ignore clicks with modifier keys
) { ) {
event.preventDefault(); event.preventDefault();
history.replace(this.props.to); history.replace(encodeURI(to));
}
};
render() {
return (
<StoreConsumer>
{store => (
<a
className={this.props.className}
href={store!.menu.history.linkForId(this.props.to)}
onClick={this.navigate.bind(this, store!.menu.history)}
>
{this.props.children}
</a>
)}
</StoreConsumer>
);
} }
} }

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();
} }
@ -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); const stringifyValue = raw ? String(value) : JSON.stringify(value);
return ( return (
<div> <div>
<FieldLabel> {this.props.label} </FieldLabel> <ExampleValue>{value}</ExampleValue> <FieldLabel> {label} </FieldLabel> <ExampleValue>{stringifyValue}</ExampleValue>
</div> </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,44 +21,36 @@ 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 null;
}, [field, showExamples]);
return ( return (
<div> <div>
<div> <div>
@ -74,24 +64,27 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
&gt;{' '} &gt;{' '}
</TypeFormat> </TypeFormat>
)} )}
{schema.contentEncoding && (
<TypeFormat>
{' '}
&lt;
{schema.contentEncoding}
&gt;{' '}
</TypeFormat>
)}
{schema.contentMediaType && (
<TypeFormat>
{' '}
&lt;
{schema.contentMediaType}
&gt;{' '}
</TypeFormat>
)}
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>} {schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
<ConstraintsView constraints={schema.constraints} /> <ConstraintsView constraints={schema.constraints} />
{schema.nullable && <NullableLabel> {l('nullable')} </NullableLabel>} <Pattern schema={schema} />
{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>} {schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
{isArrayType && schema.items && <ArrayItemDetails schema={schema.items} />}
</div> </div>
{deprecated && ( {deprecated && (
<div> <div>
@ -99,17 +92,21 @@ export class FieldDetails extends React.PureComponent<FieldProps, { patternShown
</div> </div>
)} )}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} /> <FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '} {!renderDiscriminatorSwitch && (
{exampleField} <EnumValues isArrayType={isArrayType} values={schema.enum} />
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />} )}{' '}
{renderedExamples}
<Extensions extensions={{ ...extensions, ...schema.extensions }} />
<div> <div>
<Markdown compact={true} source={description} /> <Markdown compact={true} source={description} />
</div> </div>
{schema.externalDocs && ( {schema.externalDocs && (
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} /> <ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
)} )}
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null} {(renderDiscriminatorSwitch && renderDiscriminatorSwitch(props)) || null}
{(_const && <FieldDetail label={l('const') + ':'} value={_const} />) || null}
</div> </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

@ -43,11 +43,11 @@ class Json extends React.PureComponent<JsonProps> {
} }
</SampleControls> </SampleControls>
<OptionsContext.Consumer> <OptionsContext.Consumer>
{(options) => ( {options => (
<PrismDiv <PrismDiv
className={this.props.className} className={this.props.className}
// tslint:disable-next-line // tslint:disable-next-line
ref={(node) => (this.node = node!)} ref={node => (this.node = node!)}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel), __html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel),
}} }}
@ -61,7 +61,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');
} }
}; };
@ -71,7 +73,9 @@ 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');
} }
}; };
@ -81,8 +85,10 @@ class Json extends React.PureComponent<JsonProps> {
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');
} }
} }
}; };

View File

@ -6,8 +6,8 @@ export const jsonStyles = css`
pointer-events: none; pointer-events: none;
} }
font-family: ${(props) => props.theme.typography.code.fontFamily}; font-family: ${props => props.theme.typography.code.fontFamily};
font-size: ${(props) => props.theme.typography.code.fontSize}; font-size: ${props => props.theme.typography.code.fontSize};
white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')}; white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
contain: content; contain: content;
@ -51,8 +51,8 @@ export const jsonStyles = css`
background-color: transparent; background-color: transparent;
border: 0; border: 0;
color: #fff; color: #fff;
font-family: ${(props) => props.theme.typography.code.fontFamily}; font-family: ${props => props.theme.typography.code.fontFamily};
font-size: ${(props) => props.theme.typography.code.fontSize}; font-size: ${props => props.theme.typography.code.fontSize};
padding-right: 6px; padding-right: 6px;
padding-left: 6px; padding-left: 6px;
padding-top: 0; padding-top: 0;

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};
@ -87,7 +86,7 @@ export const StyledMarkdownBlock = styled(
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,35 +17,21 @@ 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 { spec, specUrl, options = {}, onLoaded } = this.props;
const hideLoading = options.hideLoading !== undefined;
const normalizedOpts = new RedocNormalizedOptions(options); const normalizedOpts = new RedocNormalizedOptions(options);
if (normalizedOpts.nonce !== undefined) {
try {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
__webpack_nonce__ = normalizedOpts.nonce;
} catch {} // If we have exception, Webpack was not used to run this.
}
return ( return (
<ErrorBoundary> <ErrorBoundary>
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}> <StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}>
@ -56,5 +45,4 @@ export class RedocStandalone extends React.PureComponent<RedocStandaloneProps> {
</StoreBuilder> </StoreBuilder>
</ErrorBoundary> </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,27 +1,36 @@
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() { export const ResponseView = observer(({ response }: ResponseViewProps): React.ReactElement => {
const { headers, type, summary, description, code, expanded, content } = this.props.response; const { extensions, headers, type, summary, description, code, expanded, content } = response;
const mimes =
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined);
const empty = headers.length === 0 && mimes.length === 0 && !description; 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 ( return (
<div> <div>
<StyledResponseTitle <StyledResponseTitle
onClick={this.toggle} onClick={() => response.toggle()}
type={type} type={type}
empty={empty} empty={empty}
title={summary || ''} title={summary || ''}
@ -30,10 +39,9 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
/> />
{expanded && !empty && ( {expanded && !empty && (
<ResponseDetailsWrap> <ResponseDetailsWrap>
<ResponseDetails response={this.props.response} /> <ResponseDetails response={response} />
</ResponseDetailsWrap> </ResponseDetailsWrap>
)} )}
</div> </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,9 +14,15 @@ 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,
empty,
code,
opened,
className,
onClick,
}: ResponseTitleProps): React.ReactElement {
return ( return (
<button <button
className={className} className={className}
@ -37,4 +43,5 @@ export class ResponseTitle extends React.PureComponent<ResponseTitleProps> {
</button> </button>
); );
} }
}
export const ResponseTitle = React.memo<ResponseTitleProps>(ResponseTitleComponent);

View File

@ -1,4 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { l } from '../../services/Labels';
import { ResponseModel } from '../../services/models'; import { ResponseModel } from '../../services/models';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { ResponseView } from './Response'; import { ResponseView } from './Response';
@ -26,7 +27,7 @@ export class ResponsesList extends React.PureComponent<ResponseListProps> {
return ( return (
<div> <div>
<ResponsesHeader>{isCallback ? 'Callback responses' : 'Responses'}</ResponsesHeader> <ResponsesHeader>{isCallback ? l('callbackResponses') : l('responses')}</ResponsesHeader>
{responses.map(response => { {responses.map(response => {
return <ResponseView key={response.code} response={response} />; return <ResponseView key={response.code} response={response} />;
})} })}

View File

@ -1,5 +1,3 @@
// import { transparentize } from 'polished';
import { UnderlinedHeader } from '../../common-elements'; import { UnderlinedHeader } from '../../common-elements';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { ResponseTitle } from './ResponseTitle'; import { ResponseTitle } from './ResponseTitle';
@ -13,16 +11,14 @@ export const StyledResponseTitle = styled(ResponseTitle)`
border-radius: 2px; border-radius: 2px;
margin-bottom: 4px; margin-bottom: 4px;
line-height: 1.5em; line-height: 1.5em;
background-color: #f2f2f2;
cursor: pointer; cursor: pointer;
color: ${(props) => props.theme.colors.responses[props.type].color}; color: ${props => props.theme.colors.responses[props.type].color};
background-color: ${(props) => props.theme.colors.responses[props.type].backgroundColor}; background-color: ${props => props.theme.colors.responses[props.type].backgroundColor};
&:focus { &:focus {
outline: auto; outline: auto ${props => props.theme.colors.responses[props.type].color};
outline-color: ${(props) => props.theme.colors.responses[props.type].color};
} }
${(props) => ${props =>
(props.empty && (props.empty &&
` `
cursor: default; cursor: default;

View File

@ -4,7 +4,8 @@ import { Schema, SchemaProps } from './Schema';
import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements'; import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements';
import styled from '../../styled-components'; import styled from '../../styled-components';
import {humanizeConstraints} from "../../utils"; import { humanizeConstraints } from '../../utils';
import { TypeName } from '../../common-elements/fields';
const PaddedSchema = styled.div` const PaddedSchema = styled.div`
padding-left: ${({ theme }) => theme.spacing.unit * 2}px; padding-left: ${({ theme }) => theme.spacing.unit * 2}px;
@ -12,17 +13,25 @@ const PaddedSchema = styled.div`
export class ArraySchema extends React.PureComponent<SchemaProps> { export class ArraySchema extends React.PureComponent<SchemaProps> {
render() { render() {
const itemsSchema = this.props.schema.items!; const schema = this.props.schema;
const itemConstraintSchema = ( const itemsSchema = schema.items;
min: number | undefined = undefined,
max: number | undefined = undefined,
) => ({ type: 'array', minItems: min, maxItems: max });
const minMaxItems = humanizeConstraints(itemConstraintSchema(itemsSchema.schema.minItems, itemsSchema.schema.maxItems)); const minMaxItems =
schema.minItems === undefined && schema.maxItems === undefined
? ''
: `(${humanizeConstraints(schema)})`;
if (schema.displayType && !itemsSchema && !minMaxItems.length) {
return (
<div>
<TypeName>{schema.displayType}</TypeName>
</div>
);
}
return ( return (
<div> <div>
<ArrayOpenningLabel> Array ({minMaxItems})</ArrayOpenningLabel> <ArrayOpenningLabel> Array {minMaxItems}</ArrayOpenningLabel>
<PaddedSchema> <PaddedSchema>
<Schema {...this.props} schema={itemsSchema} /> <Schema {...this.props} schema={itemsSchema} />
</PaddedSchema> </PaddedSchema>

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