mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-06 05:10:20 +03:00
Merge branch 'master' into config-download-file
This commit is contained in:
commit
5f29219854
|
@ -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
2
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
* @Redocly/keyboard-warriors
|
||||||
|
/docs/ @Redocly/technical-writers
|
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
22
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
|
@ -11,8 +11,3 @@
|
||||||
- [ ] Code is linted
|
- [ ] Code is linted
|
||||||
- [ ] Tested
|
- [ ] Tested
|
||||||
- [ ] All new/updated code is covered with tests
|
- [ ] All new/updated code is covered with tests
|
||||||
|
|
||||||
## Security
|
|
||||||
|
|
||||||
- [ ] Security impact of change has been considered
|
|
||||||
- [ ] Code follows company security practices and guidelines
|
|
||||||
|
|
42
.github/workflows/demo-deploy-s3.yml
vendored
42
.github/workflows/demo-deploy-s3.yml
vendored
|
@ -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
12
.github/workflows/e2e-tests.yml
vendored
Normal 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
45
.github/workflows/main.yml
vendored
Normal 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
120
.github/workflows/publish-cli.yml
vendored
Normal 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
100
.github/workflows/publish.yml
vendored
Normal 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 }}
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -37,3 +37,4 @@ stats.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
.eslintcache
|
||||||
|
|
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run pre-commit
|
1
.prettierignore
Normal file
1
.prettierignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.md
|
32
.travis.yml
32
.travis.yml
|
@ -1,32 +0,0 @@
|
||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- '12'
|
|
||||||
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
|
|
152
CHANGELOG.md
152
CHANGELOG.md
|
@ -1,3 +1,155 @@
|
||||||
|
# [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)
|
# [2.0.0-rc.55](https://github.com/Redocly/redoc/compare/v2.0.0-rc.54...v2.0.0-rc.55) (2021-07-01)
|
||||||
|
|
||||||
|
|
||||||
|
|
307
README.md
307
README.md
|
@ -1,62 +1,118 @@
|
||||||
<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
|
||||||
|
|
||||||
[](https://travis-ci.com/Redocly/redoc) [](https://coveralls.io/github/Redocly/redoc?branch=master) [](https://david-dm.org/Redocly/redoc) [](https://david-dm.org/Redocly/redoc#info=devDependencies) [](https://www.npmjs.com/package/redoc) [](https://github.com/Redocly/redoc/blob/master/LICENSE)
|
[](https://travis-ci.com/Redocly/redoc) [](https://coveralls.io/github/Redocly/redoc?branch=master) [](https://www.npmjs.com/package/redoc) [](https://github.com/Redocly/redoc/blob/master/LICENSE)
|
||||||
|
|
||||||
[](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc) [](https://hub.docker.com/r/redocly/redoc/)
|
[](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc) [](https://hub.docker.com/r/redocly/redoc/)
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
**This is README for `2.0` version of ReDoc (React based). README for `1.x` version is on the branch [v1.x](https://github.com/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 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Live demo
|
||||||
|
|
||||||
|
If you want to see how Redoc will render your OpenAPI definition,
|
||||||
|
you can try it out online at https://redocly.github.io/redoc/.
|
||||||
|
|
||||||
|
A version of the Swagger Petstore API is displayed by default.
|
||||||
|
To test it with your own OpenAPI definition,
|
||||||
|
enter the URL for your definition and select **TRY IT**.
|
||||||
|
|
||||||
|
## Redoc vs. Reference vs. Portals
|
||||||
|
|
||||||
|
Redoc is Redocly's community-edition product. Looking for something more?
|
||||||
|
Checkout the following feature comparison of Redocly's premium products versus Redoc:
|
||||||
|
|
||||||
|
| Features | Redoc | Reference | Portals |
|
||||||
|
|------------------------------|:---------:|:---------:|:-----------:|
|
||||||
|
| **Specs** | | | |
|
||||||
|
| Swagger 2.0 | √ | √ | √ |
|
||||||
|
| OpenAPI 3.0 | √ | √ | √ |
|
||||||
|
| OpenAPI 3.1 | √ (basic) | √ | √ |
|
||||||
|
| | | | |
|
||||||
|
| **Theming** | | | |
|
||||||
|
| Fonts/colors | √ | √ | √ |
|
||||||
|
| Extra theme options | | √ | √ |
|
||||||
|
| | | | |
|
||||||
|
| **Performance** | | | |
|
||||||
|
| Pagination | | √ | √ |
|
||||||
|
| Search (enhanced) | | √ | √ |
|
||||||
|
| Search (server-side) | | | √ |
|
||||||
|
| | | | |
|
||||||
|
| **Multiple APIs** | | | |
|
||||||
|
| Multiple versions | | √ | √ |
|
||||||
|
| Multiple APIs | | | √ |
|
||||||
|
| API catalog | | | √ |
|
||||||
|
| | | | |
|
||||||
|
| **Additional features** | | | |
|
||||||
|
| Try-it console | | √ | √ |
|
||||||
|
| Automated code samples | | √ | √ |
|
||||||
|
| Deep links | | √ | √ |
|
||||||
|
| More SEO control | | | √ |
|
||||||
|
| Contextual docs | | | √ |
|
||||||
|
| Landing pages | | | √ |
|
||||||
|
| React hooks for more control | | | √ |
|
||||||
|
| Personalization | | | √ |
|
||||||
|
| Analytics integrations | | | √ |
|
||||||
|
| Feedback | | | Coming Soon |
|
||||||
|
|
||||||
|
Refer to the Redocly's documentation for more information on these products:
|
||||||
|
|
||||||
|
- [Portals](https://redoc.ly/docs/developer-portal/introduction/)
|
||||||
|
- [Reference](https://redoc.ly/docs/api-reference-docs/getting-started/)
|
||||||
|
- [Redoc](https://redoc.ly/docs/redoc/quickstart/intro/)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Extremely easy deployment
|
|
||||||
- [redoc-cli](https://github.com/Redocly/redoc/blob/master/cli/README.md) with ability to bundle your docs into **zero-dependency** HTML file
|
|
||||||
- Server Side Rendering ready
|
|
||||||
- The widest OpenAPI v2.0 features support (yes, it supports even `discriminator`) <br>
|
|
||||||

|
|
||||||
- OpenAPI 3.0 support
|
|
||||||
- Basic OpenAPI 3.1 support
|
|
||||||
- Neat **interactive** documentation for nested objects <br>
|
|
||||||

|
|
||||||
- Code samples support (via vendor extension) <br>
|
|
||||||

|
|
||||||
- Responsive three-panel design with menu/scrolling synchronization
|
- Responsive three-panel design with menu/scrolling synchronization
|
||||||
- Integrate API Introduction into side menu - ReDoc takes advantage of markdown headings from OpenAPI description field. It pulls them into side menu and also supports deep linking.
|
- [Multiple deployment options](https://redoc.ly/docs/redoc/quickstart/intro/)
|
||||||
- High-level grouping in side-menu via [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) vendor extension
|
- [Server-side rendering (SSR) ready](https://redoc.ly/docs/redoc/quickstart/cli/#redoc-cli-commands)
|
||||||
- Simple integration with `create-react-app` ([sample](https://github.com/APIs-guru/create-react-app-redoc))
|
- Ability to integrate your API introduction into the side menu
|
||||||
- Branding/customizations via [`theme` option](#redoc-options-object)
|
- [Simple integration with `create-react-app`](https://redoc.ly/docs/redoc/quickstart/react/)
|
||||||
|
|
||||||
## Roadmap
|
[Example repo](https://github.com/APIs-guru/create-react-app-redoc)
|
||||||
- [x] ~~[OpenAPI v3.0 support](https://github.com/Redocly/redoc/issues/312)~~
|
- [Command-line interface to bundle your docs into a **zero-dependency** HTML file](https://redoc.ly/docs/redoc/quickstart/cli/)
|
||||||
- [x] ~~performance optimizations~~
|
- Neat **interactive** documentation for nested objects <br>
|
||||||
- [x] ~~better navigation (menu improvements + search)~~
|

|
||||||
- [x] ~~React rewrite~~
|
|
||||||
- [x] ~~docs pre-rendering (performance and SEO)~~
|
## Customization options
|
||||||
- [ ] ability to simple branding/styling
|
[<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
|
||||||
|
- 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>
|
||||||
|

|
||||||
|
- Code samples support (via vendor extension) <br>
|
||||||
|

|
||||||
|
|
||||||
## 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.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 |
|
||||||
|
@ -64,31 +120,42 @@ Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(d
|
||||||
| 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://api-reference.rebilly.com/)
|
- [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 {
|
||||||
|
@ -99,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="https://api.redoc.ly/registry/rebilly/core-api/core/bundle/master/openapi.yaml"
|
|
||||||
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="https://api.redoc.ly/registry/rebilly/core-api/core/bundle/master/openapi.yaml"
|
|
||||||
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
|
||||||
|
@ -225,22 +204,29 @@ ReDoc makes use of the following [vendor extensions](https://swagger.io/specific
|
||||||
* [`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
|
* [`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.
|
* `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.
|
||||||
* `downloadFileName` - set file name of downloaded spec file, e.g. `swagger.json`, default `openapi.json`. Spec file in `JSON` format. (only `redoc-cli`)
|
* `downloadFileName` - set file name of downloaded spec file, e.g. `swagger.json`, default `openapi.json`. Spec file in `JSON` format. (only `redoc-cli`)
|
||||||
* `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.
|
* `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
|
||||||
|
* `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`.
|
* `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 `true`.
|
* `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).
|
||||||
|
@ -258,6 +244,11 @@ You can use all of the following options with standalone version on <redoc> tag
|
||||||
* `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. For details check [theme docs](#redoc-theme-object).
|
* `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.
|
||||||
|
|
||||||
### `<redoc>` theme object
|
### `<redoc>` theme object
|
||||||
* `spacing`
|
* `spacing`
|
||||||
|
@ -295,43 +286,33 @@ You can use all of the following options with standalone version on <redoc> tag
|
||||||
* `color`: # COMPUTED: colors.primary.main
|
* `color`: # COMPUTED: colors.primary.main
|
||||||
* `visited`: # COMPUTED: typography.links.color
|
* `visited`: # COMPUTED: typography.links.color
|
||||||
* `hover`: # COMPUTED: lighten(0.2 typography.links.color)
|
* `hover`: # COMPUTED: lighten(0.2 typography.links.color)
|
||||||
* `menu`
|
* `sidebar`
|
||||||
* `width`: '260px'
|
* `width`: '260px'
|
||||||
* `backgroundColor`: '#fafafa'
|
* `backgroundColor`: '#fafafa'
|
||||||
* `textColor`: '#333333'
|
* `textColor`: '#333333'
|
||||||
* `activeTextColor`: # COMPUTED: theme.menu.textColor (if set by user) or theme.colors.primary.main
|
* `activeTextColor`: # COMPUTED: theme.sidebar.textColor (if set by user) or theme.colors.primary.main
|
||||||
* `groupItems` # Group headings
|
* `groupItems` # Group headings
|
||||||
|
* `activeBackgroundColor`: # COMPUTED: theme.sidebar.backgroundColor
|
||||||
|
* `activeTextColor`: # COMPUTED: theme.sidebar.activeTextColor
|
||||||
* `textTransform`: 'uppercase'
|
* `textTransform`: 'uppercase'
|
||||||
* `level1Items` # Level 1 items like tags or section 1st level items
|
* `level1Items` # Level 1 items like tags or section 1st level items
|
||||||
|
* `activeBackgroundColor`: # COMPUTED: theme.sidebar.backgroundColor
|
||||||
|
* `activeTextColor`: # COMPUTED: theme.sidebar.activeTextColor
|
||||||
* `textTransform`: 'none'
|
* `textTransform`: 'none'
|
||||||
* `arrow` # menu arrow
|
* `arrow` # sidebar arrow
|
||||||
* `size`: '1.5em'
|
* `size`: '1.5em'
|
||||||
* `color`: # COMPUTED: theme.menu.textColor
|
* `color`: # COMPUTED: theme.sidebar.textColor
|
||||||
* `logo`
|
* `logo`
|
||||||
* `maxHeight`: # COMPUTED: menu.width
|
* `maxHeight`: # COMPUTED: sidebar.width
|
||||||
* `maxWidth`: # COMPUTED: menu.width
|
* `maxWidth`: # COMPUTED: sidebar.width
|
||||||
* `gutter`: '2px' # logo image padding
|
* `gutter`: '2px' # logo image padding
|
||||||
* `rightPanel`
|
* `rightPanel`
|
||||||
* `backgroundColor`: '#263238'
|
* `backgroundColor`: '#263238'
|
||||||
* `width`: '40%'
|
* `width`: '40%'
|
||||||
* `textColor`: '#ffffff'
|
* `textColor`: '#ffffff'
|
||||||
|
* `fab`
|
||||||
## Advanced usage of standalone version
|
* `backgroundColor`: '#263238'
|
||||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
* `color`: '#ffffff'
|
||||||
```js
|
|
||||||
Redoc.init(specOrSpecUrl, options, element, callback?)
|
|
||||||
```
|
|
||||||
|
|
||||||
- `specOrSpecUrl` is either JSON object with specification or an URL to the spec in `JSON` or `YAML` format
|
|
||||||
- `options` [options object](#redoc-options-object)
|
|
||||||
- `element` DOM element to put ReDoc 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
|
|
||||||
|
|
||||||
```js
|
|
||||||
Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
|
|
||||||
scrollYOffset: 50
|
|
||||||
}, document.getElementById('redoc-container'))
|
|
||||||
```
|
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
## Development
|
## Development
|
||||||
|
|
|
@ -12,20 +12,23 @@ or using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runn
|
||||||
The two following commands are available:
|
The two following commands are available:
|
||||||
|
|
||||||
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc.
|
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc.
|
||||||
Supports a server-side rendering mode (`--ssr`),
|
Supports a server-side rendering mode (`--ssr`)
|
||||||
and can watch the spec (`--watch`) to automatically reload the page whenever it changes.
|
and can watch the spec (`--watch`) to automatically reload the page whenever it changes.\
|
||||||
- `redoc-cli bundle [spec]` - bundles `spec` and ReDoc into a **zero-dependency** HTML file.
|
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 the main color changed to `orange`:<br/>
|
- Bundle with the main color changed to `orange`:<br/>
|
||||||
`$ redoc-cli bundle [spec] --options.theme.colors.primary.main=orange`
|
`$ redoc-cli build [spec] --options.theme.colors.primary.main=orange`
|
||||||
- Serve with the `nativeScrollbars` option set to true:<br/>
|
- Serve with the `nativeScrollbars` option set to true:<br/>
|
||||||
`$ redoc-cli serve [spec] --options.nativeScrollbars`
|
`$ redoc-cli serve [spec] --options.nativeScrollbars`
|
||||||
- Bundle using a custom [Handlebars](https://handlebarsjs.com/) template
|
- 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/>
|
(check the [default template](https://github.com/Redocly/redoc/blob/master/cli/template.hbs) for an example):<br/>
|
||||||
`$ redoc-cli bundle [spec] -t custom.hbs`
|
`$ redoc-cli build [spec] -t custom.hbs`
|
||||||
- Bundle using a custom template and add custom `templateOptions`:<br/>
|
- Bundle using a custom template and add custom `templateOptions`:<br/>
|
||||||
`$ redoc-cli bundle [spec] -t custom.hbs --templateOptions.metaDescription "Page meta description"`
|
`$ 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`.
|
||||||
|
|
175
cli/index.ts
175
cli/index.ts
|
@ -6,7 +6,7 @@ import { ServerStyleSheet } from 'styled-components';
|
||||||
|
|
||||||
import { compile } from 'handlebars';
|
import { compile } from 'handlebars';
|
||||||
import { createServer, IncomingMessage, ServerResponse } from 'http';
|
import { createServer, IncomingMessage, ServerResponse } from 'http';
|
||||||
import { dirname, join, resolve } from 'path';
|
import { dirname, join, resolve, extname as getExtName } from 'path';
|
||||||
|
|
||||||
import * as zlib from 'zlib';
|
import * as zlib from 'zlib';
|
||||||
|
|
||||||
|
@ -39,9 +39,78 @@ interface Options {
|
||||||
redocOptions?: any;
|
redocOptions?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mimeTypes = {
|
||||||
|
'.html': 'text/html',
|
||||||
|
'.js': 'text/javascript',
|
||||||
|
'.css': 'text/css',
|
||||||
|
'.json': 'application/json',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.jpg': 'image/jpg',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.wav': 'audio/wav',
|
||||||
|
'.mp4': 'video/mp4',
|
||||||
|
'.woff': 'application/font-woff',
|
||||||
|
'.ttf': 'application/font-ttf',
|
||||||
|
'.eot': 'application/vnd.ms-fontobject',
|
||||||
|
'.otf': 'application/font-otf',
|
||||||
|
'.wasm': 'application/wasm',
|
||||||
|
};
|
||||||
|
|
||||||
const BUNDLES_DIR = dirname(require.resolve('redoc'));
|
const BUNDLES_DIR = dirname(require.resolve('redoc'));
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
const builderForBuildCommand = yargs => {
|
||||||
|
yargs.positional('spec', {
|
||||||
|
describe: 'path or URL to your spec',
|
||||||
|
});
|
||||||
|
|
||||||
|
yargs.option('o', {
|
||||||
|
describe: 'Output file',
|
||||||
|
alias: 'output',
|
||||||
|
type: 'string',
|
||||||
|
default: 'redoc-static.html',
|
||||||
|
});
|
||||||
|
|
||||||
|
yargs.options('title', {
|
||||||
|
describe: 'Page Title',
|
||||||
|
type: 'string',
|
||||||
|
});
|
||||||
|
|
||||||
|
yargs.options('disableGoogleFont', {
|
||||||
|
describe: 'Disable Google Font',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
yargs.option('cdn', {
|
||||||
|
describe: 'Do not include ReDoc source code into html page, use link to CDN instead',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
yargs.demandOption('spec');
|
||||||
|
return yargs;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlerForBuildCommand = async (argv: any) => {
|
||||||
|
const config = {
|
||||||
|
ssr: true,
|
||||||
|
output: argv.o as string,
|
||||||
|
cdn: argv.cdn as boolean,
|
||||||
|
title: argv.title as string,
|
||||||
|
disableGoogleFont: argv.disableGoogleFont as boolean,
|
||||||
|
templateFileName: argv.template as string,
|
||||||
|
templateOptions: argv.templateOptions || {},
|
||||||
|
redocOptions: getObjectOrJSON(argv.options),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await bundle(argv.spec, config);
|
||||||
|
} catch (e) {
|
||||||
|
handleError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
YargsParser.command(
|
YargsParser.command(
|
||||||
'serve <spec>',
|
'serve <spec>',
|
||||||
'start the server',
|
'start the server',
|
||||||
|
@ -61,6 +130,12 @@ YargsParser.command(
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
yargs.option('h', {
|
||||||
|
alias: 'host',
|
||||||
|
type: 'string',
|
||||||
|
default: '127.0.0.1',
|
||||||
|
});
|
||||||
|
|
||||||
yargs.option('p', {
|
yargs.option('p', {
|
||||||
alias: 'port',
|
alias: 'port',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
@ -93,65 +168,37 @@ YargsParser.command(
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await serve(argv.port as number, argv.spec as string, config);
|
await serve(argv.host as string, argv.port as number, argv.spec as string, config);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e);
|
handleError(e);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
[
|
||||||
|
res => {
|
||||||
|
console.log(
|
||||||
|
`\n⚠️ This command is deprecated. Use "npx @redocly/openapi-cli preview-docs petstore.yaml"\n`,
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
.command(
|
||||||
|
'build <spec>',
|
||||||
|
'build definition into zero-dependency HTML-file',
|
||||||
|
builderForBuildCommand,
|
||||||
|
handlerForBuildCommand,
|
||||||
|
)
|
||||||
.command(
|
.command(
|
||||||
'bundle <spec>',
|
'bundle <spec>',
|
||||||
'bundle spec into zero-dependency HTML-file',
|
'bundle spec into zero-dependency HTML-file [deprecated]',
|
||||||
yargs => {
|
builderForBuildCommand,
|
||||||
yargs.positional('spec', {
|
handlerForBuildCommand,
|
||||||
describe: 'path or URL to your spec',
|
[
|
||||||
});
|
res => {
|
||||||
|
console.log(`\n⚠️ This command is deprecated. Use "build" command instead.\n`);
|
||||||
yargs.option('o', {
|
return res;
|
||||||
describe: 'Output file',
|
},
|
||||||
alias: 'output',
|
],
|
||||||
type: 'string',
|
|
||||||
default: 'redoc-static.html',
|
|
||||||
});
|
|
||||||
|
|
||||||
yargs.options('title', {
|
|
||||||
describe: 'Page Title',
|
|
||||||
type: 'string',
|
|
||||||
});
|
|
||||||
|
|
||||||
yargs.options('disableGoogleFont', {
|
|
||||||
describe: 'Disable Google Font',
|
|
||||||
type: 'boolean',
|
|
||||||
default: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
yargs.option('cdn', {
|
|
||||||
describe: 'Do not include ReDoc source code into html page, use link to CDN instead',
|
|
||||||
type: 'boolean',
|
|
||||||
default: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
yargs.demandOption('spec');
|
|
||||||
return yargs;
|
|
||||||
},
|
|
||||||
async (argv: any) => {
|
|
||||||
const config = {
|
|
||||||
ssr: true,
|
|
||||||
output: argv.o as string,
|
|
||||||
cdn: argv.cdn as boolean,
|
|
||||||
title: argv.title as string,
|
|
||||||
disableGoogleFont: argv.disableGoogleFont as boolean,
|
|
||||||
templateFileName: argv.template as string,
|
|
||||||
templateOptions: argv.templateOptions || {},
|
|
||||||
redocOptions: getObjectOrJSON(argv.options),
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
await bundle(argv.spec, config);
|
|
||||||
} catch (e) {
|
|
||||||
handleError(e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.demandCommand()
|
.demandCommand()
|
||||||
.options('t', {
|
.options('t', {
|
||||||
|
@ -167,7 +214,7 @@ 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(isURL(pathToSpec) ? pathToSpec : resolve(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) => {
|
||||||
|
@ -191,9 +238,19 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
response.writeHead(404);
|
try {
|
||||||
response.write('Not found');
|
const filePath = join(dirname(pathToSpec), request.url || '');
|
||||||
response.end();
|
const extname = String(getExtName(filePath)).toLowerCase() as keyof typeof mimeTypes;
|
||||||
|
|
||||||
|
const contentType = mimeTypes[extname] || 'application/octet-stream';
|
||||||
|
respondWithGzip(createReadStream(filePath), request, response, {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
response.writeHead(404);
|
||||||
|
response.write('Not found');
|
||||||
|
response.end();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.timeEnd('GET ' + request.url);
|
console.timeEnd('GET ' + request.url);
|
||||||
|
@ -201,7 +258,7 @@ async function serve(port: number, pathToSpec: string, options: Options = {}) {
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
server.listen(port, () => console.log(`Server started: http://127.0.0.1:${port}`));
|
server.listen(port, host, () => console.log(`Server started: http://${host}:${port}`));
|
||||||
|
|
||||||
if (options.watch && existsSync(pathToSpec)) {
|
if (options.watch && existsSync(pathToSpec)) {
|
||||||
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
const pathToSpecDirectory = resolve(dirname(pathToSpec));
|
||||||
|
|
1876
cli/npm-shrinkwrap.json
generated
1876
cli/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc-cli",
|
"name": "redoc-cli",
|
||||||
"version": "0.12.2",
|
"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",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"node-libs-browser": "^2.2.1",
|
"node-libs-browser": "^2.2.1",
|
||||||
"react": "^17.0.1",
|
"react": "^17.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"redoc": "2.0.0-rc.55",
|
"redoc": "2.0.0-rc.66",
|
||||||
"styled-components": "^5.3.0",
|
"styled-components": "^5.3.0",
|
||||||
"yargs": "^17.0.1"
|
"yargs": "^17.3.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|
|
@ -1,26 +1,28 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>%PAGE_TITLE%</title>
|
||||||
|
<link rel="icon" href="%PAGE_FAVICON%" />
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
<head>
|
redoc {
|
||||||
<meta charset="UTF-8" />
|
display: block;
|
||||||
<title>%PAGE_TITLE%</title>
|
}
|
||||||
<link rel="icon" href="%PAGE_FAVICON%">
|
</style>
|
||||||
<style>
|
<link
|
||||||
body {
|
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
|
||||||
margin: 0;
|
rel="stylesheet"
|
||||||
padding: 0;
|
/>
|
||||||
}
|
</head>
|
||||||
|
|
||||||
redoc {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
|
|
||||||
<script src="redoc.standalone.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
|
||||||
|
<script src="redoc.standalone.js"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,46 +1,5 @@
|
||||||
import * as webpack from 'webpack';
|
import * as webpack from 'webpack';
|
||||||
|
|
||||||
export function getBabelLoader({useBuiltIns, hot}: {useBuiltIns: boolean, hot?: boolean}) {
|
|
||||||
return {
|
|
||||||
loader: 'babel-loader',
|
|
||||||
options: {
|
|
||||||
babelrc: false,
|
|
||||||
sourceType: 'unambiguous',
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
'@babel/preset-env',
|
|
||||||
{
|
|
||||||
useBuiltIns: useBuiltIns ? 'usage' : false,
|
|
||||||
corejs: 3,
|
|
||||||
exclude: ['transform-typeof-symbol'],
|
|
||||||
targets: 'defaults',
|
|
||||||
modules: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
['@babel/preset-react', { development: false, runtime: 'classic' }],
|
|
||||||
'@babel/preset-typescript',
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
['@babel/plugin-proposal-decorators', { legacy: true }],
|
|
||||||
['@babel/plugin-proposal-class-properties', { loose: false }],
|
|
||||||
[
|
|
||||||
'@babel/plugin-transform-runtime',
|
|
||||||
{
|
|
||||||
corejs: false,
|
|
||||||
helpers: true,
|
|
||||||
// eslint-disable-next-line import/no-internal-modules
|
|
||||||
version: require('@babel/runtime/package.json').version,
|
|
||||||
regenerator: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'@babel/plugin-proposal-optional-chaining',
|
|
||||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
|
||||||
hot ? 'react-hot-loader/babel' : undefined,
|
|
||||||
].filter(Boolean)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function webpackIgnore(regexp) {
|
export function webpackIgnore(regexp) {
|
||||||
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash/noop.js'));
|
return new webpack.NormalModuleReplacementPlugin(regexp, require.resolve('lodash.noop'));
|
||||||
}
|
}
|
||||||
|
|
2
custom.d.ts
vendored
2
custom.d.ts
vendored
|
@ -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>;
|
||||||
|
|
52
demo/components/FileInput.tsx
Normal file
52
demo/components/FileInput.tsx
Normal 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;
|
|
@ -4,6 +4,7 @@ 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 DEFAULT_SPEC = 'openapi.yaml';
|
||||||
const NEW_VERSION_SPEC = 'openapi-3-1.yaml';
|
const NEW_VERSION_SPEC = 'openapi-3-1.yaml';
|
||||||
|
@ -22,7 +23,7 @@ const demos = [
|
||||||
|
|
||||||
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);
|
||||||
|
@ -40,15 +41,24 @@ 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) {
|
if (url === NEW_VERSION_SPEC) {
|
||||||
this.setState({ cors: false })
|
this.setState({ cors: false });
|
||||||
|
0;
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
specUrl: url,
|
specUrl: url,
|
||||||
|
@ -90,6 +100,7 @@ class DemoApp extends React.Component<
|
||||||
/>
|
/>
|
||||||
</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}
|
||||||
|
@ -110,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 }}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -93,7 +93,7 @@ paths:
|
||||||
parameters:
|
parameters:
|
||||||
- name: Accept-Language
|
- name: Accept-Language
|
||||||
in: header
|
in: header
|
||||||
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
|
description: 'The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US'
|
||||||
example: en-US
|
example: en-US
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
|
@ -182,6 +182,16 @@ paths:
|
||||||
}
|
}
|
||||||
requestBody:
|
requestBody:
|
||||||
$ref: '#/components/requestBodies/Pet'
|
$ref: '#/components/requestBodies/Pet'
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- pet
|
||||||
|
summary: OperationId with quotes
|
||||||
|
operationId: deletePetBy"Id
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- pet
|
||||||
|
summary: OperationId with backslash
|
||||||
|
operationId: delete\PetById
|
||||||
'/pet/{petId}':
|
'/pet/{petId}':
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
@ -259,7 +269,7 @@ paths:
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
example: "Bearer <TOKEN>"
|
example: 'Bearer <TOKEN>'
|
||||||
- name: petId
|
- name: petId
|
||||||
in: path
|
in: path
|
||||||
description: Pet id to delete
|
description: Pet id to delete
|
||||||
|
@ -432,7 +442,7 @@ paths:
|
||||||
application/json:
|
application/json:
|
||||||
example:
|
example:
|
||||||
status: 400
|
status: 400
|
||||||
message: "Invalid Order"
|
message: 'Invalid Order'
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
|
@ -533,6 +543,20 @@ paths:
|
||||||
subscriptionId:
|
subscriptionId:
|
||||||
type: string
|
type: string
|
||||||
example: AAA-123-BBB-456
|
example: AAA-123-BBB-456
|
||||||
|
'200':
|
||||||
|
description: Successful operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
maxItems: 999
|
||||||
|
minItems: 0
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
maxItems: 777
|
||||||
|
minItems: 111
|
||||||
|
items:
|
||||||
|
type: number
|
||||||
callbacks:
|
callbacks:
|
||||||
orderInProgress:
|
orderInProgress:
|
||||||
'{$request.body#/callbackUrl}?event={$request.body#/eventName}':
|
'{$request.body#/callbackUrl}?event={$request.body#/eventName}':
|
||||||
|
@ -880,11 +904,11 @@ paths:
|
||||||
type: string
|
type: string
|
||||||
examples:
|
examples:
|
||||||
response:
|
response:
|
||||||
value: <Message> OK </Message>
|
value: <Message> OK </Message>
|
||||||
text/plain:
|
text/plain:
|
||||||
examples:
|
examples:
|
||||||
response:
|
response:
|
||||||
value: OK
|
value: OK
|
||||||
'400':
|
'400':
|
||||||
description: Invalid username/password supplied
|
description: Invalid username/password supplied
|
||||||
/user/logout:
|
/user/logout:
|
||||||
|
@ -911,7 +935,7 @@ components:
|
||||||
content:
|
content:
|
||||||
multipart/form-data:
|
multipart/form-data:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Cat"
|
$ref: '#/components/schemas/Cat'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: update Cat details
|
description: update Cat details
|
||||||
|
@ -926,7 +950,7 @@ components:
|
||||||
content:
|
content:
|
||||||
multipart/form-data:
|
multipart/form-data:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Cat"
|
$ref: '#/components/schemas/Cat'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: create Cat details
|
description: create Cat details
|
||||||
|
@ -1059,8 +1083,8 @@ components:
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
externalDocs:
|
externalDocs:
|
||||||
description: "Find more info here"
|
description: 'Find more info here'
|
||||||
url: "https://example.com"
|
url: 'https://example.com'
|
||||||
description: Pet ID
|
description: Pet ID
|
||||||
$ref: '#/components/schemas/Id'
|
$ref: '#/components/schemas/Id'
|
||||||
category:
|
category:
|
||||||
|
@ -1166,6 +1190,11 @@ components:
|
||||||
description: User status
|
description: User status
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
|
image:
|
||||||
|
description: User image
|
||||||
|
type: string
|
||||||
|
contentEncoding: base64
|
||||||
|
contentMediaType: image/png
|
||||||
xml:
|
xml:
|
||||||
name: User
|
name: User
|
||||||
requestBodies:
|
requestBodies:
|
||||||
|
@ -1232,9 +1261,9 @@ webhooks:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Pet"
|
$ref: '#/components/schemas/Pet'
|
||||||
responses:
|
responses:
|
||||||
"200":
|
'200':
|
||||||
description: Return a 200 status to indicate that the data was received successfully
|
description: Return a 200 status to indicate that the data was received successfully
|
||||||
myWebhook:
|
myWebhook:
|
||||||
$ref: '#/components/pathItems/webhooks'
|
$ref: '#/components/pathItems/webhooks'
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -3,7 +3,7 @@ import ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||||
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
|
import * as HtmlWebpackPlugin from 'html-webpack-plugin';
|
||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import * as webpack from 'webpack';
|
import * as webpack from 'webpack';
|
||||||
import { getBabelLoader, webpackIgnore } from '../config/webpack-utils';
|
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(
|
||||||
|
@ -33,18 +33,20 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
},
|
},
|
||||||
|
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: __dirname,
|
static: __dirname,
|
||||||
watchContentBase: true,
|
|
||||||
port: 9090,
|
port: 9090,
|
||||||
disableHostCheck: true,
|
|
||||||
stats: 'minimal',
|
|
||||||
hot: true,
|
hot: true,
|
||||||
|
historyApiFallback: true,
|
||||||
|
open: true,
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
children: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.ts', '.tsx', '.js', '.json'],
|
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||||
fallback: {
|
fallback: {
|
||||||
path: require.resolve('path-browserify'),
|
path: require.resolve('path-browserify'),
|
||||||
|
buffer: require.resolve('buffer'),
|
||||||
http: false,
|
http: false,
|
||||||
fs: false,
|
fs: false,
|
||||||
os: false,
|
os: false,
|
||||||
|
@ -71,33 +73,28 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
rules: [
|
rules: [
|
||||||
{ 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: [getBabelLoader({useBuiltIns: true, hot: true} )],
|
loader: 'esbuild-loader',
|
||||||
exclude: {
|
options: {
|
||||||
and: [/node_modules/],
|
loader: 'tsx',
|
||||||
not: {
|
target: 'es2015',
|
||||||
or: [
|
tsconfigRaw: require('../tsconfig.json'),
|
||||||
/swagger2openapi/,
|
|
||||||
/reftools/,
|
|
||||||
/openapi-sampler/,
|
|
||||||
/mobx/,
|
|
||||||
/oas-resolver/,
|
|
||||||
/oas-kit-common/,
|
|
||||||
/oas-schema-walker/,
|
|
||||||
/\@redocly\/openapi-core/,
|
|
||||||
/colorette/,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
exclude: [/node_modules/],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
use: {
|
use: [
|
||||||
loader: 'css-loader',
|
'style-loader',
|
||||||
options: {
|
'css-loader',
|
||||||
sourceMap: true,
|
{
|
||||||
|
loader: 'esbuild-loader',
|
||||||
|
options: {
|
||||||
|
loader: 'css',
|
||||||
|
minify: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -118,6 +115,9 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
||||||
? 'benchmark/index.html'
|
? 'benchmark/index.html'
|
||||||
: 'demo/index.html',
|
: 'demo/index.html',
|
||||||
}),
|
}),
|
||||||
|
new webpack.ProvidePlugin({
|
||||||
|
Buffer: ['buffer', 'Buffer'],
|
||||||
|
}),
|
||||||
new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
|
new ForkTsCheckerWebpackPlugin({ logger: { infrastructure: 'silent', issues: 'console' } }),
|
||||||
webpackIgnore(/js-yaml\/dumper\.js$/),
|
webpackIgnore(/js-yaml\/dumper\.js$/),
|
||||||
webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
|
webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
|
||||||
|
|
114
docs/deployment/cli.md
Normal file
114
docs/deployment/cli.md
Normal 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
41
docs/deployment/docker.md
Normal 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
123
docs/deployment/html.md
Normal 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
112
docs/deployment/intro.md
Normal 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
80
docs/deployment/react.md
Normal 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
BIN
docs/images/redoc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
54
docs/quickstart.md
Normal file
54
docs/quickstart.md
Normal 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
112
docs/quickstart/cli.md
Normal 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
39
docs/quickstart/docker.md
Normal 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
214
docs/quickstart/html.md
Normal 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
44
docs/quickstart/intro.md
Normal 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
78
docs/quickstart/react.md
Normal 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!');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
|
@ -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
|
||||||
|
|
|
@ -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';
|
|
||||||
```
|
|
|
@ -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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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',
|
||||||
);
|
);
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
19
e2e/integration/urls.e2e.ts
Normal file
19
e2e/integration/urls.e2e.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
9753
package-lock.json
generated
9753
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
79
package.json
79
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"version": "2.0.0-rc.55",
|
"version": "2.0.0-rc.66",
|
||||||
"description": "ReDoc",
|
"description": "ReDoc",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -27,60 +27,53 @@
|
||||||
"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 serve --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 serve --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 serve --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",
|
"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 production --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 production --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 serve --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.14.3",
|
|
||||||
"@babel/plugin-proposal-class-properties": "^7.13.0",
|
|
||||||
"@babel/plugin-proposal-decorators": "^7.14.2",
|
|
||||||
"@babel/plugin-proposal-object-rest-spread": "^7.14.4",
|
|
||||||
"@babel/plugin-syntax-decorators": "^7.12.13",
|
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
|
||||||
"@babel/plugin-syntax-jsx": "^7.10.4",
|
|
||||||
"@babel/plugin-syntax-typescript": "^7.10.4",
|
|
||||||
"@babel/plugin-transform-runtime": "^7.14.3",
|
|
||||||
"@babel/preset-env": "^7.14.4",
|
|
||||||
"@babel/preset-react": "^7.13.13",
|
|
||||||
"@babel/preset-typescript": "^7.13.0",
|
|
||||||
"@cypress/webpack-preprocessor": "^5.9.0",
|
"@cypress/webpack-preprocessor": "^5.9.0",
|
||||||
"@hot-loader/react-dom": "^17.0.1",
|
"@hot-loader/react-dom": "^17.0.1",
|
||||||
|
"@size-limit/preset-app": "^7.0.4",
|
||||||
"@types/chai": "^4.2.18",
|
"@types/chai": "^4.2.18",
|
||||||
"@types/dompurify": "^2.2.2",
|
"@types/dompurify": "^2.2.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.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/json-pointer": "^1.0.30",
|
"@types/json-pointer": "^1.0.30",
|
||||||
"@types/lodash": "^4.14.170",
|
|
||||||
"@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/node": "^15.6.1",
|
||||||
"@types/prismjs": "^1.16.5",
|
"@types/prismjs": "^1.16.5",
|
||||||
"@types/prop-types": "^15.7.3",
|
"@types/prop-types": "^15.7.3",
|
||||||
"@types/react": "^17.0.8",
|
"@types/react": "^17.0.8",
|
||||||
|
@ -94,10 +87,7 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||||
"@typescript-eslint/parser": "^4.26.0",
|
"@typescript-eslint/parser": "^4.26.0",
|
||||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
||||||
"babel-loader": "^8.2.2",
|
|
||||||
"babel-plugin-styled-components": "^1.12.0",
|
|
||||||
"beautify-benchmark": "^0.2.4",
|
"beautify-benchmark": "^0.2.4",
|
||||||
"bundlesize": "^0.18.1",
|
|
||||||
"conventional-changelog-cli": "^2.0.34",
|
"conventional-changelog-cli": "^2.0.34",
|
||||||
"copy-webpack-plugin": "^9.0.0",
|
"copy-webpack-plugin": "^9.0.0",
|
||||||
"core-js": "^3.13.1",
|
"core-js": "^3.13.1",
|
||||||
|
@ -106,33 +96,38 @@
|
||||||
"cypress": "^7.4.0",
|
"cypress": "^7.4.0",
|
||||||
"enzyme": "^3.11.0",
|
"enzyme": "^3.11.0",
|
||||||
"enzyme-to-json": "^3.6.2",
|
"enzyme-to-json": "^3.6.2",
|
||||||
|
"esbuild-loader": "^2.18.0",
|
||||||
"eslint": "^7.27.0",
|
"eslint": "^7.27.0",
|
||||||
"eslint-plugin-import": "^2.23.4",
|
"eslint-plugin-import": "^2.23.4",
|
||||||
"eslint-plugin-react": "^7.24.0",
|
"eslint-plugin-react": "^7.25.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.2.10",
|
"fork-ts-checker-webpack-plugin": "^6.2.10",
|
||||||
"html-webpack-plugin": "^5.3.1",
|
"html-webpack-plugin": "^5.3.1",
|
||||||
|
"husky": "^7.0.0",
|
||||||
"jest": "^27.0.3",
|
"jest": "^27.0.3",
|
||||||
|
"js-yaml": "^4.1.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash.noop": "^3.0.1",
|
||||||
"mobx": "^6.3.2",
|
"mobx": "^6.3.2",
|
||||||
"prettier": "^2.3.0",
|
"prettier": "^2.3.2",
|
||||||
|
"pretty-quick": "^3.0.0",
|
||||||
"raf": "^3.4.1",
|
"raf": "^3.4.1",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-hot-loader": "^4.13.0",
|
"react-hot-loader": "^4.13.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"shelljs": "^0.8.4",
|
"shelljs": "^0.8.4",
|
||||||
"style-loader": "^2.0.0",
|
"size-limit": "^7.0.4",
|
||||||
"styled-components": "^5.3.0",
|
"styled-components": "^5.3.0",
|
||||||
"ts-jest": "^27.0.2",
|
"ts-jest": "^27.0.2",
|
||||||
"ts-loader": "^8.0.1",
|
"ts-loader": "^9.2.6",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "~4.1.0",
|
"typescript": "~4.1.0",
|
||||||
"unfetch": "^4.2.0",
|
"unfetch": "^4.2.0",
|
||||||
"url-polyfill": "^1.1.12",
|
"url-polyfill": "^1.1.12",
|
||||||
"webpack": "^5.38.1",
|
"webpack": "^5.38.1",
|
||||||
"webpack-cli": "^4.7.2",
|
"webpack-cli": "^4.7.2",
|
||||||
"webpack-dev-server": "^3.11.2",
|
"webpack-dev-server": "^4.6.0",
|
||||||
"webpack-node-externals": "^3.0.0",
|
"webpack-node-externals": "^3.0.0",
|
||||||
"workerize-loader": "github:redocly/workerize-loader#webpack-5-dist"
|
"workerize-loader": "github:redocly/workerize-loader#webpack-5-dist"
|
||||||
},
|
},
|
||||||
|
@ -144,37 +139,34 @@
|
||||||
"styled-components": "^4.1.1 || ^5.1.1"
|
"styled-components": "^4.1.1 || ^5.1.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.14.0",
|
"@redocly/openapi-core": "^1.0.0-beta.88",
|
||||||
"@redocly/openapi-core": "^1.0.0-beta.50",
|
|
||||||
"@redocly/react-dropdown-aria": "^2.0.11",
|
"@redocly/react-dropdown-aria": "^2.0.11",
|
||||||
"@types/node": "^15.6.1",
|
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"decko": "^1.2.0",
|
"decko": "^1.2.0",
|
||||||
"dompurify": "^2.2.8",
|
"dompurify": "^2.2.8",
|
||||||
"eventemitter3": "^4.0.7",
|
"eventemitter3": "^4.0.7",
|
||||||
"json-pointer": "^0.6.1",
|
"json-pointer": "^0.6.2",
|
||||||
"js-yaml": "^4.1.0",
|
|
||||||
"lunr": "^2.3.9",
|
"lunr": "^2.3.9",
|
||||||
"mark.js": "^8.11.1",
|
"mark.js": "^8.11.1",
|
||||||
"marked": "^0.7.0",
|
"marked": "^4.0.10",
|
||||||
"memoize-one": "^5.2.1",
|
|
||||||
"mobx-react": "^7.2.0",
|
"mobx-react": "^7.2.0",
|
||||||
"openapi-sampler": "^1.0.1",
|
"openapi-sampler": "^1.2.1",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"perfect-scrollbar": "^1.5.1",
|
"perfect-scrollbar": "^1.5.1",
|
||||||
"polished": "^4.1.3",
|
"polished": "^4.1.3",
|
||||||
"prismjs": "^1.24.1",
|
"prismjs": "^1.27.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react-tabs": "^3.2.2",
|
"react-tabs": "^3.2.2",
|
||||||
"slugify": "~1.4.7",
|
"slugify": "~1.4.7",
|
||||||
"stickyfill": "^1.1.1",
|
"stickyfill": "^1.1.1",
|
||||||
|
"style-loader": "^3.3.1",
|
||||||
"swagger2openapi": "^7.0.6",
|
"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": "350 kB"
|
"limit": "350 kB"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"jest": {
|
"jest": {
|
||||||
|
@ -199,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"
|
||||||
}
|
}
|
||||||
|
@ -207,6 +202,6 @@
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"parser": "typescript"
|
"arrowParens": "avoid"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -96,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;
|
||||||
|
|
|
@ -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')};
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -6,7 +6,7 @@ import styled, { css } from '../styled-components';
|
||||||
import { HistoryService } from '../services';
|
import { HistoryService } from '../services';
|
||||||
|
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
export const linkifyMixin = (className) => css`
|
export const linkifyMixin = className => css`
|
||||||
${className} {
|
${className} {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: -20px;
|
margin-left: -20px;
|
||||||
|
@ -33,17 +33,17 @@ 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 function Link(props: { to: string; className?: string; children?: any }) {
|
export function Link(props: { to: string; className?: string; children?: any }) {
|
||||||
const store = React.useContext(StoreContext);
|
const store = React.useContext(StoreContext);
|
||||||
const clickHandler = React.useCallback(
|
const clickHandler = React.useCallback(
|
||||||
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
(event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||||
if (!store) return;
|
if (!store) return;
|
||||||
navigate(store.menu.history, event, props.to);
|
navigate(store.menu.history, event, props.to);
|
||||||
},
|
},
|
||||||
[store],
|
[store, props.to],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!store) return null;
|
if (!store) return null;
|
||||||
|
@ -67,7 +67,7 @@ function navigate(history: HistoryService, event: React.MouseEvent<HTMLAnchorEle
|
||||||
!isModifiedEvent(event) // ignore clicks with modifier keys
|
!isModifiedEvent(event) // ignore clicks with modifier keys
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
history.replace(to);
|
history.replace(encodeURI(to));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 `
|
||||||
|
|
|
@ -39,7 +39,12 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
const license =
|
const license =
|
||||||
(info.license && (
|
(info.license && (
|
||||||
<InfoSpan>
|
<InfoSpan>
|
||||||
License: {info.license.identifier ? info.license.identifier : (<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;
|
||||||
|
@ -101,8 +106,8 @@ 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.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>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
25
src/components/Fields/ArrayItemDetails.tsx
Normal file
25
src/components/Fields/ArrayItemDetails.tsx
Normal 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> <{schema.displayFormat} ></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;
|
||||||
|
`;
|
|
@ -8,7 +8,7 @@ import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
|
||||||
|
|
||||||
export interface EnumValuesProps {
|
export interface EnumValuesProps {
|
||||||
values: string[];
|
values: string[];
|
||||||
type: string | 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>{' '}
|
||||||
|
|
36
src/components/Fields/Examples.tsx
Normal file
36
src/components/Fields/Examples.tsx
Normal 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;
|
||||||
|
`;
|
|
@ -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>
|
||||||
|
|
|
@ -7,18 +7,18 @@ export interface FieldDetailProps {
|
||||||
raw?: boolean;
|
raw?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FieldDetail extends React.PureComponent<FieldDetailProps> {
|
function FieldDetailComponent({ value, label, raw }: FieldDetailProps) {
|
||||||
render() {
|
if (value === undefined) {
|
||||||
if (this.props.value === undefined) {
|
return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = this.props.raw ? this.props.value : JSON.stringify(this.props.value);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<FieldLabel> {this.props.label} </FieldLabel> <ExampleValue>{value}</ExampleValue>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stringifyValue = raw ? String(value) : JSON.stringify(value);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FieldLabel> {label} </FieldLabel> <ExampleValue>{stringifyValue}</ExampleValue>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const FieldDetail = React.memo<FieldDetailProps>(FieldDetailComponent);
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PatternLabel,
|
|
||||||
RecursiveLabel,
|
RecursiveLabel,
|
||||||
TypeFormat,
|
TypeFormat,
|
||||||
TypeName,
|
TypeName,
|
||||||
TypePrefix,
|
TypePrefix,
|
||||||
TypeTitle,
|
TypeTitle,
|
||||||
ToggleButton,
|
|
||||||
FieldLabel,
|
|
||||||
ExampleValue,
|
|
||||||
} 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';
|
||||||
|
|
||||||
|
@ -24,131 +21,92 @@ import { Badge } from '../../common-elements/';
|
||||||
|
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
import { OptionsContext } from '../OptionsProvider';
|
import { OptionsContext } from '../OptionsProvider';
|
||||||
import { FieldModel } from '../../services/models/Field';
|
import { Pattern } from './Pattern';
|
||||||
import styled from '../../styled-components';
|
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, hideSchemaPattern } = this.context;
|
|
||||||
|
|
||||||
const { schema, description, example, deprecated, examples } = field;
|
|
||||||
|
|
||||||
const rawDefault = !!enumSkipQuotes || field.in === 'header'; // having quotes around header field default values is confusing and inappropriate
|
|
||||||
|
|
||||||
let renderedExamples: JSX.Element | null = null;
|
|
||||||
|
|
||||||
if (showExamples && (example !== undefined || examples !== undefined)) {
|
|
||||||
if (examples !== undefined) {
|
|
||||||
renderedExamples = <Examples field={field} />;
|
|
||||||
} else {
|
} else {
|
||||||
const label = l('example') + ':';
|
return (
|
||||||
const raw = !!field.in;
|
<FieldDetail
|
||||||
renderedExamples = <FieldDetail label={label} value={getSerializedValue(field, field.example)} raw={raw} />;
|
label={l('example') + ':'}
|
||||||
|
value={getSerializedValue(field, field.example)}
|
||||||
|
raw={Boolean(field.in)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<TypePrefix>{schema.typePrefix}</TypePrefix>
|
|
||||||
<TypeName>{schema.displayType}</TypeName>
|
|
||||||
{schema.displayFormat && (
|
|
||||||
<TypeFormat>
|
|
||||||
{' '}
|
|
||||||
<
|
|
||||||
{schema.displayFormat}
|
|
||||||
>{' '}
|
|
||||||
</TypeFormat>
|
|
||||||
)}
|
|
||||||
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
|
||||||
<ConstraintsView constraints={schema.constraints} />
|
|
||||||
{schema.pattern && !hideSchemaPattern && (
|
|
||||||
<>
|
|
||||||
<PatternLabel>
|
|
||||||
{patternShown || schema.pattern.length < MAX_PATTERN_LENGTH
|
|
||||||
? schema.pattern
|
|
||||||
: `${schema.pattern.substr(0, MAX_PATTERN_LENGTH)}...`}
|
|
||||||
</PatternLabel>
|
|
||||||
{schema.pattern.length > MAX_PATTERN_LENGTH && (
|
|
||||||
<ToggleButton onClick={this.togglePattern}>
|
|
||||||
{patternShown ? 'Hide pattern' : 'Show pattern'}
|
|
||||||
</ToggleButton>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
|
||||||
</div>
|
|
||||||
{deprecated && (
|
|
||||||
<div>
|
|
||||||
<Badge type="warning"> {l('deprecated')} </Badge>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
|
||||||
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
|
|
||||||
{renderedExamples}
|
|
||||||
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
|
|
||||||
<div>
|
|
||||||
<Markdown compact={true} source={description} />
|
|
||||||
</div>
|
|
||||||
{schema.externalDocs && (
|
|
||||||
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
|
|
||||||
)}
|
|
||||||
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(this.props)) || null}
|
|
||||||
{field.const && (<FieldDetail label={l('const') + ':'} value={field.const}/>) || null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function Examples({ field }: { field: FieldModel }) {
|
|
||||||
if (!field.examples) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}, [field, showExamples]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div>
|
||||||
<FieldLabel> {l('examples')}: </FieldLabel>
|
<div>
|
||||||
<ExamplesList>
|
<TypePrefix>{schema.typePrefix}</TypePrefix>
|
||||||
{Object.values(field.examples).map((example, idx) => {
|
<TypeName>{schema.displayType}</TypeName>
|
||||||
return (
|
{schema.displayFormat && (
|
||||||
<li key={idx}>
|
<TypeFormat>
|
||||||
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> - {example.summary || example.description}
|
{' '}
|
||||||
</li>
|
<
|
||||||
);
|
{schema.displayFormat}
|
||||||
})}
|
>{' '}
|
||||||
</ExamplesList>
|
</TypeFormat>
|
||||||
</>
|
)}
|
||||||
|
{schema.contentEncoding && (
|
||||||
|
<TypeFormat>
|
||||||
|
{' '}
|
||||||
|
<
|
||||||
|
{schema.contentEncoding}
|
||||||
|
>{' '}
|
||||||
|
</TypeFormat>
|
||||||
|
)}
|
||||||
|
{schema.contentMediaType && (
|
||||||
|
<TypeFormat>
|
||||||
|
{' '}
|
||||||
|
<
|
||||||
|
{schema.contentMediaType}
|
||||||
|
>{' '}
|
||||||
|
</TypeFormat>
|
||||||
|
)}
|
||||||
|
{schema.title && !hideSchemaTitles && <TypeTitle> ({schema.title}) </TypeTitle>}
|
||||||
|
<ConstraintsView constraints={schema.constraints} />
|
||||||
|
<Pattern schema={schema} />
|
||||||
|
{schema.isCircular && <RecursiveLabel> {l('recursive')} </RecursiveLabel>}
|
||||||
|
{isArrayType && schema.items && <ArrayItemDetails schema={schema.items} />}
|
||||||
|
</div>
|
||||||
|
{deprecated && (
|
||||||
|
<div>
|
||||||
|
<Badge type="warning"> {l('deprecated')} </Badge>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
|
||||||
|
{!renderDiscriminatorSwitch && (
|
||||||
|
<EnumValues isArrayType={isArrayType} values={schema.enum} />
|
||||||
|
)}{' '}
|
||||||
|
{renderedExamples}
|
||||||
|
<Extensions extensions={{ ...extensions, ...schema.extensions }} />
|
||||||
|
<div>
|
||||||
|
<Markdown compact={true} source={description} />
|
||||||
|
</div>
|
||||||
|
{schema.externalDocs && (
|
||||||
|
<ExternalDocumentation externalDocs={schema.externalDocs} compact={true} />
|
||||||
|
)}
|
||||||
|
{(renderDiscriminatorSwitch && renderDiscriminatorSwitch(props)) || null}
|
||||||
|
{(_const && <FieldDetail label={l('const') + ':'} value={_const} />) || null}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSerializedValue(field: FieldModel, example: any) {
|
export const FieldDetails = React.memo<FieldProps>(FieldDetailsComponent);
|
||||||
if (field.in) {
|
|
||||||
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
|
|
||||||
return decodeURIComponent(serializeParameterValue(field, example));
|
|
||||||
} else {
|
|
||||||
return example;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const ExamplesList = styled.ul`
|
|
||||||
margin-top: 1em;
|
|
||||||
padding-left: 0;
|
|
||||||
list-style-position: inside;
|
|
||||||
`;
|
|
||||||
|
|
33
src/components/Fields/Pattern.tsx
Normal file
33
src/components/Fields/Pattern.tsx
Normal 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>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -34,11 +34,11 @@ class Json extends React.PureComponent<JsonProps> {
|
||||||
<button onClick={this.collapseAll}> Collapse all </button>
|
<button onClick={this.collapseAll}> Collapse all </button>
|
||||||
</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),
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -26,7 +26,6 @@ export const StyledMarkdownBlock = styled(
|
||||||
{ compact?: boolean; inline?: boolean }
|
{ compact?: boolean; inline?: boolean }
|
||||||
>,
|
>,
|
||||||
)`
|
)`
|
||||||
|
|
||||||
font-family: ${props => props.theme.typography.fontFamily};
|
font-family: ${props => props.theme.typography.fontFamily};
|
||||||
font-weight: ${props => props.theme.typography.fontWeightRegular};
|
font-weight: ${props => props.theme.typography.fontWeightRegular};
|
||||||
line-height: ${props => props.theme.typography.lineHeight};
|
line-height: ${props => props.theme.typography.lineHeight};
|
||||||
|
@ -81,7 +80,7 @@ export const StyledMarkdownBlock = styled(
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||||
white-space:${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
|
white-space: ${({ theme }) => (theme.typography.code.wrap ? 'pre-wrap' : 'pre')};
|
||||||
background-color: ${({ theme }) => theme.codeBlock.backgroundColor};
|
background-color: ${({ theme }) => theme.codeBlock.backgroundColor};
|
||||||
color: white;
|
color: white;
|
||||||
padding: ${props => props.theme.spacing.unit * 4}px;
|
padding: ${props => props.theme.spacing.unit * 4}px;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
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';
|
||||||
|
@ -13,12 +17,21 @@ export interface RedocStandaloneProps {
|
||||||
onLoaded?: (e?: Error) => any;
|
onLoaded?: (e?: Error) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare let __webpack_nonce__: string;
|
||||||
|
|
||||||
export const RedocStandalone = function (props: RedocStandaloneProps) {
|
export const RedocStandalone = function (props: RedocStandaloneProps) {
|
||||||
const { spec, specUrl, options = {}, onLoaded } = props;
|
const { spec, specUrl, options = {}, onLoaded } = props;
|
||||||
const hideLoading = options.hideLoading !== undefined;
|
const hideLoading = argValueToBoolean(options.hideLoading, false);
|
||||||
|
|
||||||
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}>
|
||||||
|
@ -32,4 +45,4 @@ export const RedocStandalone = function (props: RedocStandaloneProps) {
|
||||||
</StoreBuilder>
|
</StoreBuilder>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,39 +1,47 @@
|
||||||
import { observer } from 'mobx-react';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
|
||||||
import { ResponseModel } from '../../services/models';
|
import type { ResponseModel, MediaTypeModel } from '../../services/models';
|
||||||
import { ResponseDetails } from './ResponseDetails';
|
import { ResponseDetails } from './ResponseDetails';
|
||||||
import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements';
|
import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements';
|
||||||
|
|
||||||
@observer
|
export interface ResponseViewProps {
|
||||||
export class ResponseView extends React.Component<{ response: ResponseModel }> {
|
response: ResponseModel;
|
||||||
toggle = () => {
|
|
||||||
this.props.response.toggle();
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { headers, type, summary, description, code, expanded, content } = this.props.response;
|
|
||||||
const mimes =
|
|
||||||
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined);
|
|
||||||
|
|
||||||
const empty = headers.length === 0 && mimes.length === 0 && !description;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<StyledResponseTitle
|
|
||||||
onClick={this.toggle}
|
|
||||||
type={type}
|
|
||||||
empty={empty}
|
|
||||||
title={summary || ''}
|
|
||||||
code={code}
|
|
||||||
opened={expanded}
|
|
||||||
/>
|
|
||||||
{expanded && !empty && (
|
|
||||||
<ResponseDetailsWrap>
|
|
||||||
<ResponseDetails response={this.props.response} />
|
|
||||||
</ResponseDetailsWrap>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ResponseView = observer(({ response }: ResponseViewProps): React.ReactElement => {
|
||||||
|
const { extensions, headers, type, summary, description, code, expanded, content } = response;
|
||||||
|
|
||||||
|
const mimes = React.useMemo<MediaTypeModel[]>(
|
||||||
|
() =>
|
||||||
|
content === undefined ? [] : content.mediaTypes.filter(mime => mime.schema !== undefined),
|
||||||
|
[content],
|
||||||
|
);
|
||||||
|
|
||||||
|
const empty = React.useMemo<boolean>(
|
||||||
|
() =>
|
||||||
|
(!extensions || Object.keys(extensions).length === 0) &&
|
||||||
|
headers.length === 0 &&
|
||||||
|
mimes.length === 0 &&
|
||||||
|
!description,
|
||||||
|
[extensions, headers, mimes, description],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StyledResponseTitle
|
||||||
|
onClick={() => response.toggle()}
|
||||||
|
type={type}
|
||||||
|
empty={empty}
|
||||||
|
title={summary || ''}
|
||||||
|
code={code}
|
||||||
|
opened={expanded}
|
||||||
|
/>
|
||||||
|
{expanded && !empty && (
|
||||||
|
<ResponseDetailsWrap>
|
||||||
|
<ResponseDetails response={response} />
|
||||||
|
</ResponseDetailsWrap>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
@ -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 }) => {
|
||||||
|
|
|
@ -14,27 +14,34 @@ export interface ResponseTitleProps {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ResponseTitle extends React.PureComponent<ResponseTitleProps> {
|
function ResponseTitleComponent({
|
||||||
render() {
|
title,
|
||||||
const { title, type, empty, code, opened, className, onClick } = this.props;
|
type,
|
||||||
return (
|
empty,
|
||||||
<button
|
code,
|
||||||
className={className}
|
opened,
|
||||||
onClick={(!empty && onClick) || undefined}
|
className,
|
||||||
aria-expanded={opened}
|
onClick,
|
||||||
disabled={empty}
|
}: ResponseTitleProps): React.ReactElement {
|
||||||
>
|
return (
|
||||||
{!empty && (
|
<button
|
||||||
<ShelfIcon
|
className={className}
|
||||||
size={'1.5em'}
|
onClick={(!empty && onClick) || undefined}
|
||||||
color={type}
|
aria-expanded={opened}
|
||||||
direction={opened ? 'down' : 'right'}
|
disabled={empty}
|
||||||
float={'left'}
|
>
|
||||||
/>
|
{!empty && (
|
||||||
)}
|
<ShelfIcon
|
||||||
<Code>{code} </Code>
|
size={'1.5em'}
|
||||||
<Markdown compact={true} inline={true} source={title} />
|
color={type}
|
||||||
</button>
|
direction={opened ? 'down' : 'right'}
|
||||||
);
|
float={'left'}
|
||||||
}
|
/>
|
||||||
|
)}
|
||||||
|
<Code>{code} </Code>
|
||||||
|
<Markdown compact={true} inline={true} source={title} />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ResponseTitle = React.memo<ResponseTitleProps>(ResponseTitleComponent);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -18,37 +18,38 @@ export interface ObjectSchemaProps extends SchemaProps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
export const ObjectSchema = observer(
|
||||||
export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
({
|
||||||
static contextType = OptionsContext;
|
schema: { fields = [], title },
|
||||||
|
showTitle,
|
||||||
|
discriminator,
|
||||||
|
skipReadOnly,
|
||||||
|
skipWriteOnly,
|
||||||
|
level,
|
||||||
|
}: ObjectSchemaProps) => {
|
||||||
|
const { expandSingleSchemaField, showObjectSchemaExamples, schemaExpansionLevel } =
|
||||||
|
React.useContext(OptionsContext);
|
||||||
|
|
||||||
get parentSchema() {
|
const filteredFields = React.useMemo(
|
||||||
return this.props.discriminator!.parentSchema;
|
() =>
|
||||||
}
|
skipReadOnly || skipWriteOnly
|
||||||
|
? fields.filter(
|
||||||
|
item =>
|
||||||
|
!(
|
||||||
|
(skipReadOnly && item.schema.readOnly) ||
|
||||||
|
(skipWriteOnly && item.schema.writeOnly)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: fields,
|
||||||
|
[skipReadOnly, skipWriteOnly, fields],
|
||||||
|
);
|
||||||
|
|
||||||
render() {
|
const expandByDefault =
|
||||||
const {
|
(expandSingleSchemaField && filteredFields.length === 1) || schemaExpansionLevel >= level!;
|
||||||
schema: { fields = [] },
|
|
||||||
showTitle,
|
|
||||||
discriminator,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const needFilter = this.props.skipReadOnly || this.props.skipWriteOnly;
|
|
||||||
|
|
||||||
const filteredFields = needFilter
|
|
||||||
? fields.filter(item => {
|
|
||||||
return !(
|
|
||||||
(this.props.skipReadOnly && item.schema.readOnly) ||
|
|
||||||
(this.props.skipWriteOnly && item.schema.writeOnly)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
: fields;
|
|
||||||
|
|
||||||
const expandByDefault = this.context.expandSingleSchemaField && filteredFields.length === 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PropertiesTable>
|
<PropertiesTable>
|
||||||
{showTitle && <PropertiesTableCaption>{this.props.schema.title}</PropertiesTableCaption>}
|
{showTitle && <PropertiesTableCaption>{title}</PropertiesTableCaption>}
|
||||||
<tbody>
|
<tbody>
|
||||||
{mapWithLast(filteredFields, (field, isLast) => {
|
{mapWithLast(filteredFields, (field, isLast) => {
|
||||||
return (
|
return (
|
||||||
|
@ -58,26 +59,26 @@ export class ObjectSchema extends React.Component<ObjectSchemaProps> {
|
||||||
field={field}
|
field={field}
|
||||||
expandByDefault={expandByDefault}
|
expandByDefault={expandByDefault}
|
||||||
renderDiscriminatorSwitch={
|
renderDiscriminatorSwitch={
|
||||||
(discriminator &&
|
discriminator?.fieldName === field.name
|
||||||
discriminator.fieldName === field.name &&
|
? () => (
|
||||||
(() => (
|
<DiscriminatorDropdown
|
||||||
<DiscriminatorDropdown
|
parent={discriminator!.parentSchema}
|
||||||
parent={this.parentSchema}
|
enumValues={field.schema.enum}
|
||||||
enumValues={field.schema.enum}
|
/>
|
||||||
/>
|
)
|
||||||
))) ||
|
: undefined
|
||||||
undefined
|
|
||||||
}
|
}
|
||||||
className={field.expanded ? 'expanded' : undefined}
|
className={field.expanded ? 'expanded' : undefined}
|
||||||
showExamples={false}
|
showExamples={showObjectSchemaExamples}
|
||||||
skipReadOnly={this.props.skipReadOnly}
|
skipReadOnly={skipReadOnly}
|
||||||
skipWriteOnly={this.props.skipWriteOnly}
|
skipWriteOnly={skipWriteOnly}
|
||||||
showTitle={this.props.showTitle}
|
showTitle={showTitle}
|
||||||
|
level={level}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</PropertiesTable>
|
</PropertiesTable>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
}
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
OneOfLabel,
|
OneOfLabel,
|
||||||
OneOfList,
|
OneOfList,
|
||||||
} from '../../common-elements/schema';
|
} from '../../common-elements/schema';
|
||||||
|
import { Badge } from '../../common-elements/shelfs';
|
||||||
import { SchemaModel } from '../../services/models';
|
import { SchemaModel } from '../../services/models';
|
||||||
import { Schema, SchemaProps } from './Schema';
|
import { Schema, SchemaProps } from './Schema';
|
||||||
|
|
||||||
|
@ -20,7 +21,11 @@ export class OneOfButton extends React.Component<OneOfButtonProps> {
|
||||||
render() {
|
render() {
|
||||||
const { idx, schema, subSchema } = this.props;
|
const { idx, schema, subSchema } = this.props;
|
||||||
return (
|
return (
|
||||||
<StyledOneOfButton active={idx === schema.activeOneOf} onClick={this.activateOneOf}>
|
<StyledOneOfButton
|
||||||
|
deprecated={subSchema.deprecated}
|
||||||
|
active={idx === schema.activeOneOf}
|
||||||
|
onClick={this.activateOneOf}
|
||||||
|
>
|
||||||
{subSchema.title || subSchema.typePrefix + subSchema.displayType}
|
{subSchema.title || subSchema.typePrefix + subSchema.displayType}
|
||||||
</StyledOneOfButton>
|
</StyledOneOfButton>
|
||||||
);
|
);
|
||||||
|
@ -50,6 +55,9 @@ export class OneOfSchema extends React.Component<SchemaProps> {
|
||||||
<OneOfButton key={subSchema.pointer} schema={schema} subSchema={subSchema} idx={idx} />
|
<OneOfButton key={subSchema.pointer} schema={schema} subSchema={subSchema} idx={idx} />
|
||||||
))}
|
))}
|
||||||
</OneOfList>
|
</OneOfList>
|
||||||
|
<div>
|
||||||
|
{oneOf[schema.activeOneOf].deprecated && <Badge type="warning">Deprecated</Badge>}
|
||||||
|
</div>
|
||||||
<Schema {...this.props} schema={oneOf[schema.activeOneOf]} />
|
<Schema {...this.props} schema={oneOf[schema.activeOneOf]} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -16,6 +16,7 @@ export interface SchemaOptions {
|
||||||
showTitle?: boolean;
|
showTitle?: boolean;
|
||||||
skipReadOnly?: boolean;
|
skipReadOnly?: boolean;
|
||||||
skipWriteOnly?: boolean;
|
skipWriteOnly?: boolean;
|
||||||
|
level?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SchemaProps extends SchemaOptions {
|
export interface SchemaProps extends SchemaOptions {
|
||||||
|
@ -25,7 +26,9 @@ export interface SchemaProps extends SchemaOptions {
|
||||||
@observer
|
@observer
|
||||||
export class Schema extends React.Component<Partial<SchemaProps>> {
|
export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
render() {
|
render() {
|
||||||
const { schema } = this.props;
|
const { schema, ...rest } = this.props;
|
||||||
|
const level = (rest.level || 0) + 1;
|
||||||
|
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return <em> Schema not provided </em>;
|
return <em> Schema not provided </em>;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +53,9 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<ObjectSchema
|
<ObjectSchema
|
||||||
{...{ ...this.props, schema: oneOf![schema.activeOneOf] }}
|
{...rest}
|
||||||
|
level={level}
|
||||||
|
schema={oneOf![schema.activeOneOf]}
|
||||||
discriminator={{
|
discriminator={{
|
||||||
fieldName: discriminatorProp,
|
fieldName: discriminatorProp,
|
||||||
parentSchema: schema,
|
parentSchema: schema,
|
||||||
|
@ -60,25 +65,20 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oneOf !== undefined) {
|
if (oneOf !== undefined) {
|
||||||
return <OneOfSchema schema={schema} {...this.props} />;
|
return <OneOfSchema schema={schema} {...rest} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type && Array.isArray(type)) {
|
const types = Array.isArray(type) ? type : [type];
|
||||||
return <ArraySchema {...(this.props as any)} />;
|
if (types.includes('object')) {
|
||||||
}
|
if (schema.fields?.length) {
|
||||||
|
return <ObjectSchema {...(this.props as any)} level={level} />;
|
||||||
switch (type) {
|
}
|
||||||
case 'object':
|
} else if (types.includes('array')) {
|
||||||
if (schema.fields?.length) {
|
return <ArraySchema {...(this.props as any)} level={level} />;
|
||||||
return <ObjectSchema {...(this.props as any)} />;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'array':
|
|
||||||
return <ArraySchema {...(this.props as any)} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: maybe adjust FieldDetails to accept schema
|
// TODO: maybe adjust FieldDetails to accept schema
|
||||||
const field = ({
|
const field = {
|
||||||
schema,
|
schema,
|
||||||
name: '',
|
name: '',
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -87,7 +87,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
|
||||||
deprecated: false,
|
deprecated: false,
|
||||||
toggle: () => null,
|
toggle: () => null,
|
||||||
expanded: false,
|
expanded: false,
|
||||||
} as any) as FieldModel; // cast needed for hot-loader to not fail
|
} as any as FieldModel; // cast needed for hot-loader to not fail
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
SearchResultsBox,
|
SearchResultsBox,
|
||||||
SearchWrap,
|
SearchWrap,
|
||||||
} from './styled.elements';
|
} from './styled.elements';
|
||||||
|
import { l } from '../../services/Labels';
|
||||||
|
|
||||||
export interface SearchBoxProps {
|
export interface SearchBoxProps {
|
||||||
search: SearchStore<string>;
|
search: SearchStore<string>;
|
||||||
|
@ -28,6 +29,7 @@ export interface SearchBoxProps {
|
||||||
|
|
||||||
export interface SearchBoxState {
|
export interface SearchBoxState {
|
||||||
results: SearchResult[];
|
results: SearchResult[];
|
||||||
|
noResults: boolean;
|
||||||
term: string;
|
term: string;
|
||||||
activeItemIdx: number;
|
activeItemIdx: number;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +41,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
results: [],
|
results: [],
|
||||||
|
noResults: false,
|
||||||
term: '',
|
term: '',
|
||||||
activeItemIdx: -1,
|
activeItemIdx: -1,
|
||||||
};
|
};
|
||||||
|
@ -47,6 +50,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
clearResults(term: string) {
|
clearResults(term: string) {
|
||||||
this.setState({
|
this.setState({
|
||||||
results: [],
|
results: [],
|
||||||
|
noResults: false,
|
||||||
term,
|
term,
|
||||||
});
|
});
|
||||||
this.props.marker.unmark();
|
this.props.marker.unmark();
|
||||||
|
@ -55,6 +59,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
clear = () => {
|
clear = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
results: [],
|
results: [],
|
||||||
|
noResults: false,
|
||||||
term: '',
|
term: '',
|
||||||
activeItemIdx: -1,
|
activeItemIdx: -1,
|
||||||
});
|
});
|
||||||
|
@ -95,6 +100,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
setResults(results: SearchResult[], term: string) {
|
setResults(results: SearchResult[], term: string) {
|
||||||
this.setState({
|
this.setState({
|
||||||
results,
|
results,
|
||||||
|
noResults: results.length === 0,
|
||||||
});
|
});
|
||||||
this.props.marker.mark(term);
|
this.props.marker.mark(term);
|
||||||
}
|
}
|
||||||
|
@ -166,6 +172,9 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
||||||
</SearchResultsBox>
|
</SearchResultsBox>
|
||||||
</PerfectScrollbarWrap>
|
</PerfectScrollbarWrap>
|
||||||
)}
|
)}
|
||||||
|
{this.state.term && this.state.noResults ? (
|
||||||
|
<SearchResultsBox data-role="search:results">{l('noResultsFound')}</SearchResultsBox>
|
||||||
|
) : null}
|
||||||
</SearchWrap>
|
</SearchWrap>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// import { transparentize } from 'polished';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import styled, { media } from '../../styled-components';
|
import styled, { media } from '../../styled-components';
|
||||||
|
@ -7,6 +6,23 @@ import { Link, UnderlinedHeader } from '../../common-elements/';
|
||||||
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
|
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
|
||||||
import { linksCss } from '../Markdown/styled.elements';
|
import { linksCss } from '../Markdown/styled.elements';
|
||||||
|
|
||||||
|
const ScopeNameList = styled.ul`
|
||||||
|
display: inline;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: inherit;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: ',';
|
||||||
|
}
|
||||||
|
&:last-child:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const ScopeName = styled.code`
|
const ScopeName = styled.code`
|
||||||
font-size: ${props => props.theme.typography.code.fontSize};
|
font-size: ${props => props.theme.typography.code.fontSize};
|
||||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||||
|
@ -15,13 +31,6 @@ const ScopeName = styled.code`
|
||||||
padding: 0.2em;
|
padding: 0.2em;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: ',';
|
|
||||||
}
|
|
||||||
&:last-child:after {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SecurityRequirementAndWrap = styled.span`
|
const SecurityRequirementAndWrap = styled.span`
|
||||||
|
@ -67,18 +76,26 @@ export class SecurityRequirement extends React.PureComponent<SecurityRequirement
|
||||||
const security = this.props.security;
|
const security = this.props.security;
|
||||||
return (
|
return (
|
||||||
<SecurityRequirementOrWrap>
|
<SecurityRequirementOrWrap>
|
||||||
{security.schemes.map(scheme => {
|
{security.schemes.length ? (
|
||||||
return (
|
security.schemes.map(scheme => {
|
||||||
<SecurityRequirementAndWrap key={scheme.id}>
|
return (
|
||||||
<Link to={scheme.sectionId}>{scheme.id}</Link>
|
<SecurityRequirementAndWrap key={scheme.id}>
|
||||||
{scheme.scopes.length > 0 && ' ('}
|
<Link to={scheme.sectionId}>{scheme.displayName}</Link>
|
||||||
{scheme.scopes.map(scope => (
|
{scheme.scopes.length > 0 && ' ('}
|
||||||
<ScopeName key={scope}>{scope}</ScopeName>
|
<ScopeNameList>
|
||||||
))}
|
{scheme.scopes.map(scope => (
|
||||||
{scheme.scopes.length > 0 && ') '}
|
<li key={scope}>
|
||||||
</SecurityRequirementAndWrap>
|
<ScopeName>{scope}</ScopeName>
|
||||||
);
|
</li>
|
||||||
})}
|
))}
|
||||||
|
</ScopeNameList>
|
||||||
|
{scheme.scopes.length > 0 && ') '}
|
||||||
|
</SecurityRequirementAndWrap>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<SecurityRequirementAndWrap>None</SecurityRequirementAndWrap>
|
||||||
|
)}
|
||||||
</SecurityRequirementOrWrap>
|
</SecurityRequirementOrWrap>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
<MiddlePanel>
|
<MiddlePanel>
|
||||||
<H2>
|
<H2>
|
||||||
<ShareLink to={scheme.sectionId} />
|
<ShareLink to={scheme.sectionId} />
|
||||||
{scheme.id}
|
{scheme.displayName}
|
||||||
</H2>
|
</H2>
|
||||||
<Markdown source={scheme.description || ''} />
|
<Markdown source={scheme.description || ''} />
|
||||||
<StyledMarkdownBlock>
|
<StyledMarkdownBlock>
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// import { observe } from 'mobx';
|
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
|
@ -8,6 +7,7 @@ import { shortenHTTPVerb } from '../../utils/openapi';
|
||||||
import { MenuItems } from './MenuItems';
|
import { MenuItems } from './MenuItems';
|
||||||
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
||||||
import { l } from '../../services/Labels';
|
import { l } from '../../services/Labels';
|
||||||
|
import { scrollIntoViewIfNeeded } from '../../utils';
|
||||||
|
|
||||||
export interface MenuItemProps {
|
export interface MenuItemProps {
|
||||||
item: IMenuItem;
|
item: IMenuItem;
|
||||||
|
@ -34,7 +34,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
|
|
||||||
scrollIntoViewIfActive() {
|
scrollIntoViewIfActive() {
|
||||||
if (this.props.item.active && this.ref.current) {
|
if (this.props.item.active && this.ref.current) {
|
||||||
this.ref.current.scrollIntoViewIfNeeded();
|
scrollIntoViewIfNeeded(this.ref.current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +46,8 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
||||||
) : (
|
) : (
|
||||||
<MenuItemLabel depth={item.depth} active={item.active} type={item.type} ref={this.ref}>
|
<MenuItemLabel depth={item.depth} active={item.active} type={item.type} ref={this.ref}>
|
||||||
<MenuItemTitle title={item.name}>
|
<MenuItemTitle title={item.sidebarLabel}>
|
||||||
{item.name}
|
{item.sidebarLabel}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</MenuItemTitle>
|
</MenuItemTitle>
|
||||||
{(item.depth > 0 && item.items.length > 0 && (
|
{(item.depth > 0 && item.items.length > 0 && (
|
||||||
|
@ -78,7 +78,7 @@ export class OperationMenuItemContent extends React.Component<OperationMenuItemC
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (this.props.item.active && this.ref.current) {
|
if (this.props.item.active && this.ref.current) {
|
||||||
this.ref.current.scrollIntoViewIfNeeded();
|
scrollIntoViewIfNeeded(this.ref.current);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ export class OperationMenuItemContent extends React.Component<OperationMenuItemC
|
||||||
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
||||||
)}
|
)}
|
||||||
<MenuItemTitle width="calc(100% - 38px)">
|
<MenuItemTitle width="calc(100% - 38px)">
|
||||||
{item.name}
|
{item.sidebarLabel}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</MenuItemTitle>
|
</MenuItemTitle>
|
||||||
</MenuItemLabel>
|
</MenuItemLabel>
|
||||||
|
|
|
@ -25,8 +25,8 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
||||||
>
|
>
|
||||||
<MenuItems items={store.items} onActivate={this.activate} root={true} />
|
<MenuItems items={store.items} onActivate={this.activate} root={true} />
|
||||||
<RedocAttribution>
|
<RedocAttribution>
|
||||||
<a target="_blank" rel="noopener noreferrer" href="https://github.com/Redocly/redoc">
|
<a target="_blank" rel="noopener noreferrer" href="https://redocly.com/redoc/">
|
||||||
Documentation Powered by ReDoc
|
Documentation Powered by Redocly
|
||||||
</a>
|
</a>
|
||||||
</RedocAttribution>
|
</RedocAttribution>
|
||||||
</PerfectScrollbarWrap>
|
</PerfectScrollbarWrap>
|
||||||
|
@ -37,7 +37,6 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
||||||
if (item && item.active && this.context.menuToggle) {
|
if (item && item.active && this.context.menuToggle) {
|
||||||
return item.expanded ? item.collapse() : item.expand();
|
return item.expanded ? item.collapse() : item.expand();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.menu.activateAndScroll(item, true);
|
this.props.menu.activateAndScroll(item, true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this._updateScroll) {
|
if (this._updateScroll) {
|
||||||
|
|
|
@ -66,11 +66,15 @@ export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function menuItemActiveBg(depth, { theme }: { theme: ResolvedThemeInterface }): string {
|
function menuItemActive(
|
||||||
|
depth,
|
||||||
|
{ theme }: { theme: ResolvedThemeInterface },
|
||||||
|
option: string,
|
||||||
|
): string {
|
||||||
if (depth > 1) {
|
if (depth > 1) {
|
||||||
return darken(0.1, theme.sidebar.backgroundColor);
|
return theme.sidebar.level1Items[option];
|
||||||
} else if (depth === 1) {
|
} else if (depth === 1) {
|
||||||
return darken(0.05, theme.sidebar.backgroundColor);
|
return theme.sidebar.groupItems[option];
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -102,17 +106,10 @@ export const menuItemDepth = {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
color: ${props => props.theme.sidebar.textColor};
|
|
||||||
`,
|
`,
|
||||||
1: css`
|
1: css`
|
||||||
font-size: 0.929em;
|
font-size: 0.929em;
|
||||||
text-transform: ${({ theme }) => theme.sidebar.level1Items.textTransform};
|
text-transform: ${({ theme }) => theme.sidebar.level1Items.textTransform};
|
||||||
&:hover {
|
|
||||||
color: ${props => props.theme.sidebar.activeTextColor};
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
2: css`
|
|
||||||
color: ${props => props.theme.sidebar.textColor};
|
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -131,7 +128,9 @@ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
|
||||||
}))<MenuItemLabelType>`
|
}))<MenuItemLabelType>`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${props =>
|
color: ${props =>
|
||||||
props.active ? props.theme.sidebar.activeTextColor : props.theme.sidebar.textColor};
|
props.active
|
||||||
|
? menuItemActive(props.depth, props, 'activeTextColor')
|
||||||
|
: props.theme.sidebar.textColor};
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 12.5px ${props => props.theme.spacing.unit * 4}px;
|
padding: 12.5px ${props => props.theme.spacing.unit * 4}px;
|
||||||
${({ depth, type, theme }) =>
|
${({ depth, type, theme }) =>
|
||||||
|
@ -140,12 +139,16 @@ export const MenuItemLabel = styled.label.attrs((props: MenuItemLabelType) => ({
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-family: ${props => props.theme.typography.headings.fontFamily};
|
font-family: ${props => props.theme.typography.headings.fontFamily};
|
||||||
${props => menuItemDepth[props.depth]};
|
${props => menuItemDepth[props.depth]};
|
||||||
background-color: ${props => (props.active ? menuItemActiveBg(props.depth, props) : '')};
|
background-color: ${props =>
|
||||||
|
props.active
|
||||||
|
? menuItemActive(props.depth, props, 'activeBackgroundColor')
|
||||||
|
: props.theme.sidebar.backgroundColor};
|
||||||
|
|
||||||
${props => (props.deprecated && deprecatedCss) || ''};
|
${props => (props.deprecated && deprecatedCss) || ''};
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${props => menuItemActiveBg(props.depth, props)};
|
color: ${props => menuItemActive(props.depth, props, 'activeTextColor')};
|
||||||
|
background-color: ${props => menuItemActive(props.depth, props, 'activeBackgroundColor')};
|
||||||
}
|
}
|
||||||
|
|
||||||
${ShelfIcon} {
|
${ShelfIcon} {
|
||||||
|
|
|
@ -56,7 +56,7 @@ const StyledStickySidebar = styled.div<{ open?: boolean }>`
|
||||||
const FloatingButton = styled.div`
|
const FloatingButton = styled.div`
|
||||||
outline: none;
|
outline: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
background-color: #f2f2f2;
|
background-color: ${({ theme }) => theme.fab.backgroundColor};
|
||||||
color: ${props => props.theme.colors.primary.main};
|
color: ${props => props.theme.colors.primary.main};
|
||||||
display: none;
|
display: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -74,6 +74,9 @@ const FloatingButton = styled.div`
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
|
svg {
|
||||||
|
color: ${({ theme }) => theme.fab.color};
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -140,9 +143,11 @@ export class StickyResponsiveSidebar extends React.Component<
|
||||||
>
|
>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</StyledStickySidebar>
|
</StyledStickySidebar>
|
||||||
<FloatingButton onClick={this.toggleNavMenu}>
|
{!this.context.hideFab && (
|
||||||
<AnimatedChevronButton open={open} />
|
<FloatingButton onClick={this.toggleNavMenu}>
|
||||||
</FloatingButton>
|
<AnimatedChevronButton open={open} />
|
||||||
|
</FloatingButton>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ const { Provider, Consumer } = StoreContext;
|
||||||
export { Provider as StoreProvider, Consumer as StoreConsumer, StoreContext };
|
export { Provider as StoreProvider, Consumer as StoreConsumer, StoreContext };
|
||||||
|
|
||||||
export function StoreBuilder(props: StoreBuilderProps) {
|
export function StoreBuilder(props: StoreBuilderProps) {
|
||||||
const {spec, specUrl, options, onLoaded, children } = props;
|
const { spec, specUrl, options, onLoaded, children } = props;
|
||||||
|
|
||||||
const [resolvedSpec, setResolvedSpec] = React.useState<any>(null);
|
const [resolvedSpec, setResolvedSpec] = React.useState<any>(null);
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ export function StoreBuilder(props: StoreBuilderProps) {
|
||||||
setResolvedSpec(resolved);
|
setResolvedSpec(resolved);
|
||||||
}
|
}
|
||||||
load();
|
load();
|
||||||
}, [spec, specUrl])
|
}, [spec, specUrl]);
|
||||||
|
|
||||||
const store = React.useMemo(() => {
|
const store = React.useMemo(() => {
|
||||||
if (!resolvedSpec) return null;
|
if (!resolvedSpec) return null;
|
||||||
|
@ -56,13 +56,14 @@ export function StoreBuilder(props: StoreBuilderProps) {
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [resolvedSpec, specUrl, options]);
|
}, [resolvedSpec, specUrl, options]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (store && onLoaded) {
|
if (store && onLoaded) {
|
||||||
onLoaded();
|
onLoaded();
|
||||||
}
|
}
|
||||||
}, [store, onLoaded])
|
}, [store, onLoaded]);
|
||||||
|
|
||||||
return children({
|
return children({
|
||||||
loading: !store,
|
loading: !store,
|
||||||
|
|
|
@ -26,7 +26,7 @@ describe('Components', () => {
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
const schemaViewElement = shallow(<Schema schema={schema} />).getElement();
|
const schemaViewElement = shallow(<Schema schema={schema} />).getElement();
|
||||||
expect(schemaViewElement.type).toEqual(ObjectSchema);
|
expect(schemaViewElement).toMatchSnapshot();
|
||||||
expect(schemaViewElement.props.discriminator).toBeDefined();
|
expect(schemaViewElement.props.discriminator).toBeDefined();
|
||||||
expect(schemaViewElement.props.discriminator.parentSchema).toBeDefined();
|
expect(schemaViewElement.props.discriminator.parentSchema).toBeDefined();
|
||||||
expect(schemaViewElement.props.discriminator.fieldName).toEqual('type');
|
expect(schemaViewElement.props.discriminator.fieldName).toEqual('type');
|
||||||
|
|
|
@ -6,18 +6,19 @@ import * as React from 'react';
|
||||||
import { OneOfSchema, Schema } from '../';
|
import { OneOfSchema, Schema } from '../';
|
||||||
import { OpenAPIParser, SchemaModel } from '../../services';
|
import { OpenAPIParser, SchemaModel } from '../../services';
|
||||||
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
||||||
|
import { withTheme } from '../testProviders';
|
||||||
|
|
||||||
const options = new RedocNormalizedOptions({});
|
const options = new RedocNormalizedOptions({});
|
||||||
describe('Components', () => {
|
describe('Components', () => {
|
||||||
describe('SchemaView', () => {
|
describe('SchemaView', () => {
|
||||||
|
const parser = new OpenAPIParser(
|
||||||
|
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
|
||||||
|
undefined,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
describe('OneOf', () => {
|
describe('OneOf', () => {
|
||||||
it('should pass down skipReadOnly/skipReadWrite to nested oneOf', () => {
|
it('should pass down skipReadOnly/skipReadWrite to nested oneOf', () => {
|
||||||
const parser = new OpenAPIParser(
|
|
||||||
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
|
|
||||||
undefined,
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
const schema = new SchemaModel(
|
const schema = new SchemaModel(
|
||||||
parser,
|
parser,
|
||||||
{ oneOf: [{ type: 'string' }, { type: 'integer' }] },
|
{ oneOf: [{ type: 'string' }, { type: 'integer' }] },
|
||||||
|
@ -38,5 +39,19 @@ describe('Components', () => {
|
||||||
expect(schemaViewElement.props.skipReadOnly).toBeTruthy();
|
expect(schemaViewElement.props.skipReadOnly).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('OneOf deprecated', () => {
|
||||||
|
const schema = new SchemaModel(
|
||||||
|
parser,
|
||||||
|
{ oneOf: [{ type: 'string', deprecated: true }, { type: 'integer' }] },
|
||||||
|
'',
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
it('should match snapshot', () => {
|
||||||
|
const component = shallow(withTheme(<Schema schema={schema} />));
|
||||||
|
expect(component.render()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
28
src/components/__tests__/SecurityRequirement.test.tsx
Normal file
28
src/components/__tests__/SecurityRequirement.test.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
|
||||||
|
import { OpenAPIParser } from '../../services';
|
||||||
|
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
|
||||||
|
import { SecurityRequirement } from '../SecurityRequirement/SecurityRequirement';
|
||||||
|
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
||||||
|
|
||||||
|
const options = new RedocNormalizedOptions({});
|
||||||
|
describe('Components', () => {
|
||||||
|
describe('SecurityRequirement', () => {
|
||||||
|
describe('SecurityRequirement', () => {
|
||||||
|
it("should render 'None' when empty object in security open api", () => {
|
||||||
|
const parser = new OpenAPIParser(
|
||||||
|
{ openapi: '3.0', info: { title: 'test', version: '0' }, paths: {} },
|
||||||
|
undefined,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
const securityRequirement = new SecurityRequirementModel({}, parser);
|
||||||
|
const securityElement = shallow(
|
||||||
|
<SecurityRequirement key={1} security={securityRequirement} />,
|
||||||
|
).getElement();
|
||||||
|
expect(securityElement.props.children.type.target).toEqual('span');
|
||||||
|
expect(securityElement.props.children.props.children).toEqual('None');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,53 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Components SchemaView OneOf deprecated should match snapshot 1`] = `
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="sc-kfYoZR juYXUf"
|
||||||
|
>
|
||||||
|
One of
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="sc-dlMDgC EoFth"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="sc-fKgJPI iEPbLk"
|
||||||
|
>
|
||||||
|
string
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="sc-fKgJPI bpjiHN"
|
||||||
|
>
|
||||||
|
integer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="sc-bqGGPW eSYQnm"
|
||||||
|
type="warning"
|
||||||
|
>
|
||||||
|
Deprecated
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<span
|
||||||
|
class="sc-fbIWvP sc-FRrlG CMpTe bBFKjV"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="sc-fbIWvP sc-fXazdy CMpTe gJKPGC"
|
||||||
|
>
|
||||||
|
string
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="sc-iBzEeX sc-cOifOu dFWqin hjSJYo"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -1,11 +1,11 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ThemeProvider } from 'styled-components';
|
import { ThemeProvider } from 'styled-components';
|
||||||
import defaultTheme from '../theme';
|
import defaultTheme, { resolveTheme } from '../theme';
|
||||||
|
|
||||||
export default class TestThemeProvider extends React.Component {
|
export default class TestThemeProvider extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={defaultTheme}>
|
<ThemeProvider theme={resolveTheme(defaultTheme)}>
|
||||||
{React.Children.only(this.props.children as any)}
|
{React.Children.only(this.props.children as any)}
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,6 +10,7 @@ export interface LabelsConfig {
|
||||||
arrayOf: string;
|
arrayOf: string;
|
||||||
webhook: string;
|
webhook: string;
|
||||||
const: string;
|
const: string;
|
||||||
|
noResultsFound: string;
|
||||||
download: string;
|
download: string;
|
||||||
downloadSpecification: string;
|
downloadSpecification: string;
|
||||||
responses: string;
|
responses: string;
|
||||||
|
@ -32,6 +33,7 @@ const labels: LabelsConfig = {
|
||||||
arrayOf: 'Array of ',
|
arrayOf: 'Array of ',
|
||||||
webhook: 'Event',
|
webhook: 'Event',
|
||||||
const: 'Value',
|
const: 'Value',
|
||||||
|
noResultsFound: 'No results found',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
downloadSpecification: 'Download OpenAPI specification',
|
downloadSpecification: 'Download OpenAPI specification',
|
||||||
responses: 'Responses',
|
responses: 'Responses',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as marked from 'marked';
|
import * as React from 'react';
|
||||||
|
import { marked } from 'marked';
|
||||||
|
|
||||||
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
import { highlight, safeSlugify, unescapeHTMLChars } from '../utils';
|
||||||
import { AppStore } from './AppStore';
|
import { AppStore } from './AppStore';
|
||||||
|
@ -56,10 +57,12 @@ export class MarkdownRenderer {
|
||||||
headings: MarkdownHeading[] = [];
|
headings: MarkdownHeading[] = [];
|
||||||
currentTopHeading: MarkdownHeading;
|
currentTopHeading: MarkdownHeading;
|
||||||
|
|
||||||
|
public parser: marked.Parser; // required initialization, `parser` is used by `marked.Renderer` instance under the hood
|
||||||
private headingEnhanceRenderer: marked.Renderer;
|
private headingEnhanceRenderer: marked.Renderer;
|
||||||
private originalHeadingRule: typeof marked.Renderer.prototype.heading;
|
private originalHeadingRule: typeof marked.Renderer.prototype.heading;
|
||||||
|
|
||||||
constructor(public options?: RedocNormalizedOptions) {
|
constructor(public options?: RedocNormalizedOptions) {
|
||||||
|
this.parser = new marked.Parser();
|
||||||
this.headingEnhanceRenderer = new marked.Renderer();
|
this.headingEnhanceRenderer = new marked.Renderer();
|
||||||
this.originalHeadingRule = this.headingEnhanceRenderer.heading.bind(
|
this.originalHeadingRule = this.headingEnhanceRenderer.heading.bind(
|
||||||
this.headingEnhanceRenderer,
|
this.headingEnhanceRenderer,
|
||||||
|
@ -98,7 +101,7 @@ export class MarkdownRenderer {
|
||||||
|
|
||||||
attachHeadingsDescriptions(rawText: string) {
|
attachHeadingsDescriptions(rawText: string) {
|
||||||
const buildRegexp = (heading: MarkdownHeading) => {
|
const buildRegexp = (heading: MarkdownHeading) => {
|
||||||
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}`);
|
return new RegExp(`##?\\s+${heading.name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}\s*\n`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const flatHeadings = this.flattenHeadings(this.headings);
|
const flatHeadings = this.flattenHeadings(this.headings);
|
||||||
|
@ -121,10 +124,7 @@ export class MarkdownRenderer {
|
||||||
prevRegexp = regexp;
|
prevRegexp = regexp;
|
||||||
prevPos = currentPos;
|
prevPos = currentPos;
|
||||||
}
|
}
|
||||||
prevHeading.description = rawText
|
prevHeading.description = rawText.substring(prevPos).replace(prevRegexp, '').trim();
|
||||||
.substring(prevPos)
|
|
||||||
.replace(prevRegexp, '')
|
|
||||||
.trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
headingRule = (
|
headingRule = (
|
||||||
|
@ -132,7 +132,7 @@ export class MarkdownRenderer {
|
||||||
level: 1 | 2 | 3 | 4 | 5 | 6,
|
level: 1 | 2 | 3 | 4 | 5 | 6,
|
||||||
raw: string,
|
raw: string,
|
||||||
slugger: marked.Slugger,
|
slugger: marked.Slugger,
|
||||||
) => {
|
): string => {
|
||||||
if (level === 1) {
|
if (level === 1) {
|
||||||
this.currentTopHeading = this.saveHeading(text, level);
|
this.currentTopHeading = this.saveHeading(text, level);
|
||||||
} else if (level === 2) {
|
} else if (level === 2) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import {
|
||||||
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
SECURITY_DEFINITIONS_COMPONENT_NAME,
|
||||||
setSecuritySchemePrefix,
|
setSecuritySchemePrefix,
|
||||||
JsonPointer,
|
JsonPointer,
|
||||||
|
alphabeticallyByProp,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||||
import { GroupModel, OperationModel } from './models';
|
import { GroupModel, OperationModel } from './models';
|
||||||
|
@ -130,9 +131,11 @@ export class MenuBuilder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of OperationsGroup items for the tags of the group or for all tags
|
* Returns array of OperationsGroup items for the tags of the group or for all tags
|
||||||
|
* @param parser
|
||||||
* @param tagsMap tags info returned from `getTagsWithOperations`
|
* @param tagsMap tags info returned from `getTagsWithOperations`
|
||||||
* @param parent parent item
|
* @param parent parent item
|
||||||
* @param group group which this tag belongs to. if not provided gets all tags
|
* @param group group which this tag belongs to. if not provided gets all tags
|
||||||
|
* @param options normalized options
|
||||||
*/
|
*/
|
||||||
static getTagsItems(
|
static getTagsItems(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
|
@ -183,14 +186,21 @@ export class MenuBuilder {
|
||||||
|
|
||||||
res.push(item);
|
res.push(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.sortTagsAlphabetically) {
|
||||||
|
res.sort(alphabeticallyByProp<GroupModel | OperationModel>('name'));
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns array of Operation items for the tag
|
* Returns array of Operation items for the tag
|
||||||
|
* @param parser
|
||||||
* @param parent parent OperationsGroup
|
* @param parent parent OperationsGroup
|
||||||
* @param tag tag info returned from `getTagsWithOperations`
|
* @param tag tag info returned from `getTagsWithOperations`
|
||||||
* @param depth items depth
|
* @param depth items depth
|
||||||
|
* @param options - normalized options
|
||||||
*/
|
*/
|
||||||
static getOperationsItems(
|
static getOperationsItems(
|
||||||
parser: OpenAPIParser,
|
parser: OpenAPIParser,
|
||||||
|
@ -209,6 +219,11 @@ export class MenuBuilder {
|
||||||
operation.depth = depth;
|
operation.depth = depth;
|
||||||
res.push(operation);
|
res.push(operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.sortOperationsAlphabetically) {
|
||||||
|
res.sort(alphabeticallyByProp<OperationModel>('name'));
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +241,7 @@ export class MenuBuilder {
|
||||||
getTags(parser, webhooks, true);
|
getTags(parser, webhooks, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec.paths){
|
if (spec.paths) {
|
||||||
getTags(parser, spec.paths);
|
getTags(parser, spec.paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { SpecStore } from './models';
|
||||||
import { history as historyInst, HistoryService } from './HistoryService';
|
import { history as historyInst, HistoryService } from './HistoryService';
|
||||||
import { ScrollService } from './ScrollService';
|
import { ScrollService } from './ScrollService';
|
||||||
|
|
||||||
import { flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
|
import { escapeHTMLAttrChars, flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
|
||||||
import { GROUP_DEPTH } from './MenuBuilder';
|
import { GROUP_DEPTH } from './MenuBuilder';
|
||||||
|
|
||||||
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
export type MenuItemGroupType = 'group' | 'tag' | 'section';
|
||||||
|
@ -16,6 +16,7 @@ export interface IMenuItem {
|
||||||
id: string;
|
id: string;
|
||||||
absoluteIdx?: number;
|
absoluteIdx?: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
sidebarLabel: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
depth: number;
|
depth: number;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
@ -46,7 +47,7 @@ export class MenuStore {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
|
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(id)}"]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -150,9 +151,9 @@ export class MenuStore {
|
||||||
} else {
|
} else {
|
||||||
if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) {
|
if (id.startsWith(SECURITY_SCHEMES_SECTION_PREFIX)) {
|
||||||
item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id));
|
item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id));
|
||||||
this.activate(item);
|
this.activateAndScroll(item, false);
|
||||||
}
|
}
|
||||||
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
|
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(id)}"]`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -162,7 +163,7 @@ export class MenuStore {
|
||||||
*/
|
*/
|
||||||
getElementAt(idx: number): Element | null {
|
getElementAt(idx: number): Element | null {
|
||||||
const item = this.flatItems[idx];
|
const item = this.flatItems[idx];
|
||||||
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
|
return (item && querySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(item.id)}"]`)) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,7 +175,7 @@ export class MenuStore {
|
||||||
if (item && item.type === 'group') {
|
if (item && item.type === 'group') {
|
||||||
item = item.items[0];
|
item = item.items[0];
|
||||||
}
|
}
|
||||||
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
|
return (item && querySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(item.id)}"]`)) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -210,6 +211,7 @@ export class MenuStore {
|
||||||
|
|
||||||
this.deactivate(this.activeItem);
|
this.deactivate(this.activeItem);
|
||||||
if (!item) {
|
if (!item) {
|
||||||
|
this.activeItemIdx = -1;
|
||||||
this.history.replace('', rewriteHistory);
|
this.history.replace('', rewriteHistory);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -222,7 +224,7 @@ export class MenuStore {
|
||||||
|
|
||||||
this.activeItemIdx = item.absoluteIdx!;
|
this.activeItemIdx = item.absoluteIdx!;
|
||||||
if (updateLocation) {
|
if (updateLocation) {
|
||||||
this.history.replace(item.id, rewriteHistory);
|
this.history.replace(encodeURI(item.id), rewriteHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.activate();
|
item.activate();
|
||||||
|
|
|
@ -174,16 +174,34 @@ export class OpenAPIParser {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shallowDeref<T extends unknown>(obj: OpenAPIRef | T): T {
|
||||||
|
if (this.isRef(obj)) {
|
||||||
|
const schemaName = getDefinitionName(obj.$ref);
|
||||||
|
if (schemaName && this.options.ignoreNamedSchemas.has(schemaName)) {
|
||||||
|
return { type: 'object', title: schemaName } as T;
|
||||||
|
}
|
||||||
|
const resolved = this.byRef<T>(obj.$ref);
|
||||||
|
return this.allowMergeRefs ? this.mergeRefs(obj, resolved, false) : (resolved as T);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
mergeRefs(ref, resolved, mergeAsAllOf: boolean) {
|
mergeRefs(ref, resolved, mergeAsAllOf: boolean) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { $ref, ...rest } = ref;
|
const { $ref, ...rest } = ref;
|
||||||
const keys = Object.keys(rest);
|
const keys = Object.keys(rest);
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
|
if (this.isRef(resolved)) {
|
||||||
|
return this.shallowDeref(resolved);
|
||||||
|
}
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
if (mergeAsAllOf && keys.some((k) => k !== 'description' && k !== 'title' && k !== 'externalDocs')) {
|
if (
|
||||||
|
mergeAsAllOf &&
|
||||||
|
keys.some(k => k !== 'description' && k !== 'title' && k !== 'externalDocs')
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
allOf: [resolved, rest],
|
allOf: [rest, resolved],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// small optimization
|
// small optimization
|
||||||
|
@ -194,13 +212,6 @@ export class OpenAPIParser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shalowDeref<T extends object>(obj: OpenAPIRef | T): T {
|
|
||||||
if (this.isRef(obj)) {
|
|
||||||
return this.byRef<T>(obj.$ref)!;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge allOf constraints.
|
* Merge allOf constraints.
|
||||||
* @param schema schema with allOF
|
* @param schema schema with allOF
|
||||||
|
@ -239,7 +250,7 @@ export class OpenAPIParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
const allOfSchemas = schema.allOf
|
const allOfSchemas = schema.allOf
|
||||||
.map((subSchema) => {
|
.map(subSchema => {
|
||||||
if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
|
if (subSchema && subSchema.$ref && used$Refs.has(subSchema.$ref)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
@ -253,7 +264,7 @@ export class OpenAPIParser {
|
||||||
schema: subMerged,
|
schema: subMerged,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((child) => child !== undefined) as Array<{
|
.filter(child => child !== undefined) as Array<{
|
||||||
$ref: string | undefined;
|
$ref: string | undefined;
|
||||||
schema: MergedOpenAPISchema;
|
schema: MergedOpenAPISchema;
|
||||||
}>;
|
}>;
|
||||||
|
@ -332,7 +343,7 @@ export class OpenAPIParser {
|
||||||
const def = this.deref(schemas[defName]);
|
const def = this.deref(schemas[defName]);
|
||||||
if (
|
if (
|
||||||
def.allOf !== undefined &&
|
def.allOf !== undefined &&
|
||||||
def.allOf.find((obj) => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
|
def.allOf.find(obj => obj.$ref !== undefined && $refs.indexOf(obj.$ref) > -1)
|
||||||
) {
|
) {
|
||||||
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
|
res['#/components/schemas/' + defName] = [def['x-discriminator-value'] || defName];
|
||||||
}
|
}
|
||||||
|
@ -358,7 +369,7 @@ export class OpenAPIParser {
|
||||||
const beforeAllOf = allOf.slice(0, i);
|
const beforeAllOf = allOf.slice(0, i);
|
||||||
const afterAllOf = allOf.slice(i + 1);
|
const afterAllOf = allOf.slice(i + 1);
|
||||||
return {
|
return {
|
||||||
oneOf: sub.oneOf.map((part) => {
|
oneOf: sub.oneOf.map(part => {
|
||||||
const merged = this.mergeAllOf({
|
const merged = this.mergeAllOf({
|
||||||
allOf: [...beforeAllOf, part, ...afterAllOf],
|
allOf: [...beforeAllOf, part, ...afterAllOf],
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,12 @@ import { isNumeric, mergeObjects } from '../utils/helpers';
|
||||||
import { LabelsConfigRaw, setRedocLabels } from './Labels';
|
import { LabelsConfigRaw, setRedocLabels } from './Labels';
|
||||||
import { MDXComponentMeta } from './MarkdownRenderer';
|
import { MDXComponentMeta } from './MarkdownRenderer';
|
||||||
|
|
||||||
|
export enum SideNavStyleEnum {
|
||||||
|
SummaryOnly = 'summary-only',
|
||||||
|
PathOnly = 'path-only',
|
||||||
|
IdOnly = 'id-only',
|
||||||
|
}
|
||||||
|
|
||||||
export interface RedocRawOptions {
|
export interface RedocRawOptions {
|
||||||
theme?: ThemeInterface;
|
theme?: ThemeInterface;
|
||||||
scrollYOffset?: number | string | (() => number);
|
scrollYOffset?: number | string | (() => number);
|
||||||
|
@ -13,6 +19,8 @@ export interface RedocRawOptions {
|
||||||
requiredPropsFirst?: boolean | string;
|
requiredPropsFirst?: boolean | string;
|
||||||
sortPropsAlphabetically?: boolean | string;
|
sortPropsAlphabetically?: boolean | string;
|
||||||
sortEnumValuesAlphabetically?: boolean | string;
|
sortEnumValuesAlphabetically?: boolean | string;
|
||||||
|
sortOperationsAlphabetically?: boolean | string;
|
||||||
|
sortTagsAlphabetically?: boolean | string;
|
||||||
noAutoAuth?: boolean | string;
|
noAutoAuth?: boolean | string;
|
||||||
nativeScrollbars?: boolean | string;
|
nativeScrollbars?: boolean | string;
|
||||||
pathInMiddlePanel?: boolean | string;
|
pathInMiddlePanel?: boolean | string;
|
||||||
|
@ -23,6 +31,7 @@ export interface RedocRawOptions {
|
||||||
disableSearch?: boolean | string;
|
disableSearch?: boolean | string;
|
||||||
onlyRequiredInSamples?: boolean | string;
|
onlyRequiredInSamples?: boolean | string;
|
||||||
showExtensions?: boolean | string | string[];
|
showExtensions?: boolean | string | string[];
|
||||||
|
sideNavStyle?: SideNavStyleEnum;
|
||||||
hideSingleRequestSampleTab?: boolean | string;
|
hideSingleRequestSampleTab?: boolean | string;
|
||||||
menuToggle?: boolean | string;
|
menuToggle?: boolean | string;
|
||||||
jsonSampleExpandLevel?: number | string | 'all';
|
jsonSampleExpandLevel?: number | string | 'all';
|
||||||
|
@ -30,6 +39,8 @@ export interface RedocRawOptions {
|
||||||
simpleOneOfTypeLabel?: boolean | string;
|
simpleOneOfTypeLabel?: boolean | string;
|
||||||
payloadSampleIdx?: number;
|
payloadSampleIdx?: number;
|
||||||
expandSingleSchemaField?: boolean | string;
|
expandSingleSchemaField?: boolean | string;
|
||||||
|
schemaExpansionLevel?: number | string | 'all';
|
||||||
|
showObjectSchemaExamples?: boolean | string;
|
||||||
|
|
||||||
unstable_ignoreMimeParameters?: boolean;
|
unstable_ignoreMimeParameters?: boolean;
|
||||||
|
|
||||||
|
@ -43,14 +54,17 @@ export interface RedocRawOptions {
|
||||||
maxDisplayedEnumValues?: number;
|
maxDisplayedEnumValues?: number;
|
||||||
ignoreNamedSchemas?: string[] | string;
|
ignoreNamedSchemas?: string[] | string;
|
||||||
hideSchemaPattern?: boolean;
|
hideSchemaPattern?: boolean;
|
||||||
|
generatedPayloadSamplesMaxDepth?: number;
|
||||||
|
nonce?: string;
|
||||||
|
hideFab?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
|
export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
|
||||||
if (val === undefined) {
|
if (val === undefined) {
|
||||||
return defaultValue || false;
|
return defaultValue || false;
|
||||||
}
|
}
|
||||||
if (typeof val === 'string') {
|
if (typeof val === 'string') {
|
||||||
return val === 'false' ? false : true;
|
return val !== 'false';
|
||||||
}
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
@ -65,6 +79,12 @@ function argValueToNumber(value: number | string | undefined): number | undefine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function argValueToExpandLevel(value?: number | string | undefined, defaultValue = 0): number {
|
||||||
|
if (value === 'all') return Infinity;
|
||||||
|
|
||||||
|
return argValueToNumber(value) || defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
export class RedocNormalizedOptions {
|
export class RedocNormalizedOptions {
|
||||||
static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) {
|
static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) {
|
||||||
if (value === 'all') {
|
if (value === 'all') {
|
||||||
|
@ -72,7 +92,7 @@ export class RedocNormalizedOptions {
|
||||||
}
|
}
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
const res = {};
|
const res = {};
|
||||||
value.split(',').forEach((code) => {
|
value.split(',').forEach(code => {
|
||||||
res[code.trim()] = true;
|
res[code.trim()] = true;
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
|
@ -138,7 +158,25 @@ export class RedocNormalizedOptions {
|
||||||
case 'false':
|
case 'false':
|
||||||
return false;
|
return false;
|
||||||
default:
|
default:
|
||||||
return value.split(',').map((ext) => ext.trim());
|
return value.split(',').map(ext => ext.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static normalizeSideNavStyle(value: RedocRawOptions['sideNavStyle']): SideNavStyleEnum {
|
||||||
|
const defaultValue = SideNavStyleEnum.SummaryOnly;
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case defaultValue:
|
||||||
|
return value;
|
||||||
|
case SideNavStyleEnum.PathOnly:
|
||||||
|
return SideNavStyleEnum.PathOnly;
|
||||||
|
case SideNavStyleEnum.IdOnly:
|
||||||
|
return SideNavStyleEnum.IdOnly;
|
||||||
|
default:
|
||||||
|
return defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,6 +202,16 @@ export class RedocNormalizedOptions {
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static normalizeGeneratedPayloadSamplesMaxDepth(
|
||||||
|
value?: number | string | undefined,
|
||||||
|
): number {
|
||||||
|
if (!isNaN(Number(value))) {
|
||||||
|
return Math.max(0, Number(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 10;
|
||||||
|
}
|
||||||
|
|
||||||
theme: ResolvedThemeInterface;
|
theme: ResolvedThemeInterface;
|
||||||
scrollYOffset: () => number;
|
scrollYOffset: () => number;
|
||||||
hideHostname: boolean;
|
hideHostname: boolean;
|
||||||
|
@ -171,6 +219,8 @@ export class RedocNormalizedOptions {
|
||||||
requiredPropsFirst: boolean;
|
requiredPropsFirst: boolean;
|
||||||
sortPropsAlphabetically: boolean;
|
sortPropsAlphabetically: boolean;
|
||||||
sortEnumValuesAlphabetically: boolean;
|
sortEnumValuesAlphabetically: boolean;
|
||||||
|
sortOperationsAlphabetically: boolean;
|
||||||
|
sortTagsAlphabetically: boolean;
|
||||||
noAutoAuth: boolean;
|
noAutoAuth: boolean;
|
||||||
nativeScrollbars: boolean;
|
nativeScrollbars: boolean;
|
||||||
pathInMiddlePanel: boolean;
|
pathInMiddlePanel: boolean;
|
||||||
|
@ -180,6 +230,7 @@ export class RedocNormalizedOptions {
|
||||||
disableSearch: boolean;
|
disableSearch: boolean;
|
||||||
onlyRequiredInSamples: boolean;
|
onlyRequiredInSamples: boolean;
|
||||||
showExtensions: boolean | string[];
|
showExtensions: boolean | string[];
|
||||||
|
sideNavStyle: SideNavStyleEnum;
|
||||||
hideSingleRequestSampleTab: boolean;
|
hideSingleRequestSampleTab: boolean;
|
||||||
menuToggle: boolean;
|
menuToggle: boolean;
|
||||||
jsonSampleExpandLevel: number;
|
jsonSampleExpandLevel: number;
|
||||||
|
@ -188,6 +239,8 @@ export class RedocNormalizedOptions {
|
||||||
simpleOneOfTypeLabel: boolean;
|
simpleOneOfTypeLabel: boolean;
|
||||||
payloadSampleIdx: number;
|
payloadSampleIdx: number;
|
||||||
expandSingleSchemaField: boolean;
|
expandSingleSchemaField: boolean;
|
||||||
|
schemaExpansionLevel: number;
|
||||||
|
showObjectSchemaExamples: boolean;
|
||||||
|
|
||||||
/* tslint:disable-next-line */
|
/* tslint:disable-next-line */
|
||||||
unstable_ignoreMimeParameters: boolean;
|
unstable_ignoreMimeParameters: boolean;
|
||||||
|
@ -198,6 +251,10 @@ export class RedocNormalizedOptions {
|
||||||
|
|
||||||
ignoreNamedSchemas: Set<string>;
|
ignoreNamedSchemas: Set<string>;
|
||||||
hideSchemaPattern: boolean;
|
hideSchemaPattern: boolean;
|
||||||
|
generatedPayloadSamplesMaxDepth: number;
|
||||||
|
hideFab: boolean;
|
||||||
|
|
||||||
|
nonce?: string;
|
||||||
|
|
||||||
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
constructor(raw: RedocRawOptions, defaults: RedocRawOptions = {}) {
|
||||||
raw = { ...defaults, ...raw };
|
raw = { ...defaults, ...raw };
|
||||||
|
@ -229,6 +286,8 @@ export class RedocNormalizedOptions {
|
||||||
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
||||||
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
|
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
|
||||||
this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically);
|
this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically);
|
||||||
|
this.sortOperationsAlphabetically = argValueToBoolean(raw.sortOperationsAlphabetically);
|
||||||
|
this.sortTagsAlphabetically = argValueToBoolean(raw.sortTagsAlphabetically);
|
||||||
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
|
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
|
||||||
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
||||||
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
||||||
|
@ -238,6 +297,7 @@ export class RedocNormalizedOptions {
|
||||||
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
||||||
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
||||||
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
||||||
|
this.sideNavStyle = RedocNormalizedOptions.normalizeSideNavStyle(raw.sideNavStyle);
|
||||||
this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab);
|
this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab);
|
||||||
this.menuToggle = argValueToBoolean(raw.menuToggle, true);
|
this.menuToggle = argValueToBoolean(raw.menuToggle, true);
|
||||||
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
||||||
|
@ -248,6 +308,8 @@ export class RedocNormalizedOptions {
|
||||||
this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel);
|
this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel);
|
||||||
this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx);
|
this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx);
|
||||||
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
|
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
|
||||||
|
this.schemaExpansionLevel = argValueToExpandLevel(raw.schemaExpansionLevel);
|
||||||
|
this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples);
|
||||||
|
|
||||||
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);
|
||||||
|
|
||||||
|
@ -257,8 +319,14 @@ export class RedocNormalizedOptions {
|
||||||
this.maxDisplayedEnumValues = argValueToNumber(raw.maxDisplayedEnumValues);
|
this.maxDisplayedEnumValues = argValueToNumber(raw.maxDisplayedEnumValues);
|
||||||
const ignoreNamedSchemas = Array.isArray(raw.ignoreNamedSchemas)
|
const ignoreNamedSchemas = Array.isArray(raw.ignoreNamedSchemas)
|
||||||
? raw.ignoreNamedSchemas
|
? raw.ignoreNamedSchemas
|
||||||
: raw.ignoreNamedSchemas?.split(',').map((s) => s.trim());
|
: raw.ignoreNamedSchemas?.split(',').map(s => s.trim());
|
||||||
this.ignoreNamedSchemas = new Set(ignoreNamedSchemas);
|
this.ignoreNamedSchemas = new Set(ignoreNamedSchemas);
|
||||||
this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern);
|
this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern);
|
||||||
|
this.generatedPayloadSamplesMaxDepth =
|
||||||
|
RedocNormalizedOptions.normalizeGeneratedPayloadSamplesMaxDepth(
|
||||||
|
raw.generatedPayloadSamplesMaxDepth,
|
||||||
|
);
|
||||||
|
this.nonce = raw.nonce;
|
||||||
|
this.hideFab = argValueToBoolean(raw.hideFab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ export class SearchStore<T> {
|
||||||
|
|
||||||
fromExternalJS(path?: string, exportName?: string) {
|
fromExternalJS(path?: string, exportName?: string) {
|
||||||
if (path && exportName) {
|
if (path && exportName) {
|
||||||
this.searchWorker.fromExternalJS(path, exportName)
|
this.searchWorker.fromExternalJS(path, exportName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user