Merge remote-tracking branch 'upstream/main'

# Conflicts:
#	src/components/RedocStandalone.tsx
#	src/services/models/Field.ts
#	src/services/models/Schema.ts
This commit is contained in:
Sven Depickere 2022-11-26 20:20:46 +01:00
commit fbda09e3b1
169 changed files with 8961 additions and 2901 deletions

2
.github/CODEOWNERS vendored Normal file
View File

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

View File

@ -14,7 +14,7 @@ Hi! We're really excited that you are interested in contributing to ReDoc. Befor
## Pull Request Guidelines
Before submitting a pull request, please make sure the following is done:
1. Fork the repository and create your branch from master.
1. Fork the repository and create your branch from main.
2. Run `npm install` in the repository root.
3. If youve fixed a bug or added code that should be tested, add tests!
4. Ensure the test suite passes (`npm test`). Tip: `npm test -- --watch TestName` is helpful in development.

View File

@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'type: bug'
labels: 'Type: Bug'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
labels: 'Type: Enhancement'
assignees: ''
---
@ -17,4 +17,4 @@ A clear and concise description of what you want to happen.
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.
Add any other context or screenshots about the feature request here.

View File

@ -6,7 +6,7 @@ jobs:
build-and-e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: npm ci
- run: npm run bundle
- run: npm run e2e
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- run: npm run bundle
- run: npm run e2e

View File

@ -3,43 +3,77 @@ on:
release:
types: [published]
jobs:
push_to_registry:
name: Push Docker image to GitHub Packages
# 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@v3
# - 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},${DOCKER_IMAGE}:latest"
# 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@v3
# with:
# context: ./cli
# push: ${{ github.event_name != 'pull_request' }}
# tags: ${{ steps.prep.outputs.tags }}
dockerhub:
name: Publish redoc image to DockerHub
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
- name: Checkout
uses: actions/checkout@v3
- name: Docker meta
id: docker_meta
uses: crazy-max/ghaction-docker-meta@v1
with:
images: redocly/redoc
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
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
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: ./cli
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.prep.outputs.tags }}
context: .
file: ./config/docker/Dockerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.docker_meta.outputs.tags }}
labels: ${{ steps.docker_meta.outputs.labels }}

View File

@ -3,7 +3,7 @@ name: Publish cli
on:
push:
branches:
- master
- main
jobs:
bundle:
@ -11,20 +11,20 @@ jobs:
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
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 ci && npm ci --prefix cli
- run: npm run bundle
- name: Store bundle artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: bundles-cli
path: bundles
@ -34,17 +34,17 @@ jobs:
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- run: npm test
e2e-tests:
needs: [bundle]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- name: Download bundled artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: bundles-cli
path: bundles
@ -54,10 +54,10 @@ jobs:
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
@ -65,11 +65,11 @@ jobs:
npm-${{ hashFiles('package-lock.json') }}
npm-
- name: Install dependencies
run: npm ci
run: npm ci && npm ci --prefix cli
- name: Bundle
run: npm run compile:cli
- name: Store bundle artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: cli
path: cli
@ -81,9 +81,9 @@ jobs:
changed: ${{ steps.check.outputs.changed }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v3
- name: Check if version has been updated
id: check
uses: EndBug/version-check@v2.0.1
@ -96,18 +96,18 @@ jobs:
if: needs.check-version-cli.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download cli bundled artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: cli
path: cli
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}

View File

@ -9,20 +9,20 @@ jobs:
bundle:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
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 ci && npm ci --prefix cli
- run: npm run bundle
- name: Store bundle artifact
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
with:
name: bundles
path: bundles
@ -30,62 +30,38 @@ jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- run: npm test
e2e-tests:
needs: [bundle]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- name: Download bundled artifact
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
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:
name: Publish to NPM
needs: [bundle, unit-tests, e2e-tests]
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v1
- uses: actions/setup-node@v3
with:
node-version: '14.x'
registry-url: 'https://registry.npmjs.org'
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download bundled artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v3
with:
name: bundles
path: bundles
- name: Cache node modules
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS
key: npm-${{ hashFiles('package-lock.json') }}
@ -98,3 +74,40 @@ jobs:
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-cdn:
name: Publish to CDN
needs: [bundle, unit-tests, e2e-tests]
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Configure AWS
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: Download all artifact
uses: actions/download-artifact@v3
- name: Publish to S3
run: npm run publish-cdn
invalidate-cache:
name: Clear cache
runs-on: ubuntu-latest
needs: [publish, publish-cdn]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Configure AWS
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: Invalidate cache
run: ./scripts/invalidate-cache.sh
shell: bash
env:
DISTRIBUTION: ${{ secrets.DISTRIBUTION }}

View File

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

View File

@ -6,7 +6,7 @@ jobs:
build-and-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: npm ci
- run: npm run bundle
- run: npm test
- uses: actions/checkout@v3
- run: npm ci && npm ci --prefix cli
- run: npm run bundle
- run: npm test

1
.gitignore vendored
View File

@ -28,6 +28,7 @@ bundles/
typings/*
!typings/styled-patch.d.ts
cli/index.js
cli/__test__/*/**/*.html
/benchmark/revisions

View File

@ -1,3 +1,185 @@
# [2.0.0](https://github.com/Redocly/redoc/compare/v2.0.0-rc.77...v2.0.0) (2022-09-12)
# [2.0.0-rc.77](https://github.com/Redocly/redoc/compare/v2.0.0-rc.76...v2.0.0-rc.77) (2022-09-06)
### Bug Fixes
* add hard limit on deref depth to prevent crashes ([ddde105](https://github.com/Redocly/redoc/commit/ddde105acaf0a77b0bb5d13df5fd6180bc8169e9))
* do not use discriminator when specific schema was referenced in oneOf or anyOf ([#2153](https://github.com/Redocly/redoc/issues/2153)) ([6ac1e1e](https://github.com/Redocly/redoc/commit/6ac1e1eb183e97e2cd67ad14d8a39fac8289ebcc))
* hoistOneOf missing refs stack and improve allOf for same $ref ([bb325d0](https://github.com/Redocly/redoc/commit/bb325d0d285c4cf4ee7c6d70878d2dd0dc9c6ed7))
* latest docker cli tag ([#2140](https://github.com/Redocly/redoc/issues/2140)) ([8dc03eb](https://github.com/Redocly/redoc/commit/8dc03eb7ed262d6b1d460425ce43990710470845))
* markdown parent name ([#2062](https://github.com/Redocly/redoc/issues/2062)) ([da9ed0b](https://github.com/Redocly/redoc/commit/da9ed0b4d1a4070d326ecb472459f0ff916c6036))
### Features
* feet: search feature to support path ([#2145](https://github.com/Redocly/redoc/issues/2145)) ([c52ee83f](https://github.com/Redocly/redoc/commit/c52ee83f77ccfc79137c85deafe8d93e68465d45))
# [2.0.0-rc.76](https://github.com/Redocly/redoc/compare/v2.0.0-rc.75...v2.0.0-rc.76) (2022-08-18)
### Bug Fixes
* "API Docs By Redocly" overlapping last element in sidebar ([#2132](https://github.com/Redocly/redoc/issues/2132)) ([c60c6f5](https://github.com/Redocly/redoc/commit/c60c6f58917563d57c0eef650b9dfcece2e15049))
* encoding issue in CDN responses ([#2130](https://github.com/Redocly/redoc/issues/2130)) ([7816902](https://github.com/Redocly/redoc/commit/781690284a45b2b8af9eb525757632d0d19ef453))
* Optional authentication not rendered properly ([#2117](https://github.com/Redocly/redoc/issues/2117)) ([#2134](https://github.com/Redocly/redoc/issues/2134)) ([efd5e09](https://github.com/Redocly/redoc/commit/efd5e09c907b36a3999f4c9c3165b6b2bdc1d536))
### Features
* add clear cache for publish action ([#2129](https://github.com/Redocly/redoc/issues/2129)) ([d8093e3](https://github.com/Redocly/redoc/commit/d8093e3e2086874242eac82ddd202f35d5b8d558))
# [2.0.0-rc.75](https://github.com/Redocly/redoc/compare/v2.0.0-rc.74...v2.0.0-rc.75) (2022-08-10)
### Bug Fixes
* duplication of title ([#2119](https://github.com/Redocly/redoc/issues/2119)) ([40ebfd2](https://github.com/Redocly/redoc/commit/40ebfd2d63758b37665e2e4447732f671811e2a5))
* handle error if security scopes is invalid ([#2113](https://github.com/Redocly/redoc/issues/2113)) ([428fd69](https://github.com/Redocly/redoc/commit/428fd6983dc257f524121d98aeb1c58b39cf81f7))
* publishing docker image to github packages ([#2115](https://github.com/Redocly/redoc/issues/2115)) ([250f6d1](https://github.com/Redocly/redoc/commit/250f6d12b2d31d2166990bd9cb83ca1c63509686))
* Redocly logo ([#2109](https://github.com/Redocly/redoc/issues/2109)) ([a35bb3f](https://github.com/Redocly/redoc/commit/a35bb3ff26bf10b0e54383222df283800d6ee2c8))
* search and navigate error ([cfd810f](https://github.com/Redocly/redoc/commit/cfd810fdf9d37862e07458fa1c3c04046e22f315))
* sibling for openapi 3.1 ([#2112](https://github.com/Redocly/redoc/issues/2112)) ([0b1a790](https://github.com/Redocly/redoc/commit/0b1a79009010f0640a3030093b7c0dcf8caa49e4))
### Features
* add notification about new version available ([#2100](https://github.com/Redocly/redoc/issues/2100)) ([d6ca8cc](https://github.com/Redocly/redoc/commit/d6ca8cc53b9667f09ce8fef88dfac1039c562b78))
# [2.0.0-rc.74](https://github.com/Redocly/redoc/compare/v2.0.0-rc.73...v2.0.0-rc.74) (2022-07-28)
### Bug Fixes
* invalid url when href is empty ([#2105](https://github.com/Redocly/redoc/issues/2105)) ([e5f0235](https://github.com/Redocly/redoc/commit/e5f02359851a3797283ee513d734ab8e27266b92))
# [2.0.0-rc.73](https://github.com/Redocly/redoc/compare/v2.0.0-rc.72...v2.0.0-rc.73) (2022-07-28)
### Bug Fixes
* add label API docs by Redocly ([#2099](https://github.com/Redocly/redoc/issues/2099)) ([dcdab83](https://github.com/Redocly/redoc/commit/dcdab838903a5d923c5e327d07d7743214769a61))
* add the latest tag for the CLI docker image ([#2087](https://github.com/Redocly/redoc/issues/2087)) ([80ecd0f](https://github.com/Redocly/redoc/commit/80ecd0f19746379b056bfb1b11950693f3dc3724))
* correct URLs of OperationModel servers for static site generation ([#2081](https://github.com/Redocly/redoc/issues/2081)) ([b1afd08](https://github.com/Redocly/redoc/commit/b1afd08bcf83770b537ed1eb9c90341de0162a1c))
* enum duplication values when schema uses a specific combination of oneOf and allOf([#2088](https://github.com/Redocly/redoc/issues/2088)) ([e411847](https://github.com/Redocly/redoc/commit/e4118479f69209c5dd09a2be0e978834dcd9eb8f))
* highlight text syntax ([#2069](https://github.com/Redocly/redoc/issues/2069)) ([4fc6aa0](https://github.com/Redocly/redoc/commit/4fc6aa0859c94e25fd30c4a4250455e44cc76488))
* merge reference for openapi 3.1 ([#2063](https://github.com/Redocly/redoc/issues/2063)) ([87541e4](https://github.com/Redocly/redoc/commit/87541e45dc2526696deb32a6350a14a44a709b54))
* nested patternProperties ([#2073](https://github.com/Redocly/redoc/issues/2073)) ([9920991](https://github.com/Redocly/redoc/commit/99209910806b85289a89fb3131049ed79118bc72))
* operation url in static page ([#2093](https://github.com/Redocly/redoc/issues/2093)) ([98eec19](https://github.com/Redocly/redoc/commit/98eec19647b63f3598ec30fdeb428f614cf93ad4))
* property with nested allOf ([#2083](https://github.com/Redocly/redoc/issues/2083)) ([7cc0500](https://github.com/Redocly/redoc/commit/7cc0500f3c1ddd1da17ee31278468207093f9281))
* recursion for boolean items ([#2097](https://github.com/Redocly/redoc/issues/2097)) ([a5804db](https://github.com/Redocly/redoc/commit/a5804db1ce60ee6d90db8a3b54138eb1ca420c6f))
* resolve dependency conflict in installing ([#2060](https://github.com/Redocly/redoc/issues/2060)) ([e26c8b2](https://github.com/Redocly/redoc/commit/e26c8b23d9b36abd5572bd0fe350d74a5cf65afb))
* restore old variant security injections ([#2075](https://github.com/Redocly/redoc/issues/2075)) ([1a1bc26](https://github.com/Redocly/redoc/commit/1a1bc26503c06b6a7022289e5b9353bd59e48a9a))
* rewrite recursive checks ([#2072](https://github.com/Redocly/redoc/issues/2072)) ([2970f95](https://github.com/Redocly/redoc/commit/2970f959cfa31cb4d5288ca23ca05cd34357dcec))
* Scrolling keeps rewriting url after a Redoc element was removed [#2051](https://github.com/Redocly/redoc/issues/2051) ([#2085](https://github.com/Redocly/redoc/issues/2085)) ([0045be0](https://github.com/Redocly/redoc/commit/0045be0b753b8fb7d8d58a4e511783a6ba858444))
* mis-nesting of aria roles on sidebar navigation ([#2050](https://github.com/Redocly/redoc/issues/2050)) ([7ca10da](https://github.com/Redocly/redoc/commit/7ca10daf12f2cac9fecf559b11f0f0c8bd21ae43))
* 404 on the documentation page ([#2092](https://github.com/Redocly/redoc/issues/2050)) ([17bb08](https://github.com/Redocly/redoc/commit/17bb08909a1734e6e59c83ce29f31ae7cf6fc784))
# [2.0.0-rc.72](https://github.com/Redocly/redoc/compare/v2.0.0-rc.71...v2.0.0-rc.72) (2022-06-02)
### Bug Fixes
* handled style change in ServerUrl and ServersOverlay dynamically ([#1989](https://github.com/Redocly/redoc/issues/1989)) ([a366de4](https://github.com/Redocly/redoc/commit/a366de4cf67fb94baa33b7b5c311cc1f54a63e53))
* nested items with refs ([#2035](https://github.com/Redocly/redoc/issues/2035)) ([51127aa](https://github.com/Redocly/redoc/commit/51127aadc3e6b0f8e4066afb1c3b2ea6db453da2))
# [2.0.0-rc.71](https://github.com/Redocly/redoc/compare/v2.0.0-rc.70...v2.0.0-rc.71) (2022-05-31)
### Bug Fixes
* constraints label details ([eb0917d](https://github.com/Redocly/redoc/commit/eb0917d002e57353027fee9c8f07605de8f1ff6f))
* merge allOf in correct order ([#2020](https://github.com/Redocly/redoc/issues/2020)) ([1e4ea03](https://github.com/Redocly/redoc/commit/1e4ea03d4a9b7eddf3e4cc7cbdbd4d913583e837))
### Features
* add hideSecuritySection option allowing to disable the Security panel ([#2027](https://github.com/Redocly/redoc/issues/2027)) ([49cc11d](https://github.com/Redocly/redoc/commit/49cc11d91795653ca870e9276a1e0cd617964e25))
* add Redoc to Redocly CDN ([#2026](https://github.com/Redocly/redoc/issues/2026)) ([77104d6](https://github.com/Redocly/redoc/commit/77104d6c0d6f457aa08a158e93b52a45877be84e))
* add support prefix items ([27a9dba](https://github.com/Redocly/redoc/commit/27a9dbaf46aded01a6512645dab27870a85cc73b))
* remove auth section ([#2022](https://github.com/Redocly/redoc/issues/2022)) ([a863302](https://github.com/Redocly/redoc/commit/a863302cc803bdf27187c613157ba90af1040fc4))
* show minProperties maxProperties ([#2015](https://github.com/Redocly/redoc/issues/2015)) ([82712c5](https://github.com/Redocly/redoc/commit/82712c5b408dc6bc142307d45fb962de2a43ffba))
# [2.0.0-rc.70](https://github.com/Redocly/redoc/compare/2.0.0-rc.69...2.0.0-rc.70) (2022-05-17)
### Features
* display patternProperties ([#2008](https://github.com/Redocly/redoc/issues/2008)) ([660cc85](https://github.com/Redocly/redoc/commit/660cc857bc86787e16237b407fe5f5d7a493bb48))
* support conditional operators ([#1939](https://github.com/Redocly/redoc/issues/1939)) ([291b62a](https://github.com/Redocly/redoc/commit/291b62a206b68f8b4d98e4b74b71c0cad20a8b9b))
* theme add links textDecoration options ([#1599](https://github.com/Redocly/redoc/issues/1599)) ([ba06485](https://github.com/Redocly/redoc/commit/ba06485ece27acbb6b846500817f4bff3e4997ba))
# [2.0.0-rc.69](https://github.com/Redocly/redoc/compare/v2.0.0-rc.68.1...v2.0.0-rc.69) (2022-05-12)
### Bug Fixes
* wrong base url format causing error when constructing new URL ([#1996](https://github.com/Redocly/redoc/issues/1996)) ([d2cdaa1](https://github.com/Redocly/redoc/commit/d2cdaa1221b6a5e7b5da2418414bce1586069deb))
### Features
* add download file option ([#1699](https://github.com/Redocly/redoc/issues/1699)) ([b601c9a](https://github.com/Redocly/redoc/commit/b601c9ae9e3288286f28e06854bd93cb3507706e))
* add option to display verb in webhooks ([#1994](https://github.com/Redocly/redoc/issues/1994)) ([311d2ce](https://github.com/Redocly/redoc/commit/311d2ce64dcf1e68c2563a276b34dda0e08b709c))
* support .redocly.yaml for options for redoc-cli ([#1981](https://github.com/Redocly/redoc/issues/1981)) ([1f417d6](https://github.com/Redocly/redoc/commit/1f417d67c6b2e0b49e41c713958c100d8e1ad19d))
# [2.0.0-rc.68](https://github.com/Redocly/redoc/compare/v2.0.0-rc.67...v2.0.0-rc.68) (2022-05-10)
### Bug Fixes
* examples in json schema object([5b9aa27](https://github.com/Redocly/redoc/commit/5b9aa27af03a1c4616f7e0195afeba47d1deeaa0))
* handle error when definition load fails ([#1979](https://github.com/Redocly/redoc/issues/1979)) ([508ebd5](https://github.com/Redocly/redoc/commit/508ebd58a3d66f2337e9641852322458a1bd9e6b))
* large text in examples value ([#1974](https://github.com/Redocly/redoc/issues/1974)) ([60bc603](https://github.com/Redocly/redoc/commit/60bc603e9bb85a0c9c7ac38f7014876d397f0191))
* not show scopes if keys empty or not exist ([#1975](https://github.com/Redocly/redoc/issues/1975)) ([4e793f0](https://github.com/Redocly/redoc/commit/4e793f07a81fa8bcd4ad384d1f87b3e6c290edb7))
* remove dropdown-aria and use native select ([#1954](https://github.com/Redocly/redoc/issues/1954)) ([186f5a9](https://github.com/Redocly/redoc/commit/186f5a98bd466b1820121aadb865291bef8c6755))
* make Redoc lib compatible with Webpack 5 ([#1982](https://github.com/Redocly/redoc/issues/1982)) ([867861](https://github.com/Redocly/redoc/commit/8678615a0e19c9484b4cd495d70293b542d196a5))
### Features
* implement configurable minimum characer length to init search ([#1402](https://github.com/Redocly/redoc/issues/1402)) ([0fa08fa](https://github.com/Redocly/redoc/commit/0fa08faab1c176a4bfc5a553e8e8f8b07aca659f))
* support OAS 3.1 unevaluatedProperties ([#1978](https://github.com/Redocly/redoc/issues/1978)) ([0755ac6](https://github.com/Redocly/redoc/commit/0755ac6f04514eb0c08f90afceeda7858206b435))
* publish dockerhub ([#1971](https://github.com/Redocly/redoc/issues/1971)) ([7e01a0](https://github.com/Redocly/redoc/commit/7e01a0cfe2ad8d06075bfc66ef3860edbef033f8))
# [2.0.0-rc.67](https://github.com/Redocly/redoc/compare/v2.0.0-rc.66...v2.0.0-rc.67) (2022-04-28)
### Bug Fixes
* Expand/Collapse all buttons disappears for flat structures ([#1424](https://github.com/Redocly/redoc/issues/1424)) ([2ca8e08](https://github.com/Redocly/redoc/commit/2ca8e081baea6996eb01b5df27b8cd88331d5c96))
* improve markdown render with CRLF ([#1953](https://github.com/Redocly/redoc/issues/1953)) ([aba2d1a](https://github.com/Redocly/redoc/commit/aba2d1ad2d8dda9f52055c36ebde1323457dfd3e))
* issue with navigation when operationId contains backslash or quotes ([#1513](https://github.com/Redocly/redoc/issues/1513)) ([8f7e56c](https://github.com/Redocly/redoc/commit/8f7e56c747d88be5c5eb5c4bbaee0ff69e9cb2ec))
* prefix operation ids with parent id ([#1245](https://github.com/Redocly/redoc/issues/1245)) ([fd8917e](https://github.com/Redocly/redoc/commit/fd8917e5c109840c1bfa4c2c0902b6dcec200286))
### Features
* add optional BASE_PATH to Docker config ([#1378](https://github.com/Redocly/redoc/issues/1378)) ([90f71c0](https://github.com/Redocly/redoc/commit/90f71c0d77719871910cfba883a32ad131bef059))
* theme add sidebar activeBackgroundColor and activeTextColor ([#1600](https://github.com/Redocly/redoc/issues/1600)) ([6716b08](https://github.com/Redocly/redoc/commit/6716b08e8871d95880e9f5a6c5491038002754e8))
# [2.0.0-rc.66](https://github.com/Redocly/redoc/compare/v2.0.0-rc.65...v2.0.0-rc.66) (2022-03-30)
@ -12,6 +194,7 @@
* 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))
* h2 set color form theme.colors.text.primary ([#1491](https://github.com/Redocly/redoc/pull/1491)) ([25be93](https://github.com/Redocly/redoc/commit/25be934bb184d7b2b6b47d004b3c83ce4d16a2c6))
@ -23,7 +206,8 @@
* 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/
* use x-displayName in securityDefinitions ([#1444](https://github.com/Redocly/redoc/pull/1444)) ([ac6fb4](https://github.com/Redocly/redoc/commit/ac6fb458a4eee8d0da4b63f9bafc7669adc8af03))
* deprecated badge on one of any of buttons ([#1930](https://github.com/Redocly/redoc/pull/1930)) ([f60b47](https://github.com/Redocly/redoc/commit/f60b4758330dd756d670309827da60d3465b672a))

View File

@ -1,15 +1,15 @@
<div align="center">
<img alt="Redoc logo" src="https://raw.githubusercontent.com/Redocly/redoc/master//docs/images/redoc.png" width="400px" />
<img alt="Redoc logo" src="https://raw.githubusercontent.com/Redocly/redoc/main//docs/images/redoc.png" width="400px" />
# Generate interactive API documentation from OpenAPI definitions
[![Build Status](https://travis-ci.com/Redocly/redoc.svg?branch=master)](https://travis-ci.com/Redocly/redoc) [![Coverage Status](https://coveralls.io/repos/Redocly/redoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Redocly/redoc?branch=master) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Redocly/redoc/blob/master/LICENSE)
[![Coverage Status](https://coveralls.io/repos/Redocly/redoc/badge.svg?branch=main&service=github)](https://coveralls.io/github/Redocly/redoc?branch=main) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Redocly/redoc/blob/main/LICENSE)
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](https://hub.docker.com/r/redocly/redoc/)
[![bundle size](http://img.badgesize.io/https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](https://hub.docker.com/r/redocly/redoc/)
</div>
**This is 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**
**The README for the `1.x` version is on the [v1.x](https://github.com/Redocly/redoc/tree/v1.x) branch.**
## About Redoc
@ -21,7 +21,7 @@ By default Redoc offers a three-panel, responsive layout:
- The central panel contains the documentation.
- The right panel contains request and response examples.
![Redoc demo](https://raw.githubusercontent.com/Redocly/redoc/master/demo/redoc-demo.png)
![Redoc demo](https://raw.githubusercontent.com/Redocly/redoc/main/demo/redoc-demo.png)
## Live demo
@ -72,16 +72,16 @@ Checkout the following feature comparison of Redocly's premium products versus R
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/)
- [Portals](https://redocly.com/docs/developer-portal/introduction/)
- [Reference](https://redocly.com/docs/api-reference-docs/getting-started/)
- [Redoc](https://redocly.com/docs/redoc/quickstart/intro/)
## Features
- Responsive three-panel design with menu/scrolling synchronization
- [Multiple deployment options](https://redoc.ly/docs/redoc/quickstart/intro/)
- [Server-side rendering (SSR) ready](https://redoc.ly/docs/redoc/quickstart/cli/#redoc-cli-commands)
- [Multiple deployment options](https://redocly.com/docs/redoc/quickstart/intro/)
- [Server-side rendering (SSR) ready](https://redocly.com/docs/redoc/quickstart/cli/#redoc-cli-commands)
- Ability to integrate your API introduction into the side menu
- [Simple integration with `create-react-app`](https://redoc.ly/docs/redoc/quickstart/react/)
- [Simple integration with `create-react-app`](https://redocly.com/docs/redoc/quickstart/react/)
[Example repo](https://github.com/APIs-guru/create-react-app-redoc)
- [Command-line interface to bundle your docs into a **zero-dependency** HTML file](https://redoc.ly/docs/redoc/quickstart/cli/)
@ -89,9 +89,9 @@ Refer to the Redocly's documentation for more information on these products:
![](docs/images/nested-demo.gif)
## Customization options
[<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redoc.ly/#services)
- High-level grouping in side-menu with the [`x-tagGroups`](https://redoc.ly/docs/api-reference-docs/specification-extensions/x-tag-groups/) specification extension
- Branding/customizations using the [`theme` option](https://redoc.ly/docs/api-reference-docs/configuration/theming/)
[<img alt="Customization services" src="http://i.imgur.com/c4sUF7M.png" height="60px">](https://redocly.com/#services)
- High-level grouping in side-menu with the [`x-tagGroups`](https://redocly.com/docs/api-reference-docs/specification-extensions/x-tag-groups/) specification extension
- Branding/customizations using the [`theme` option](https://redocly.com/docs/api-reference-docs/configuration/theming/)
## Support
- OpenAPI v3.0 support
@ -102,9 +102,9 @@ Refer to the Redocly's documentation for more information on these products:
![](docs/images/code-samples-demo.gif)
## Releases
**Important:** all the 2.x releases are deployed to npm and can be used with jsdeliver:
- 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
**Important:** all the 2.x releases are deployed to npm and can be used with Redocly-cdn:
- particular release, for example, `v2.0.0`: https://cdn.redoc.ly/redoc/v2.0.0/bundles/redoc.standalone.js
- `latest` release: https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js
Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(deprecated)**:
- particular release, for example `v1.2.0`: https://rebilly.github.io/ReDoc/releases/v1.2.0/redoc.min.js
@ -131,11 +131,11 @@ Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(d
## Lint OpenAPI definitions
Redocly's OpenAPI CLI is an open source command-line tool that you can use to lint
Redocly's CLI is an [open source command-line tool](https://github.com/Redocly/redocly-cli) 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.
Refer to [Redocly configuration](https://redocly.com/docs/cli/configuration/) in the OpenAPI documentation for more information.
## Deployment
@ -166,7 +166,7 @@ replace the `spec-url` attribute with the url or local file address to your defi
</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>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"> </script>
</body>
</html>
@ -174,11 +174,11 @@ replace the `spec-url` attribute with the url or local file address to your defi
For step-by-step instructions for how to get started using Redoc
to render your OpenAPI definition, refer to the
[**Redoc quickstart guide**](https://redoc.ly/docs/redoc/quickstart/intro/).
[**Redoc quickstart guide**](https://redocly.com/docs/redoc/quickstart/) and [**How to use the HTML element**](https://redocly.com/docs/redoc/deployment/html/).
## Redoc CLI
For more information on Redoc's commmand-line interface, refer to
[**Using the Redoc CLI**](https://redoc.ly/docs/redoc/quickstart/cli/).
[**Using the Redoc CLI**](https://redocly.com/docs/redoc/deployment/cli/).
## Configuration
@ -188,7 +188,7 @@ You can inject the Security Definitions widget into any place in your definition
For more information, refer to [Security definitions injection](docs/security-definitions-injection.md).
### OpenAPI specification extensions
Redoc uses the following [specification extensions](https://swagger.io/specification/#specificationExtensions):
Redoc uses the following [specification extensions](https://redocly.com/docs/api-reference-docs/spec-extensions/):
* [`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-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples
@ -207,11 +207,14 @@ Redoc uses the following [specification extensions](https://swagger.io/specifica
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.
* `minCharacterLengthToInitSearch` - set minimal characters length to init search, default `3`, minimal `1`.
* `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.
* `generatedPayloadSamplesMaxDepth` - set the maximum render depth for JSON payload samples (responses and request body). The default value is `10`.
* `maxDisplayedEnumValues` - display only specified number of enum values. hide rest values under spoiler.
* `hideDownloadButton` - do not show "Download" spec button. **THIS DOESN'T MAKE YOUR SPEC PRIVATE**, it just hides the button.
* `downloadFileName` - set a custom file name for the downloaded API definition file.
* `downloadDefinitionUrl` - If the 'Download' button is visible in the API reference documentation (hideDownloadButton=false), the URL configured here will open when that button is selected. Provide it as an absolute URL with the full URI scheme.
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
* `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.
@ -229,7 +232,6 @@ You can use all of the following options with the standalone version of the <red
* `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`.
* `nativeScrollbars` - use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs).
* `noAutoAuth` - do not inject Authentication section automatically.
* `onlyRequiredInSamples` - shows only required fields in request samples.
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one.
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
@ -238,16 +240,17 @@ You can use all of the following options with the standalone version of the <red
* **number**: A fixed number of pixels to be used as offset.
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset.
* **function**: A getter function. Must return a number representing the offset (in pixels).
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display.
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by Redoc are ignored. Can be boolean or an array of `string` with names of extensions to display.
* `sortPropsAlphabetically` - sort properties alphabetically.
* `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!**
* `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.
* `showWebhookVerb` - when set to `true`, shows the HTTP request method for webhooks in operations and in the sidebar.
### `<redoc>` theme object
* `spacing`
@ -285,26 +288,38 @@ You can use all of the following options with the standalone version of the <red
* `color`: # COMPUTED: colors.primary.main
* `visited`: # COMPUTED: typography.links.color
* `hover`: # COMPUTED: lighten(0.2 typography.links.color)
* `menu`
* `textDecoration`: 'auto'
* `hoverTextDecoration`: 'auto'
* `sidebar`
* `width`: '260px'
* `backgroundColor`: '#fafafa'
* `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
* `activeBackgroundColor`: # COMPUTED: theme.sidebar.backgroundColor
* `activeTextColor`: # COMPUTED: theme.sidebar.activeTextColor
* `textTransform`: 'uppercase'
* `level1Items` # Level 1 items like tags or section 1st level items
* `activeBackgroundColor`: # COMPUTED: theme.sidebar.backgroundColor
* `activeTextColor`: # COMPUTED: theme.sidebar.activeTextColor
* `textTransform`: 'none'
* `arrow` # menu arrow
* `arrow` # sidebar arrow
* `size`: '1.5em'
* `color`: # COMPUTED: theme.menu.textColor
* `color`: # COMPUTED: theme.sidebar.textColor
* `logo`
* `maxHeight`: # COMPUTED: menu.width
* `maxWidth`: # COMPUTED: menu.width
* `maxHeight`: # COMPUTED: sidebar.width
* `maxWidth`: # COMPUTED: sidebar.width
* `gutter`: '2px' # logo image padding
* `rightPanel`
* `backgroundColor`: '#263238'
* `width`: '40%'
* `textColor`: '#ffffff'
* `servers`
* `overlay`
* `backgroundColor`: '#fafafa'
* `textColor`: '#263238'
* `url`
* `backgroundColor`: '#fff'
* `fab`
* `backgroundColor`: '#263238'
* `color`: '#ffffff'

View File

@ -14,7 +14,7 @@ The two following commands are available:
- `redoc-cli serve [spec]` - starts the server with `spec` rendered with ReDoc.
Supports a server-side rendering mode (`--ssr`)
and can watch the spec (`--watch`) to automatically reload the page whenever it changes.\
Deprecated. Use `npx @redocly/openapi-cli preview-docs [spec]`
Deprecated. Use `npx @redocly/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.
@ -26,9 +26,15 @@ Some examples:
- Serve with the `nativeScrollbars` option set to true:<br/>
`$ redoc-cli serve [spec] --options.nativeScrollbars`
- Bundle using a custom [Handlebars](https://handlebarsjs.com/) template
(check the [default template](https://github.com/Redocly/redoc/blob/master/cli/template.hbs) for an example):<br/>
(check the [default template](https://github.com/Redocly/redoc/blob/main/cli/template.hbs) for an example):<br/>
`$ redoc-cli build [spec] -t custom.hbs`
- Bundle using a custom template and add custom `templateOptions`:<br/>
`$ redoc-cli build [spec] -t custom.hbs --templateOptions.metaDescription "Page meta description"`
#### With a Redocly configuration file ([more info](https://redocly.com/docs/cli/configuration/#redocly-configuration-file)):
1. Go to folder with your Redocly configuration file (`.redocly.yaml` or `redocly.yaml`) and your OpenAPI definition file.
2. Build the site using the `build` command (options from the Redocly configuration file will be automatically fetched):
`redoc build openapi.yaml`
For more details, run `redoc-cli --help`.

View File

@ -0,0 +1,2 @@
features.openapi:
disableSearch: true

View File

@ -0,0 +1,30 @@
import { spawnSync } from 'child_process';
import { readFileSync } from 'fs';
describe('build', () => {
it('should use .redocly.yaml', () => {
const r = spawnSync(
'ts-node',
['../../../index.ts', 'build', ' ../../../../demo/openapi.yaml', '--output=redoc-test.html'],
{
cwd: __dirname,
shell: true,
},
);
const out = r.stdout.toString('utf-8');
const err = r.stderr.toString('utf-8');
const result = `${out}\n${err}`;
try {
const redocStaticFile = readFileSync(`${__dirname}/redoc-test.html`, 'utf8');
expect(redocStaticFile).toContain('"options":{"disableSearch":true}');
expect(redocStaticFile).not.toContain('role="search"');
} catch (err) {
expect(err.toString()).toContain('{"options":{"disableSearch":"true"}');
}
expect(result).toContain('Found .redocly.yaml and using features.openapi options');
expect(result).toContain('bundled successfully');
});
});

View File

@ -0,0 +1,34 @@
import { spawnSync } from 'child_process';
import { readFileSync } from 'fs';
describe('build with inline options', () => {
it('should use inline options and ignore .redocly.yaml', () => {
const r = spawnSync(
'ts-node',
[
'../../../index.ts',
'build',
' ../../../../demo/openapi.yaml',
'--options.disableSearch="false" ',
],
{
cwd: __dirname,
shell: true,
},
);
const out = r.stdout.toString('utf-8');
const err = r.stderr.toString('utf-8');
const result = `${out}\n${err}`;
expect(result).not.toContain('Found .redocly.yaml and using features.openapi options');
expect(result).toContain('bundled successfully');
try {
const redocStaticFile = readFileSync(`${__dirname}/redoc-static.html`, 'utf8');
expect(redocStaticFile).toContain('"options":{"disableSearch":"false"}');
expect(redocStaticFile).toContain('role="search"');
} catch (err) {
expect(err.toString()).toContain('"options":{"disableSearch":"false"}');
}
});
});

View File

@ -0,0 +1,24 @@
import { spawnSync } from 'child_process';
describe('build with url', () => {
it('should not fail on resolving url', () => {
const r = spawnSync(
'ts-node',
[
'../../../index.ts',
'build',
'http://petstore.swagger.io/v2/swagger.json',
'--output=url-test.html',
],
{
cwd: __dirname,
shell: true,
},
);
const out = r.stdout.toString('utf-8');
const err = r.stderr.toString('utf-8');
const result = `${out}\n${err}`;
expect(result).toContain('bundled successfully');
});
});

View File

@ -1,6 +1,7 @@
#!/usr/bin/env node
/* tslint:disable:no-implicit-dependencies */
import * as React from 'react';
import * as updateNotifier from 'update-notifier';
import { renderToString } from 'react-dom/server';
import { ServerStyleSheet } from 'styled-components';
@ -25,6 +26,12 @@ import {
import * as mkdirp from 'mkdirp';
import * as YargsParser from 'yargs';
// eslint-disable-next-line import/no-extraneous-dependencies
import { findConfig } from '@redocly/openapi-core';
// eslint-disable-next-line import/no-extraneous-dependencies
import { parseYaml } from '@redocly/openapi-core';
// eslint-disable-next-line import/no-extraneous-dependencies
import { Config } from '@redocly/openapi-core';
interface Options {
ssr?: boolean;
@ -105,6 +112,7 @@ const handlerForBuildCommand = async (argv: any) => {
};
try {
notifyUpdateCliVersion();
await bundle(argv.spec, config);
} catch (e) {
handleError(e);
@ -168,6 +176,7 @@ YargsParser.command(
};
try {
notifyUpdateCliVersion();
await serve(argv.host as string, argv.port as number, argv.spec as string, config);
} catch (e) {
handleError(e);
@ -176,7 +185,7 @@ YargsParser.command(
[
res => {
console.log(
`\n⚠ This command is deprecated. Use "npx @redocly/openapi-cli preview-docs petstore.yaml"\n`,
`\n⚠ This command is deprecated. Use "npx @redocly/cli preview-docs petstore.yaml"\n`,
);
return res;
},
@ -334,6 +343,7 @@ async function getPageHTML(
const specUrl = redocOptions.specUrl || (isURL(pathToSpec) ? pathToSpec : undefined);
const store = await createStore(spec, specUrl, redocOptions);
const sheet = new ServerStyleSheet();
// @ts-ignore
html = renderToString(sheet.collectStyles(React.createElement(Redoc, { store })));
css = sheet.getStyleTags();
state = await store.toJS();
@ -361,7 +371,7 @@ async function getPageHTML(
</script>`,
redocHead: ssr
? (cdn
? '<script src="https://unpkg.com/redoc@next/bundles/redoc.standalone.js"></script>'
? '<script src="https://unpkg.com/redoc@latest/bundles/redoc.standalone.js"></script>'
: `<script>${redocStandaloneSrc}</script>`) + css
: '<script src="redoc.standalone.js"></script>',
title: title || spec.info.title || 'ReDoc documentation',
@ -447,6 +457,30 @@ function getObjectOrJSON(options) {
handleError(e);
}
default:
const configFile = findConfig();
if (configFile) {
console.log(`Found ${configFile} and using features.openapi options`);
try {
const config = parseYaml(readFileSync(configFile, 'utf-8')) as Config;
return config['features.openapi'];
} catch (e) {
console.warn(`Found ${configFile} but failed to parse: ${e.message}`);
}
}
return {};
}
}
function notifyUpdateCliVersion() {
const pkg = require('./package.json');
const notifier = updateNotifier({
pkg,
updateCheckInterval: 0,
shouldNotifyInNpmScript: true,
});
notifier.notify({
message:
'Run `{updateCommand}` to update.\nChangelog: https://github.com/Redocly/redoc/releases/tag/{latestVersion}',
});
}

2156
cli/npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "redoc-cli",
"version": "0.13.10",
"version": "0.13.20",
"description": "ReDoc's Command Line Interface",
"main": "index.js",
"bin": "index.js",
@ -19,16 +19,15 @@
"node-libs-browser": "^2.2.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"redoc": "2.0.0-rc.66",
"redoc": "2.0.0-rc.77",
"styled-components": "^5.3.0",
"update-notifier": "^5.0.1",
"yargs": "^17.3.1"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/chokidar": "^2.1.3",
"@types/handlebars": "^4.1.0",
"@types/mkdirp": "^1.0.1"
}
}

View File

@ -5,7 +5,7 @@
# npm i -g http-server
# http-server -p 8000 --cors
FROM node:alpine
FROM node:12-alpine
RUN apk update && apk add --no-cache git
@ -26,6 +26,7 @@ FROM nginx:alpine
ENV PAGE_TITLE="ReDoc"
ENV PAGE_FAVICON="favicon.png"
ENV BASE_PATH=
ENV SPEC_URL="http://petstore.swagger.io/v2/swagger.json"
ENV PORT=80
ENV REDOC_OPTIONS=

View File

@ -45,9 +45,10 @@ Another issue with OpenShift is that the default exposed port `80` cannot be use
- `PAGE_TITLE` (default `"ReDoc"`) - page title
- `PAGE_FAVICON` (default `"favicon.png"`) - URL to page favicon
- `BASE_PATH` (optional) - prepend favicon & standalone bundle with this path
- `SPEC_URL` (default `"http://petstore.swagger.io/v2/swagger.json"`) - URL to spec
- `PORT` (default `80`) - nginx port
- `REDOC_OPTIONS` - [`<redoc>` tag attributes](https://github.com/Redocly/redoc#redoc-tag-attributes)
- `REDOC_OPTIONS` (optional) - [`<redoc>` tag attributes](https://github.com/Redocly/redoc#redoc-tag-attributes)
## Build

View File

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

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>%PAGE_TITLE%</title>
<link rel="icon" href="%PAGE_FAVICON%" />
<link rel="icon" href="%BASE_PATH%%PAGE_FAVICON%" />
<style>
body {
margin: 0;
@ -23,6 +23,6 @@
<body>
<redoc spec-url="%SPEC_URL%" %REDOC_OPTIONS%></redoc>
<script src="redoc.standalone.js"></script>
<script src="%BASE_PATH%redoc.standalone.js"></script>
</body>
</html>

View File

@ -1,7 +1,6 @@
import * as React from 'react';
import { render } from 'react-dom';
import styled from 'styled-components';
import { resolve as urlResolve } from 'url';
import { RedocStandalone } from '../src';
import ComboBox from './ComboBox';
import FileInput from './components/FileInput';
@ -22,7 +21,7 @@ const demos = [
];
class DemoApp extends React.Component<
{},
Record<string, unknown>,
{ spec: object | undefined; specUrl: string; dropdownOpen: boolean; cors: boolean }
> {
constructor(props) {
@ -87,7 +86,7 @@ class DemoApp extends React.Component<
let proxiedUrl = specUrl;
if (specUrl !== DEFAULT_SPEC) {
proxiedUrl = cors
? '\\\\cors.redoc.ly/' + urlResolve(window.location.href, specUrl)
? '\\\\cors.redoc.ly/' + new URL(specUrl, window.location.href).href
: specUrl;
}
return (
@ -95,7 +94,7 @@ class DemoApp extends React.Component<
<Heading>
<a href=".">
<Logo
src="https://github.com/Redocly/redoc/raw/master/docs/images/redoc-logo.png"
src="https://github.com/Redocly/redoc/raw/main/docs/images/redoc.png"
alt="Redoc logo"
/>
</a>

View File

@ -16,14 +16,14 @@ info:
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md).
# OpenAPI Specification
This API is documented in **OpenAPI format** and is based on
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md).
# Cross-Origin Resource Sharing
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
@ -88,12 +88,14 @@ x-tagGroups:
tags:
- pet_model
- store_model
security:
- {}
paths:
/pet:
parameters:
- name: Accept-Language
in: header
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
description: 'The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US'
example: en-US
required: false
schema:
@ -182,6 +184,16 @@ paths:
}
requestBody:
$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}':
get:
tags:
@ -259,7 +271,7 @@ paths:
required: false
schema:
type: string
example: "Bearer <TOKEN>"
example: 'Bearer <TOKEN>'
- name: petId
in: path
description: Pet id to delete
@ -295,6 +307,9 @@ paths:
content:
application/json:
schema:
unevaluatedProperties:
type: integer
format: int32
$ref: '#/components/schemas/ApiResponse'
security:
- petstore_auth:
@ -432,7 +447,7 @@ paths:
application/json:
example:
status: 400
message: "Invalid Order"
message: 'Invalid Order'
requestBody:
content:
application/json:
@ -894,11 +909,11 @@ paths:
type: string
examples:
response:
value: <Message> OK </Message>
value: <Message> OK </Message>
text/plain:
examples:
response:
value: OK
value: OK
'400':
description: Invalid username/password supplied
/user/logout:
@ -925,7 +940,7 @@ components:
content:
multipart/form-data:
schema:
$ref: "#/components/schemas/Cat"
$ref: '#/components/schemas/Cat'
responses:
'200':
description: update Cat details
@ -940,13 +955,40 @@ components:
content:
multipart/form-data:
schema:
$ref: "#/components/schemas/Cat"
$ref: '#/components/schemas/Cat'
responses:
'200':
description: create Cat details
schemas:
ApiResponse:
type: object
patternProperties:
^S_\\w+\\.[1-9]{2,4}$:
description: The measured skill for hunting
if:
x-displayName: fieldName === 'status'
else:
minLength: 1
maxLength: 10
then:
format: url
type: string
enum:
- success
- failed
^O_\\w+\\.[1-9]{2,4}$:
type: object
properties:
nestedProperty:
type: [string, boolean]
description: The measured skill for hunting
default: lazy
example: adventurous
enum:
- clueless
- lazy
- adventurous
- aggressive
properties:
code:
type: integer
@ -962,7 +1004,7 @@ components:
- type: object
properties:
huntingSkill:
type: string
type: [string, boolean]
description: The measured skill for hunting
default: lazy
example: adventurous
@ -1073,8 +1115,8 @@ components:
properties:
id:
externalDocs:
description: "Find more info here"
url: "https://example.com"
description: 'Find more info here'
url: 'https://example.com'
description: Pet ID
$ref: '#/components/schemas/Id'
category:
@ -1086,15 +1128,26 @@ components:
example: Guru
photoUrls:
description: The list of URL to a cute photos featuring pet
type: [string, integer, 'null', array]
type: [string, integer, 'null']
minItems: 1
maxItems: 20
maxItems: 10
xml:
name: photoUrl
wrapped: true
items:
type: string
format: url
if:
x-displayName: isString
type: string
then:
minItems: 1
maxItems: 15
else:
x-displayName: notString
type: [integer, 'null']
minItems: 1
maxItems: 20
friend:
$ref: '#/components/schemas/Pet'
tags:
@ -1118,12 +1171,19 @@ components:
petType:
description: Type of a pet
type: string
huntingSkill:
type: [integer]
enum:
- 0
- 1
- 2
xml:
name: Pet
Tag:
type: object
properties:
id:
type: number
description: Tag ID
$ref: '#/components/schemas/Id'
name:
@ -1185,6 +1245,35 @@ components:
type: string
contentEncoding: base64
contentMediaType: image/png
addresses:
type: array
minItems: 0
maxLength: 10
prefixItems:
- type: object
properties:
city:
type: string
minLength: 0
country:
type: string
minLength: 0
street:
description: includes build/apartment number
type: string
minLength: 0
- type: number
items:
type: string
if:
title: userStatus === 10
properties:
userStatus:
enum: [10]
then:
required: ['phone']
else:
required: []
xml:
name: User
requestBodies:
@ -1251,9 +1340,9 @@ webhooks:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
$ref: '#/components/schemas/Pet'
responses:
"200":
'200':
description: Return a 200 status to indicate that the data was received successfully
myWebhook:
$ref: '#/components/pathItems/webhooks'

View File

@ -16,14 +16,14 @@ info:
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md).
# OpenAPI Specification
This API is documented in **OpenAPI format** and is based on
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md).
# Cross-Origin Resource Sharing
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
@ -38,7 +38,7 @@ info:
OAuth2 - an open protocol to allow secure authorization in a simple
and standard method from web, mobile and desktop applications.
<SecurityDefinitions />
<!-- ReDoc-Inject: <security-definitions> -->
version: 1.0.0
title: Swagger Petstore
@ -83,12 +83,14 @@ x-tagGroups:
tags:
- pet_model
- store_model
security:
- {}
paths:
/pet:
parameters:
- name: Accept-Language
in: header
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
description: 'The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US'
example: en-US
required: false
schema:
@ -254,7 +256,7 @@ paths:
required: false
schema:
type: string
example: "Bearer <TOKEN>"
example: 'Bearer <TOKEN>'
- name: petId
in: path
description: Pet id to delete
@ -401,6 +403,7 @@ paths:
application/json:
schema:
type: object
minProperties: 2
additionalProperties:
type: integer
format: int32
@ -429,7 +432,7 @@ paths:
application/json:
example:
status: 400
message: "Invalid Order"
message: 'Invalid Order'
requestBody:
content:
application/json:
@ -877,11 +880,11 @@ paths:
type: string
examples:
response:
value: <Message> OK </Message>
value: <Message> OK </Message>
text/plain:
examples:
response:
value: OK
value: OK
'400':
description: Invalid username/password supplied
/user/logout:
@ -1027,8 +1030,8 @@ components:
properties:
id:
externalDocs:
description: "Find more info here"
url: "https://example.com"
description: 'Find more info here'
url: 'https://example.com'
description: Pet ID
allOf:
- $ref: '#/components/schemas/Id'
@ -1134,6 +1137,26 @@ components:
description: User status
type: integer
format: int32
addresses:
type: array
minItems: 0
maxLength: 10
items:
- type: object
properties:
city:
type: string
minLength: 0
country:
type: string
minLength: 0
street:
description: includes build/apartment number
type: string
minLength: 0
- type: number
additionalItems:
type: string
xml:
name: User
requestBodies:
@ -1201,7 +1224,7 @@ x-webhooks:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
$ref: '#/components/schemas/Pet'
responses:
"200":
'200':
description: Return a 200 status to indicate that the data was received successfully

View File

@ -15,13 +15,13 @@ info:
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md).
# OpenAPI Specification
This API is documented in **OpenAPI format** and is based on
[Petstore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
tool and [ReDoc](https://github.com/Redocly/redoc) documentation. In addition to standard
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md).
OpenAPI syntax we use a few [vendor extensions](https://github.com/Redocly/redoc/blob/main/docs/redoc-vendor-extensions.md).
# Cross-Origin Resource Sharing
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
And that allows cross-domain communication from the browser.

View File

@ -1,7 +1,7 @@
---
title: Use the Redoc CLI
redirectFrom:
- /docs/quickstart/cli/
- /docs/redoc/quickstart/cli/
---
# How to use the Redoc CLI
@ -54,9 +54,9 @@ The CLI includes the following commands:
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/).
- `--options`: Customizes your output using [Redoc functionality options](https://redocly.com/docs/api-reference-docs/configuration/functionality) or [Redoc theming options](https://redocly.com/docs/api-reference-docs/configuration/theming).
To add nested options, use dot notation.
- **`redoc-cli bundle [spec]`:** Bundles `spec` and Redoc into a zero-dependency HTML file. Options include:
- **`redoc-cli build [spec]`:** Builds `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.
@ -70,13 +70,13 @@ The CLI includes the following commands:
Bundle with the main color changed to `orange`:
```bash
redoc-cli bundle openapi.yaml --options.theme.colors.primary.main=orange
redoc-cli build 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"
redoc-cli build http://petstore.swagger.io/v2/swagger.json -t custom.hbs --templateOptions.metaDescription "Page meta description"
```
Sample Handlebars template:

View File

@ -1,7 +1,7 @@
---
title: Use the Redoc Docker image
redirectFrom:
- /docs/quickstart/docker/
- /docs/redoc/quickstart/docker/
---
# How to use the Redoc Docker image
@ -37,5 +37,5 @@ docker run -p 8080:80 -e SPEC_URL=https://api.example.com/openapi.json redocly/r
## 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)
a sample [Dockerfile](https://github.com/Redocly/redoc/blob/main/config/docker/Dockerfile)
in our code repo.

View File

@ -1,7 +1,7 @@
---
title: Use the Redoc HTML element
redirectFrom:
- /docs/quickstart/html/
- /docs/redoc/quickstart/html/
---
# How to use the Redoc HTML element
@ -51,7 +51,7 @@ or the files located in your `node modules` folder.
To reference the Redoc script with a CDN link:
```html
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"> </script>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"> </script>
```
### Node modules link
@ -97,7 +97,7 @@ 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.
- `options`: See [features.openapi object](/docs/api-reference-docs/configuration/functionality.mdx) 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.

View File

@ -1,7 +1,7 @@
---
title: Redoc deployment guide
redirectFrom:
- /docs/quickstart/intro/
- /docs/redoc/quickstart/intro/
---
# Redoc deployment guide
@ -46,12 +46,12 @@ section in the documentation.
If you want to view your Redoc output locally, you can simulate an HTTP server.
#### Redocly OpenAPI CLI
#### Redocly CLI
Redocly OpenAPI CLI is an open source command-line tool that includes a command
Redocly 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
If you have [Redocly CLI](https://redocly.com/docs/cli/#installation-and-usage) installed, `cd` into your
project directory and run the following command:
```bash
@ -72,7 +72,7 @@ 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.
[Redocly CLI commands](https://redocly.com/docs/cli/commands/preview-docs/#preview-docs) in the Redocly CLI documentation.
#### Python

View File

@ -1,7 +1,7 @@
---
title: Use the Redoc React component
redirectFrom:
- /docs/quickstart/react/
- /docs/redoc/quickstart/react/
---
# How to use the Redoc React component
@ -59,7 +59,7 @@ For example:
```
For more information on configuration options, refer to the
[Configuration options for Reference docs](https://redocly.com/docs/api-reference-docs/configuration/)
[Configuration options for Reference docs](https://redocly.com/docs/api-reference-docs/configuration/functionality/)
section of the documentation. Options available for Redoc are noted,
"Supported in Redoc CE".

View File

@ -38,7 +38,7 @@ replace the `spec-url` attribute with the URL or local file address to your defi
<!--
Link to Redoc JavaScript on CDN for rendering standalone element
-->
<script src="https://cdn.jsdelivr.net/npm/redoc@latest/bundles/redoc.standalone.js"></script>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>
```

View File

@ -1,112 +0,0 @@
---
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
```

View File

@ -1,39 +0,0 @@
---
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.

View File

@ -1,214 +0,0 @@
---
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>
```

View File

@ -1,44 +0,0 @@
---
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.

View File

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

View File

@ -49,7 +49,7 @@ describe('Menu', () => {
cy.location('hash').should('equal', '#tag/pet');
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', () => {
@ -76,4 +76,20 @@ describe('Menu', () => {
.then($h5 => $h5[0].firstChild!.nodeValue!.trim())
.should('eq', 'Response Schema:');
});
it('should be able to open the operation details when the operation IDs have quotes', () => {
cy.visit('e2e/standalone-3-1.html');
cy.get('label span[title="pet"]').click({ multiple: true, force: true });
cy.get('li').contains('OperationId with quotes').click({ multiple: true, force: true });
cy.get('h2').contains('OperationId with quotes').should('be.visible');
cy.url().should('include', 'deletePetBy%22Id');
});
it.only('should encode URL when the operation IDs have backslashes', () => {
cy.visit('e2e/standalone-3-1.html');
cy.get('label span[title="pet"]').click({ multiple: true, force: true });
cy.get('li').contains('OperationId with backslash').click({ multiple: true, force: true });
cy.get('h2').contains('OperationId with backslash').should('be.visible');
cy.url().should('include', 'delete%5CPetById');
});
});

View File

@ -21,12 +21,12 @@ describe('Servers', () => {
initReDoc(win, spec, {});
// TODO add cy-data attributes
cy.get('[data-section-id="operation/addPet"]').should(
cy.get('[data-section-id="tag/pet/operation/addPet"]').should(
'contain',
'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',
'http://petstore.swagger.io/sandbox/pet',
);
@ -40,7 +40,7 @@ describe('Servers', () => {
initReDoc(win, spec, {});
// TODO add cy-data attributes
cy.get('[data-section-id="operation/addPet"]').should(
cy.get('[data-section-id="tag/pet/operation/addPet"]').should(
'contain',
'http://localhost:' + win.location.port + '/pet',
);
@ -55,7 +55,7 @@ describe('Servers', () => {
initReDoc(win, spec, {});
// TODO add cy-data attributes
cy.get('[data-section-id="operation/addPet"]').should(
cy.get('[data-section-id="tag/pet/operation/addPet"]').should(
'contain',
'http://localhost:' + win.location.port + '/pet',
);

View File

@ -45,7 +45,7 @@ describe('Search', () => {
getSearchInput().type('{enter}', { force: true });
cy.contains('[role=navigation] [role=menuitem]', 'Introduction').should('have.class', 'active');
cy.contains('[role=menu] [role=menuitem]', 'Introduction').should('have.class', 'active');
});
it('should mark search results', () => {
@ -59,4 +59,20 @@ describe('Search', () => {
getSearchInput().type('xzss', { force: true });
getSearchResults().should('exist').should('contain', 'No results found');
});
it('should allow search by path or keywords in path', () => {
getSearchInput().clear().type('uploadImage', { force: true });
cy.get('[role=search] [role=menuitem]')
.should('have.length', 1)
.first()
.should('contain', 'uploads an image');
getSearchInput()
.clear()
.type('/pet/{petId}/uploadImage', { force: true, parseSpecialCharSequences: false });
cy.get('[role=search] [role=menuitem]')
.should('have.length', 1)
.first()
.should('contain', 'uploads an image');
});
});

View File

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

1148
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,13 @@
{
"name": "redoc",
"version": "2.0.0-rc.66",
"version": "2.0.0",
"description": "ReDoc",
"repository": {
"type": "git",
"url": "git://github.com/Redocly/redoc"
},
"browserslist": [
"defaults",
"ie 11"
"defaults"
],
"engines": {
"node": ">=6.9",
@ -33,8 +32,9 @@
"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: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",
"test:update-snapshot": "jest --updateSnapshot",
"e2e": "cypress run",
"e2e-ci": "cypress run --record",
"bundlesize": "size-limit",
@ -44,17 +44,18 @@
"bundle:standalone": "webpack --env production --env standalone --mode=production",
"bundle:lib": "webpack --mode=production && npm run declarations",
"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",
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:browser && npm run bundle:standalone && npm run compile:cli",
"declarations": "tsc --emitDeclarationOnly -p tsconfig.lib.json && cp -R src/types typings/",
"stats": "webpack --env production --env standalone --json --profile --mode=production > stats.json",
"prettier": "prettier --write \"cli/index.ts\" \"src/**/*.{ts,tsx}\"",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
"lint": "eslint 'src/**/*.{js,ts,tsx}' --cache",
"lint": "eslint --fix 'src/**/*.{js,ts,tsx}' --cache",
"benchmark": "node ./benchmark/benchmark.js",
"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",
"build:sofico-version": "webpack --env playground='false' bench='false' --mode=production --config demo/webpack.config.ts ",
"build:demo": "webpack --mode=production --config demo/webpack.config.ts",
"publish-cdn": "scripts/publish-cdn.sh",
"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;CC-BY-4.0;Python-2.0' --summary",
"docker:build": "docker build -f config/docker/Dockerfile -t redoc .",
@ -62,8 +63,8 @@
"pre-commit": "pretty-quick --staged"
},
"devDependencies": {
"@cypress/webpack-preprocessor": "^5.9.0",
"@hot-loader/react-dom": "^17.0.1",
"@cypress/webpack-preprocessor": "^5.12.0",
"@hot-loader/react-dom": "^17.0.2",
"@size-limit/preset-app": "^7.0.4",
"@types/chai": "^4.2.18",
"@types/dompurify": "^2.2.2",
@ -73,7 +74,7 @@
"@types/json-pointer": "^1.0.30",
"@types/lunr": "^2.3.3",
"@types/mark.js": "^8.11.5",
"@types/marked": "^4.0.1",
"@types/marked": "^4.0.3",
"@types/node": "^15.6.1",
"@types/prismjs": "^1.16.5",
"@types/prop-types": "^15.7.3",
@ -110,6 +111,7 @@
"license-checker": "^25.0.1",
"lodash.noop": "^3.0.1",
"mobx": "^6.3.2",
"outdent": "^0.8.0",
"prettier": "^2.3.2",
"pretty-quick": "^3.0.0",
"raf": "^3.4.1",
@ -123,10 +125,10 @@
"ts-jest": "^27.0.2",
"ts-loader": "^9.2.6",
"ts-node": "^10.0.0",
"tslib": "^2.4.0",
"typescript": "~4.1.0",
"unfetch": "^4.2.0",
"url-polyfill": "^1.1.12",
"webpack": "^5.38.1",
"webpack": "^5.50.1",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.6.0",
"webpack-node-externals": "^3.0.0",
@ -140,8 +142,7 @@
"styled-components": "^4.1.1 || ^5.1.1"
},
"dependencies": {
"@redocly/openapi-core": "^1.0.0-beta.88",
"@redocly/react-dropdown-aria": "^2.0.11",
"@redocly/openapi-core": "^1.0.0-beta.104",
"classnames": "^2.3.1",
"decko": "^1.2.0",
"dompurify": "^2.2.8",
@ -149,11 +150,11 @@
"json-pointer": "^0.6.2",
"lunr": "^2.3.9",
"mark.js": "^8.11.1",
"marked": "^4.0.10",
"marked": "^4.0.15",
"mobx-react": "^7.2.0",
"openapi-sampler": "^1.2.1",
"openapi-sampler": "^1.3.0",
"path-browserify": "^1.0.1",
"perfect-scrollbar": "^1.5.1",
"perfect-scrollbar": "^1.5.5",
"polished": "^4.1.3",
"prismjs": "^1.27.0",
"prop-types": "^15.7.2",
@ -168,6 +169,14 @@
{
"path": "./bundles/redoc.standalone.js",
"limit": "350 kB"
},
{
"path": "./bundles/redoc.lib.js",
"limit": "100 kB"
},
{
"path": "./bundles/redoc.browser.lib.js",
"limit": "100 kB"
}
],
"jest": {
@ -187,10 +196,12 @@
"coveragePathIgnorePatterns": [
"\\.d\\.ts$",
"/benchmark/",
"/node_modules/"
"/node_modules/",
"src/services/__tests__/models/helpers.ts"
],
"modulePathIgnorePatterns": [
"/benchmark/"
"/benchmark/",
"src/services/__tests__/models/helpers.ts"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"

24
scripts/invalidate-cache.sh Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -e # exit on error
echo jsdelivr clearing cache
curl -i -X POST https://purge.jsdelivr.net/ \
-H 'cache-control: no-cache' \
-H 'content-type: application/json' \
-d '{
"path": [
"npm/redoc@latest/bundles/redoc.browser.lib.js",
"npm/redoc@latest/bundles/redoc.lib.js",
"npm/redoc@latest/bundles/redoc.standalone.js"
]
}'
echo
echo start invalidate cloudfront
aws cloudfront create-invalidation --distribution-id $DISTRIBUTION --paths "/redoc/*"
echo Cache cleared successfully
exit 0

40
scripts/publish-cdn.sh Executable file
View File

@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -e # exit on error
# TODO: Update script!
VERSION=$(node scripts/version.js)
VERSION_TAG=v${VERSION:0:1}.x
copy_to_s3 () {
aws s3 cp --exclude "*" --include "*.js" --content-type "application/javascript; charset=utf-8" bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive
aws s3 cp --exclude "*" --include "*.map" --content-type "application/json" bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive
aws s3 cp --exclude "*" --include "*.txt" bundles "s3://redocly-cdn/redoc/$1/bundles" --recursive
aws s3 cp CHANGELOG.md "s3://redocly-cdn/redoc/$1/CHANGELOG.md"
aws s3 cp LICENSE "s3://redocly-cdn/redoc/$1/LICENSE"
aws s3 cp package.json "s3://redocly-cdn/redoc/$1/package.json"
aws s3 cp README.md "s3://redocly-cdn/redoc/$1/README.md"
}
if aws s3 ls "redocly-cdn/redoc/v$VERSION/" "$@"; then
echo "Version $VERSION already exists"
exit 1
else
echo Releasing $VERSION
echo Uploading to S3 $VERSION
copy_to_s3 "v$VERSION"
echo Uploading to S3 $VERSION_TAG
copy_to_s3 "$VERSION_TAG" $@
if [[ "$VERSION_TAG" == "v2.x" ]]; then
echo Uploading to S3 latest
copy_to_s3 latest $@
fi
echo
echo Deployed successfully
exit 0
fi

1
scripts/version.js Normal file
View File

@ -0,0 +1 @@
console.log(require('../package.json').version);

View File

@ -8,36 +8,24 @@ export interface CopyButtonWrapperProps {
children: (props: { renderCopyButton: () => React.ReactNode }) => React.ReactNode;
}
export class CopyButtonWrapper extends React.PureComponent<
CopyButtonWrapperProps,
{ tooltipShown: boolean }
> {
constructor(props) {
super(props);
this.state = {
tooltipShown: false,
};
}
export const CopyButtonWrapper = (
props: CopyButtonWrapperProps & { tooltipShown?: boolean },
): JSX.Element => {
const [tooltipShown, setTooltipShown] = React.useState(false);
render() {
return this.props.children({ renderCopyButton: this.renderCopyButton });
}
copy = () => {
const copy = () => {
const content =
typeof this.props.data === 'string'
? this.props.data
: JSON.stringify(this.props.data, null, 2);
typeof props.data === 'string' ? props.data : JSON.stringify(props.data, null, 2);
ClipboardService.copyCustom(content);
this.showTooltip();
showTooltip();
};
renderCopyButton = () => {
const renderCopyButton = () => {
return (
<button onClick={this.copy}>
<button onClick={copy}>
<Tooltip
title={ClipboardService.isSupported() ? 'Copied' : 'Not supported in your browser'}
open={this.state.tooltipShown}
open={tooltipShown}
>
Copy
</Tooltip>
@ -45,15 +33,12 @@ export class CopyButtonWrapper extends React.PureComponent<
);
};
showTooltip() {
this.setState({
tooltipShown: true,
});
const showTooltip = () => {
setTooltipShown(true);
setTimeout(() => {
this.setState({
tooltipShown: false,
});
setTooltipShown(false);
}, 1500);
}
}
};
return props.children({ renderCopyButton: renderCopyButton }) as JSX.Element;
};

View File

@ -0,0 +1,68 @@
import * as React from 'react';
import styled from '../../styled-components';
import { ArrowIconProps, DropdownProps, DropdownOption } from './types';
const ArrowSvg = ({ className, style }: ArrowIconProps): JSX.Element => (
<svg
className={className}
style={style}
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="6 9 12 15 18 9" />
</svg>
);
const ArrowIcon = styled(ArrowSvg)`
position: absolute;
pointer-events: none;
z-index: 1;
top: 50%;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
right: 8px;
margin: auto;
text-align: center;
polyline {
color: ${props => props.variant === 'dark' && 'white'};
}
`;
const DropdownComponent = (props: DropdownProps): JSX.Element => {
const { options, onChange, placeholder, value = '', variant, className } = props;
const handleOnChange = event => {
const { selectedIndex } = event.target;
const index = placeholder ? selectedIndex - 1 : selectedIndex;
onChange(options[index]);
};
return (
<div className={className}>
<ArrowIcon variant={variant} />
<select onChange={handleOnChange} value={value} className="dropdown-select">
{placeholder && (
<option disabled hidden value={placeholder}>
{placeholder}
</option>
)}
{options.map(({ idx, value, title }: DropdownOption, index) => (
<option key={idx || value + index} value={value}>
{title || value}
</option>
))}
</select>
<label>{value}</label>
</div>
);
};
export const Dropdown = React.memo<DropdownProps>(DropdownComponent);

View File

@ -0,0 +1,2 @@
export * from './styled';
export * from './types';

View File

@ -0,0 +1,94 @@
import styled from 'styled-components';
import { Dropdown as DropdownComponent } from './Dropdown';
export const Dropdown = styled(DropdownComponent)<{
fullWidth?: boolean;
}>`
label {
box-sizing: border-box;
min-width: 100px;
outline: none;
display: inline-block;
font-family: ${props => props.theme.typography.headings.fontFamily};
color: ${({ theme }) => theme.colors.text.primary};
vertical-align: bottom;
width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')};
text-transform: none;
padding: 0 22px 0 4px;
font-size: 0.929em;
line-height: 1.5em;
font-family: inherit;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.dropdown-select {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
border: none;
appearance: none;
cursor: pointer;
color: ${({ theme }) => theme.colors.text.primary};
line-height: inherit;
font-family: inherit;
}
box-sizing: border-box;
min-width: 100px;
outline: none;
display: inline-block;
border-radius: 2px;
border: 1px solid rgba(38, 50, 56, 0.5);
vertical-align: bottom;
padding: 2px 0px 2px 6px;
position: relative;
width: auto;
background: white;
color: #263238;
font-family: ${props => props.theme.typography.headings.fontFamily};
font-size: 0.929em;
line-height: 1.5em;
cursor: pointer;
transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
&:hover,
&:focus-within {
border: 1px solid ${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};
}
`;
export const SimpleDropdown = styled(Dropdown)`
margin-left: 10px;
text-transform: none;
font-size: 0.969em;
font-size: 1em;
border: none;
padding: 0 1.2em 0 0;
background: transparent;
&:hover,
&:focus-within {
border: none;
box-shadow: none;
label {
color: ${props => props.theme.colors.primary.main};
text-shadow: 0px 0px 0px ${props => props.theme.colors.primary.main};
}
}
`;
export const MimeLabel = styled.span`
margin-left: 10px;
text-transform: none;
font-size: 0.929em;
color: black;
`;

View File

@ -0,0 +1,25 @@
export interface DropdownOption {
idx?: number;
value: string;
title?: string;
serverUrl?: string;
label?: string;
}
export interface DropdownProps {
options: DropdownOption[];
onChange: (option: DropdownOption) => void;
ariaLabel?: string;
className?: string;
placeholder?: string;
value?: string;
dense?: boolean;
fullWidth?: boolean;
variant?: 'dark' | 'light';
}
export interface ArrowIconProps {
className?: string;
variant?: 'light' | 'dark';
style?: React.CSSProperties;
}

View File

@ -1,143 +0,0 @@
import Dropdown from '@redocly/react-dropdown-aria';
import styled from '../styled-components';
export interface DropdownOption {
idx: number;
value: string;
}
export interface DropdownProps {
options: DropdownOption[];
value: string;
onChange: (option: DropdownOption) => void;
ariaLabel: string;
}
export const StyledDropdown = styled(Dropdown)`
&& {
box-sizing: border-box;
min-width: 100px;
outline: none;
display: inline-block;
border-radius: 2px;
border: 1px solid rgba(38, 50, 56, 0.5);
vertical-align: bottom;
padding: 2px 0px 2px 6px;
position: relative;
width: auto;
background: white;
color: #263238;
font-family: ${props => props.theme.typography.headings.fontFamily};
font-size: 0.929em;
line-height: 1.5em;
cursor: pointer;
transition: border 0.25s ease, color 0.25s ease, box-shadow 0.25s ease;
&:hover,
&:focus-within {
border: 1px solid ${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};
}
.dropdown-selector {
display: inline-flex;
padding: 0;
height: auto;
padding-right: 20px;
position: relative;
margin-bottom: 5px;
}
.dropdown-selector-value {
font-family: ${props => props.theme.typography.headings.fontFamily};
position: relative;
font-size: 0.929em;
width: 100%;
line-height: 1;
vertical-align: middle;
color: #263238;
left: 0;
transition: color 0.25s ease, text-shadow 0.25s ease;
}
.dropdown-arrow {
position: absolute;
right: 3px;
top: 50%;
transform: translateY(-50%);
border-color: ${props => props.theme.colors.primary.main} transparent transparent;
border-style: solid;
border-width: 0.35em 0.35em 0;
width: 0;
svg {
display: none;
}
}
.dropdown-selector-content {
position: absolute;
margin-top: 2px;
left: -2px;
right: 0;
z-index: 10;
min-width: 100px;
background: white;
border: 1px solid rgba(38, 50, 56, 0.2);
box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);
max-height: 220px;
overflow: auto;
}
.dropdown-option {
font-size: 0.9em;
color: #263238;
cursor: pointer;
padding: 0.4em;
background-color: #ffffff;
&[aria-selected='true'] {
background-color: rgba(0, 0, 0, 0.05);
}
&:hover {
background-color: rgba(38, 50, 56, 0.12);
}
}
input {
cursor: pointer;
height: 1px;
background-color: transparent;
}
}
`;
export const SimpleDropdown = styled(StyledDropdown)`
&& {
margin-left: 10px;
text-transform: none;
font-size: 0.969em;
font-size: 1em;
border: none;
padding: 0 1.2em 0 0;
background: transparent;
&:hover,
&:focus-within {
border: none;
box-shadow: none;
.dropdown-selector-value {
color: ${props => props.theme.colors.primary.main};
text-shadow: 0px 0px 0px ${props => props.theme.colors.primary.main};
}
}
}
`;
export const MimeLabel = styled.span`
margin-left: 10px;
text-transform: none;
font-size: 0.929em;
color: black;
`;

View File

@ -1,4 +1,4 @@
import styled, { extensionsHook, media } from '../styled-components';
import styled, { extensionsHook, media, css } from '../styled-components';
import { deprecatedCss } from './mixins';
export const PropertiesTableCaption = styled.caption`
@ -72,7 +72,26 @@ export const PropertyNameCell = styled(PropertyCell)`
${deprecatedCss};
}
${({ kind }) => (kind !== 'field' ? 'font-style: italic' : '')};
${({ kind }) =>
kind === 'patternProperties' &&
css`
> span.property-name {
display: inline-table;
white-space: break-spaces;
margin-right: 20px;
::before,
::after {
content: '/';
filter: opacity(0.2);
}
}
`}
${({ kind = '' }) =>
['field', 'additionalProperties', 'patternProperties'].includes(kind)
? ''
: 'font-style: italic'};
${extensionsHook('PropertyNameCell')};
`;

View File

@ -1,6 +1,6 @@
import { transparentize } from 'polished';
import styled, { extensionsHook } from '../styled-components';
import styled, { extensionsHook, css } from '../styled-components';
import { PropertyNameCell } from './fields-layout';
import { ShelfIcon } from './shelfs';
@ -17,6 +17,27 @@ export const ClickablePropertyNameCell = styled(PropertyNameCell)`
&:focus {
font-weight: ${({ theme }) => theme.typography.fontWeightBold};
}
${({ kind }) =>
kind === 'patternProperties' &&
css`
display: inline-flex;
margin-right: 20px;
> span.property-name {
white-space: break-spaces;
text-align: left;
::before,
::after {
content: '/';
filter: opacity(0.2);
}
}
> svg {
align-self: center;
}
`}
}
${ShelfIcon} {
height: ${({ theme }) => theme.schema.arrow.size};
@ -56,6 +77,10 @@ export const RequiredLabel = styled(FieldLabel.withComponent('div'))`
line-height: 1;
`;
export const PropertyLabel = styled(RequiredLabel)`
color: ${props => props.theme.colors.primary.light};
`;
export const RecursiveLabel = styled(FieldLabel)`
color: ${({ theme }) => theme.colors.warning.main};
font-size: 13px;
@ -71,6 +96,7 @@ export const PatternLabel = styled(FieldLabel)`
export const ExampleValue = styled(FieldLabel)`
border-radius: 2px;
word-break: break-word;
${({ theme }) => `
background-color: ${transparentize(0.95, theme.colors.text.primary)};
color: ${transparentize(0.1, theme.colors.text.primary)};

View File

@ -4,8 +4,8 @@ export * from './linkify';
export * from './shelfs';
export * from './fields-layout';
export * from './schema';
export * from './dropdown';
export * from './mixins';
export * from './tabs';
export * from './samples';
export * from './perfect-scrollbar';
export * from './Dropdown';

View File

@ -67,7 +67,7 @@ function navigate(history: HistoryService, event: React.MouseEvent<HTMLAnchorEle
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
history.replace(to);
history.replace(encodeURI(to));
}
}

View File

@ -8,35 +8,34 @@ const directionMap = {
down: '0',
};
class IntShelfIcon extends React.PureComponent<{
const IntShelfIcon = (props: {
className?: string;
float?: 'left' | 'right';
size?: string;
color?: string;
direction: 'left' | 'right' | 'up' | 'down';
style?: React.CSSProperties;
}> {
render() {
return (
<svg
className={this.props.className}
style={this.props.style}
version="1.1"
viewBox="0 0 24 24"
x="0"
xmlns="http://www.w3.org/2000/svg"
y="0"
aria-hidden="true"
>
<polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 " />
</svg>
);
}
}
}): JSX.Element => {
return (
<svg
className={props.className}
style={props.style}
version="1.1"
viewBox="0 0 24 24"
x="0"
xmlns="http://www.w3.org/2000/svg"
y="0"
aria-hidden="true"
>
<polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 " />
</svg>
);
};
export const ShelfIcon = styled(IntShelfIcon)`
height: ${props => props.size || '18px'};
width: ${props => props.size || '18px'};
min-width: ${props => props.size || '18px'};
vertical-align: middle;
float: ${props => props.float || ''};
transition: transform 0.2s ease-out;

View File

@ -21,7 +21,14 @@ export class CallbackSamples extends React.Component<CallbackSamplesProps> {
context: RedocNormalizedOptions;
private renderDropdown = props => {
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
return (
<DropdownOrLabel
Label={MimeLabel}
Dropdown={InvertedSimpleDropdown}
{...props}
variant="dark"
/>
);
};
render() {

View File

@ -17,20 +17,18 @@ export interface CallbackTitleProps {
onClick?: () => void;
}
export class CallbackTitle extends React.PureComponent<CallbackTitleProps> {
render() {
const { name, opened, className, onClick, httpVerb, deprecated } = this.props;
export const CallbackTitle = (props: CallbackTitleProps) => {
const { name, opened, className, onClick, httpVerb, deprecated } = props;
return (
<CallbackTitleWrapper className={className} onClick={onClick || undefined}>
<OperationBadgeStyled type={httpVerb}>{shortenHTTPVerb(httpVerb)}</OperationBadgeStyled>
<ShelfIcon size={'1.5em'} direction={opened ? 'down' : 'right'} float={'left'} />
<CallbackName deprecated={deprecated}>{name}</CallbackName>
{deprecated ? <Badge type="warning"> {l('deprecated')} </Badge> : null}
</CallbackTitleWrapper>
);
}
}
return (
<CallbackTitleWrapper className={className} onClick={onClick || undefined}>
<OperationBadgeStyled type={httpVerb}>{shortenHTTPVerb(httpVerb)}</OperationBadgeStyled>
<ShelfIcon size={'1.5em'} direction={opened ? 'down' : 'right'} float={'left'} />
<CallbackName deprecated={deprecated}>{name}</CallbackName>
{deprecated ? <Badge type="warning"> {l('deprecated')} </Badge> : null}
</CallbackTitleWrapper>
);
};
const CallbackTitleWrapper = styled.button`
border: 0;

View File

@ -4,8 +4,8 @@ import * as React from 'react';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
import { ContentItemModel } from '../../services/MenuBuilder';
import { GroupModel, OperationModel } from '../../services/models';
import type { ContentItemModel } from '../../services';
import type { GroupModel, OperationModel } from '../../services/models';
import { Operation } from '../Operation/Operation';
@observer
@ -79,7 +79,11 @@ export class SectionItem extends React.Component<ContentItemProps> {
</Header>
</MiddlePanel>
</Row>
<AdvancedMarkdown source={description || ''} htmlWrap={middlePanelWrap} />
<AdvancedMarkdown
parentId={this.props.item.id}
source={description || ''}
htmlWrap={middlePanelWrap}
/>
{externalDocs && (
<Row>
<MiddlePanel>

View File

@ -1,10 +1,18 @@
import * as React from 'react';
import { StyledComponent } from 'styled-components';
import { DropdownProps, MimeLabel, SimpleDropdown } from '../../common-elements/dropdown';
import { DropdownProps, MimeLabel, SimpleDropdown } from '../../common-elements/Dropdown';
export interface DropdownOrLabelProps extends DropdownProps {
Label?: React.ComponentClass;
Dropdown?: React.ComponentClass;
Label?: StyledComponent<any, any, Record<string, any>, never>;
Dropdown?: StyledComponent<
React.NamedExoticComponent<DropdownProps>,
any,
{
fullWidth?: boolean | undefined;
},
never
>;
}
export function DropdownOrLabel(props: DropdownOrLabelProps): JSX.Element {
@ -12,5 +20,5 @@ export function DropdownOrLabel(props: DropdownOrLabelProps): JSX.Element {
if (props.options.length === 1) {
return <Label>{props.options[0].value}</Label>;
}
return <Dropdown {...props} searchable={false} />;
return <Dropdown {...props} />;
}

View File

@ -59,8 +59,8 @@ export const ServersOverlay = styled.div<{ expanded: boolean }>`
position: absolute;
width: 100%;
z-index: 100;
background: #fafafa;
color: #263238;
background: ${props => props.theme.rightPanel.servers.overlay.backgroundColor};
color: ${props => props.theme.rightPanel.servers.overlay.textColor};
box-sizing: border-box;
box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.33);
overflow: hidden;
@ -78,7 +78,7 @@ export const ServerItem = styled.div`
export const ServerUrl = styled.div`
padding: 5px;
border: 1px solid #ccc;
background: #fff;
background: ${props => props.theme.rightPanel.servers.url.backgroundColor};
word-break: break-all;
color: ${props => props.theme.colors.primary.main};
> span {

View File

@ -37,6 +37,6 @@ export class ErrorBoundary extends React.Component<
</ErrorWrapper>
);
}
return React.Children.only(this.props.children);
return <React.Fragment>{React.Children.only(this.props.children)}</React.Fragment>;
}
}

View File

@ -4,9 +4,20 @@ import { ConstraintsView } from './FieldContstraints';
import { Pattern } from './Pattern';
import { SchemaModel } from '../../services';
import styled from '../../styled-components';
import { OptionsContext } from '../OptionsProvider';
export function ArrayItemDetails({ schema }: { schema: SchemaModel }) {
if (!schema || (schema.type === 'string' && !schema.constraints.length)) return null;
const { hideSchemaPattern } = React.useContext(OptionsContext);
if (
!schema ||
(schema.type === 'string' && !schema.constraints.length) ||
((!schema?.pattern || hideSchemaPattern) &&
!schema.items &&
!schema.displayFormat &&
!schema.constraints.length) // return null for cases where all constraints are empty
) {
return null;
}
return (
<Wrapper>

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { FieldLabel, ExampleValue } from '../../common-elements/fields';
import { getSerializedValue } from '../../utils';
import { getSerializedValue, isArray } from '../../utils';
import { l } from '../../services/Labels';
import { FieldModel } from '../../services';
@ -15,22 +15,31 @@ export function Examples({ field }: { field: FieldModel }) {
return (
<>
<FieldLabel> {l('examples')}: </FieldLabel>
<ExamplesList>
{Object.values(field.examples).map((example, idx) => {
{isArray(field.examples) ? (
field.examples.map((example, idx) => {
const value = getSerializedValue(field, example);
const stringifyValue = field.in ? String(value) : JSON.stringify(value);
return (
<li key={idx}>
<React.Fragment key={idx}>
<ExampleValue>{stringifyValue}</ExampleValue>{' '}
</React.Fragment>
);
})
) : (
<ExamplesList>
{Object.values(field.examples).map((example, idx) => (
<li key={idx + example.value}>
<ExampleValue>{getSerializedValue(field, example.value)}</ExampleValue> -{' '}
{example.summary || example.description}
</li>
);
})}
</ExamplesList>
))}
</ExamplesList>
)}
</>
);
}
const ExamplesList = styled.ul`
margin-top: 1em;
padding-left: 0;
list-style-position: inside;
list-style-position: outside;
`;

View File

@ -1,9 +1,12 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { ClickablePropertyNameCell, RequiredLabel } from '../../common-elements/fields';
import {
ClickablePropertyNameCell,
PropertyLabel,
RequiredLabel,
} from '../../common-elements/fields';
import { FieldDetails } from './FieldDetails';
import {
InnerPropertiesWrap,
PropertyBullet,
@ -11,11 +14,11 @@ import {
PropertyDetailsCell,
PropertyNameCell,
} from '../../common-elements/fields-layout';
import { ShelfIcon } from '../../common-elements/';
import { Schema } from '../Schema/Schema';
import { FieldModel } from '../../services/models';
import { Schema, SchemaOptions } from '../Schema/Schema';
import type { SchemaOptions } from '../Schema/Schema';
import type { FieldModel } from '../../services/models';
export interface FieldProps extends SchemaOptions {
className?: string;
@ -46,12 +49,20 @@ export class Field extends React.Component<FieldProps> {
};
render() {
const { className, field, isLast, expandByDefault } = this.props;
const { className = '', field, isLast, expandByDefault } = this.props;
const { name, deprecated, required, kind } = field;
const withSubSchema = !field.schema.isPrimitive && !field.schema.isCircular;
const expanded = field.expanded === undefined ? expandByDefault : field.expanded;
const labels = (
<>
{kind === 'additionalProperties' && <PropertyLabel>additional property</PropertyLabel>}
{kind === 'patternProperties' && <PropertyLabel>pattern property</PropertyLabel>}
{required && <RequiredLabel>required</RequiredLabel>}
</>
);
const paramName = withSubSchema ? (
<ClickablePropertyNameCell
className={deprecated ? 'deprecated' : ''}
@ -64,16 +75,16 @@ export class Field extends React.Component<FieldProps> {
onKeyPress={this.handleKeyPress}
aria-label="expand properties"
>
<span>{name}</span>
<span className="property-name">{name}</span>
<ShelfIcon direction={expanded ? 'down' : 'right'} />
</button>
{required && <RequiredLabel> required </RequiredLabel>}
{labels}
</ClickablePropertyNameCell>
) : (
<PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}>
<PropertyBullet />
<span>{name}</span>
{required && <RequiredLabel> required </RequiredLabel>}
<span className="property-name">{name}</span>
{labels}
</PropertyNameCell>
);

View File

@ -1,4 +1,5 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import {
RecursiveLabel,
@ -7,7 +8,7 @@ import {
TypePrefix,
TypeTitle,
} from '../../common-elements/fields';
import { getSerializedValue } from '../../utils';
import { getSerializedValue, isObject } from '../../utils';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { Markdown } from '../Markdown/Markdown';
import { EnumValues } from './EnumValues';
@ -25,7 +26,7 @@ import { OptionsContext } from '../OptionsProvider';
import { Pattern } from './Pattern';
import { ArrayItemDetails } from './ArrayItemDetails';
function FieldDetailsComponent(props: FieldProps) {
export const FieldDetailsComponent = observer((props: FieldProps) => {
const { enumSkipQuotes, hideSchemaTitles } = React.useContext(OptionsContext);
const { showExamples, field, renderDiscriminatorSwitch } = props;
@ -60,6 +61,10 @@ function FieldDetailsComponent(props: FieldProps) {
return null;
}, [field, showExamples]);
const defaultValue = isObject(schema.default)
? getSerializedValue(field, schema.default).replace(`${field.name}=`, '')
: schema.default;
return (
<div>
<div>
@ -100,7 +105,7 @@ function FieldDetailsComponent(props: FieldProps) {
<Badge type="warning"> {l('deprecated')} </Badge>
</div>
)}
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={schema.default} />
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={defaultValue} />
{!renderDiscriminatorSwitch && (
<EnumValues isArrayType={isArrayType} values={schema.enum} />
)}{' '}
@ -117,6 +122,6 @@ function FieldDetailsComponent(props: FieldProps) {
{(_const && <FieldDetail label={l('const') + ':'} value={_const} />) || null}
</div>
);
}
});
export const FieldDetails = React.memo<FieldProps>(FieldDetailsComponent);

View File

@ -1,7 +1,7 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { DropdownProps, DropdownOption } from '../../common-elements/dropdown';
import { DropdownProps, DropdownOption } from '../../common-elements/Dropdown';
import { DropdownLabel, DropdownWrapper } from '../PayloadSamples/styled.elements';
export interface GenericChildrenSwitcherProps<T> {
@ -32,8 +32,8 @@ export class GenericChildrenSwitcher<T> extends React.Component<
};
}
switchItem = ({ idx }) => {
if (this.props.items) {
switchItem = ({ idx }: DropdownOption) => {
if (this.props.items && idx !== undefined) {
this.setState({
activeItemIdx: idx,
});

View File

@ -19,37 +19,43 @@ const JsonViewerWrap = styled.div`
}
`;
class Json extends React.PureComponent<JsonProps> {
node: HTMLDivElement;
const Json = (props: JsonProps) => {
const [node, setNode] = React.useState<HTMLDivElement>();
render() {
return <CopyButtonWrapper data={this.props.data}>{this.renderInner}</CopyButtonWrapper>;
}
const renderInner = ({ renderCopyButton }) => {
const showFoldingButtons =
props.data &&
Object.values(props.data).some(value => typeof value === 'object' && value !== null);
renderInner = ({ renderCopyButton }) => (
<JsonViewerWrap>
<SampleControls>
{renderCopyButton()}
<button onClick={this.expandAll}> Expand all </button>
<button onClick={this.collapseAll}> Collapse all </button>
</SampleControls>
<OptionsContext.Consumer>
{options => (
<PrismDiv
className={this.props.className}
// tslint:disable-next-line
ref={node => (this.node = node!)}
dangerouslySetInnerHTML={{
__html: jsonToHTML(this.props.data, options.jsonSampleExpandLevel),
}}
/>
)}
</OptionsContext.Consumer>
</JsonViewerWrap>
);
return (
<JsonViewerWrap>
<SampleControls>
{renderCopyButton()}
{showFoldingButtons && (
<>
<button onClick={expandAll}> Expand all </button>
<button onClick={collapseAll}> Collapse all </button>
</>
)}
</SampleControls>
<OptionsContext.Consumer>
{options => (
<PrismDiv
className={props.className}
// tslint:disable-next-line
ref={node => setNode(node!)}
dangerouslySetInnerHTML={{
__html: jsonToHTML(props.data, options.jsonSampleExpandLevel),
}}
/>
)}
</OptionsContext.Consumer>
</JsonViewerWrap>
);
};
expandAll = () => {
const elements = this.node.getElementsByClassName('collapsible');
const expandAll = () => {
const elements = node?.getElementsByClassName('collapsible');
for (const collapsed of Array.prototype.slice.call(elements)) {
const parentNode = collapsed.parentNode as Element;
parentNode.classList.remove('collapsed');
@ -57,8 +63,8 @@ class Json extends React.PureComponent<JsonProps> {
}
};
collapseAll = () => {
const elements = this.node.getElementsByClassName('collapsible');
const collapseAll = () => {
const elements = node?.getElementsByClassName('collapsible');
// skip first item to avoid collapsing whole object/array
const elementsArr = Array.prototype.slice.call(elements, 1);
@ -69,7 +75,7 @@ class Json extends React.PureComponent<JsonProps> {
}
};
collapseElement = (target: HTMLElement) => {
const collapseElement = (target: HTMLElement) => {
let collapsed;
if (target.className === 'collapser') {
collapsed = target.parentElement!.getElementsByClassName('collapsible')[0];
@ -83,26 +89,27 @@ class Json extends React.PureComponent<JsonProps> {
}
};
clickListener = (event: MouseEvent) => {
this.collapseElement(event.target as HTMLElement);
};
const clickListener = React.useCallback((event: MouseEvent) => {
collapseElement(event.target as HTMLElement);
}, []);
focusListener = (event: KeyboardEvent) => {
const focusListener = React.useCallback((event: KeyboardEvent) => {
if (event.key === 'Enter') {
this.collapseElement(event.target as HTMLElement);
collapseElement(event.target as HTMLElement);
}
};
}, []);
componentDidMount() {
this.node!.addEventListener('click', this.clickListener);
this.node!.addEventListener('focus', this.focusListener);
}
React.useEffect(() => {
node?.addEventListener('click', clickListener);
node?.addEventListener('focus', focusListener);
return () => {
node?.removeEventListener('click', clickListener);
node?.removeEventListener('focus', focusListener);
};
}, [clickListener, focusListener, node]);
componentWillUnmount() {
this.node!.removeEventListener('click', this.clickListener);
this.node!.removeEventListener('focus', this.focusListener);
}
}
return <CopyButtonWrapper data={props.data}>{renderInner}</CopyButtonWrapper>;
};
export const JsonViewer = styled(Json)`
${jsonStyles};

View File

@ -9,6 +9,7 @@ import { StoreConsumer } from '../StoreBuilder';
export interface AdvancedMarkdownProps extends BaseMarkdownProps {
htmlWrap?: (part: JSX.Element) => JSX.Element;
parentId?: string;
}
export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
@ -28,7 +29,7 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
throw new Error('When using components in markdown, store prop must be provided');
}
const renderer = new MarkdownRenderer(options);
const renderer = new MarkdownRenderer(options, this.props.parentId);
const parts = renderer.renderMdWithComponents(source);
if (!parts.length) {
@ -42,7 +43,8 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
{ key: idx },
);
}
return <part.component key={idx} {...{ ...part.props, ...part.propsSelector(store) }} />;
const PartComponent = part.component as React.FunctionComponent;
return <PartComponent key={idx} {...{ ...part.props, ...part.propsSelector(store) }} />;
});
}
}

View File

@ -6,7 +6,7 @@ import { StyledComponent } from 'styled-components';
export const linksCss = css`
a {
text-decoration: none;
text-decoration: ${props => props.theme.typography.links.textDecoration};
color: ${props => props.theme.typography.links.color};
&:visited {
@ -15,6 +15,7 @@ export const linksCss = css`
&:hover {
color: ${props => props.theme.typography.links.hover};
text-decoration: ${props => props.theme.typography.links.hoverTextDecoration};
}
}
`;

View File

@ -1,7 +1,7 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { DropdownProps } from '../../common-elements/dropdown';
import { DropdownOption, DropdownProps } from '../../common-elements/Dropdown';
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
import { DropdownLabel, DropdownWrapper } from '../PayloadSamples/styled.elements';
@ -20,8 +20,8 @@ export interface MediaTypesSwitchProps {
@observer
export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
switchMedia = ({ idx }) => {
if (this.props.content) {
switchMedia = ({ idx }: DropdownOption) => {
if (this.props.content && idx !== undefined) {
this.props.content.activate(idx);
}
};

View File

@ -17,6 +17,7 @@ import { RequestSamples } from '../RequestSamples/RequestSamples';
import { ResponsesList } from '../Responses/ResponsesList';
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
import { SECTION_ATTR } from '../../services';
const Description = styled.div`
margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px;
@ -26,48 +27,48 @@ export interface OperationProps {
operation: OperationModel;
}
@observer
export class Operation extends React.Component<OperationProps> {
render() {
const { operation } = this.props;
const { name: summary, description, deprecated, externalDocs, isWebhook } = operation;
const hasDescription = !!(description || externalDocs);
return (
<OptionsContext.Consumer>
{options => (
<Row>
<MiddlePanel>
<H2>
<ShareLink to={operation.id} />
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
{isWebhook && <Badge type="primary"> Webhook </Badge>}
</H2>
{options.pathInMiddlePanel && !isWebhook && (
<Endpoint operation={operation} inverted={true} />
export const Operation = observer(({ operation }: OperationProps): JSX.Element => {
const { name: summary, description, deprecated, externalDocs, isWebhook, httpVerb } = operation;
const hasDescription = !!(description || externalDocs);
const { showWebhookVerb } = React.useContext(OptionsContext);
return (
<OptionsContext.Consumer>
{options => (
<Row {...{ [SECTION_ATTR]: operation.operationHash }} id={operation.operationHash}>
<MiddlePanel>
<H2>
<ShareLink to={operation.id} />
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
{isWebhook && (
<Badge type="primary">
{' '}
Webhook {showWebhookVerb && httpVerb && '| ' + httpVerb.toUpperCase()}
</Badge>
)}
{hasDescription && (
<Description>
{description !== undefined && <Markdown source={description} />}
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
</Description>
)}
<Extensions extensions={operation.extensions} />
<SecurityRequirements securities={operation.security} />
<Parameters parameters={operation.parameters} body={operation.requestBody} />
<ResponsesList responses={operation.responses} />
<CallbacksList callbacks={operation.callbacks} />
</MiddlePanel>
<DarkRightPanel>
{!options.pathInMiddlePanel && !isWebhook && <Endpoint operation={operation} />}
<RequestSamples operation={operation} />
<ResponseSamples operation={operation} />
<CallbackSamples callbacks={operation.callbacks} />
</DarkRightPanel>
</Row>
)}
</OptionsContext.Consumer>
);
}
}
</H2>
{options.pathInMiddlePanel && !isWebhook && (
<Endpoint operation={operation} inverted={true} />
)}
{hasDescription && (
<Description>
{description !== undefined && <Markdown source={description} />}
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
</Description>
)}
<Extensions extensions={operation.extensions} />
<SecurityRequirements securities={operation.security} />
<Parameters parameters={operation.parameters} body={operation.requestBody} />
<ResponsesList responses={operation.responses} />
<CallbacksList callbacks={operation.callbacks} />
</MiddlePanel>
<DarkRightPanel>
{!options.pathInMiddlePanel && !isWebhook && <Endpoint operation={operation} />}
<RequestSamples operation={operation} />
<ResponseSamples operation={operation} />
<CallbackSamples callbacks={operation.callbacks} />
</DarkRightPanel>
</Row>
)}
</OptionsContext.Consumer>
);
});

View File

@ -10,6 +10,7 @@ import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema';
import { Markdown } from '../Markdown/Markdown';
import { ConstraintsView } from '../Fields/FieldContstraints';
function safePush(obj, prop, item) {
if (!obj[prop]) {
@ -79,6 +80,9 @@ export function BodyContent(props: {
return (
<>
{description !== undefined && <Markdown source={description} />}
{schema?.type === 'object' && (
<ConstraintsView constraints={schema?.constraints || []} />
)}
<Schema
skipReadOnly={isRequestType}
skipWriteOnly={!isRequestType}

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import styled from '../../styled-components';
import { DropdownProps } from '../../common-elements';
import { DropdownOption, DropdownProps } from '../../common-elements';
import { MediaTypeModel } from '../../services/models';
import { Markdown } from '../Markdown/Markdown';
import { Example } from './Example';
@ -21,10 +21,12 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps, Media
state = {
activeIdx: 0,
};
switchMedia = ({ idx }) => {
this.setState({
activeIdx: idx,
});
switchMedia = ({ idx }: DropdownOption) => {
if (idx !== undefined) {
this.setState({
activeIdx: idx,
});
}
};
render() {
const { activeIdx } = this.state;

View File

@ -33,6 +33,13 @@ export class PayloadSamples extends React.Component<PayloadSamplesProps> {
}
private renderDropdown = props => {
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
return (
<DropdownOrLabel
Label={MimeLabel}
Dropdown={InvertedSimpleDropdown}
{...props}
variant="dark"
/>
);
};
}

View File

@ -1,6 +1,6 @@
import { transparentize } from 'polished';
import styled from '../../styled-components';
import { StyledDropdown } from '../../common-elements';
import { Dropdown } from '../../common-elements/Dropdown';
export const MimeLabel = styled.div`
padding: 0.9em;
@ -27,46 +27,27 @@ export const DropdownWrapper = styled.div`
position: relative;
`;
export const InvertedSimpleDropdown = styled(StyledDropdown)`
&& {
margin-left: 10px;
text-transform: none;
font-size: 0.929em;
margin: 0 0 10px 0;
display: block;
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
export const InvertedSimpleDropdown = styled(Dropdown)`
label {
color: ${({ theme }) => theme.rightPanel.textColor};
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
font-size: 1em;
text-transform: none;
border: none;
}
margin: 0 0 10px 0;
display: block;
background-color: ${({ theme }) => transparentize(0.6, theme.rightPanel.backgroundColor)};
border: none;
padding: 0.9em 1.6em 0.9em 0.9em;
box-shadow: none;
&:hover,
&:focus-within {
border: none;
padding: 0.9em 1.6em 0.9em 0.9em;
box-shadow: none;
&:hover,
&:focus-within {
border: none;
box-shadow: none;
}
&:focus-within {
background-color: ${({ theme }) => transparentize(0.3, theme.rightPanel.backgroundColor)};
}
.dropdown-arrow {
border-top-color: ${({ theme }) => theme.rightPanel.textColor};
}
.dropdown-selector-value {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
color: ${({ theme }) => theme.rightPanel.textColor};
}
.dropdown-selector-content {
margin: 0;
margin-top: 2px;
.dropdown-option {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
background-color: ${({ theme }) => transparentize(0.3, theme.rightPanel.backgroundColor)};
}
`;

View File

@ -27,13 +27,19 @@ export const RedocStandalone = function (props: RedocStandaloneProps) {
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 (
<ErrorBoundary>
<StoreBuilder spec={spec} specUrl={specUrl} options={options} onLoaded={onLoaded}>
<StoreBuilder
spec={spec ? { ...spec } : undefined}
specUrl={specUrl}
options={options}
onLoaded={onLoaded}
>
{({ loading, store }) =>
!loading ? (
<Redoc store={store!} />

View File

@ -10,6 +10,7 @@ import { Schema } from '../Schema';
import { Extensions } from '../Fields/Extensions';
import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders';
import { ConstraintsView } from '../Fields/FieldContstraints';
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
render() {
@ -21,7 +22,14 @@ export class ResponseDetails extends React.PureComponent<{ response: ResponseMod
<ResponseHeaders headers={headers} />
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
{({ schema }) => {
return <Schema skipWriteOnly={true} key="schema" schema={schema} />;
return (
<>
{schema?.type === 'object' && (
<ConstraintsView constraints={schema?.constraints || []} />
)}
<Schema skipWriteOnly={true} key="schema" schema={schema} />
</>
);
}}
</MediaTypesSwitch>
</>

View File

@ -6,6 +6,7 @@ import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements';
import styled from '../../styled-components';
import { humanizeConstraints } from '../../utils';
import { TypeName } from '../../common-elements/fields';
import { ObjectSchema } from './ObjectSchema';
const PaddedSchema = styled.div`
padding-left: ${({ theme }) => theme.spacing.unit * 2}px;
@ -21,6 +22,9 @@ export class ArraySchema extends React.PureComponent<SchemaProps> {
? ''
: `(${humanizeConstraints(schema)})`;
if (schema.fields) {
return <ObjectSchema {...(this.props as any)} level={this.props.level} />;
}
if (schema.displayType && !itemsSchema && !minMaxItems.length) {
return (
<div>

View File

@ -1,7 +1,7 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { DropdownOption, StyledDropdown } from '../../common-elements/dropdown';
import { DropdownOption, Dropdown } from '../../common-elements/Dropdown';
import { SchemaModel } from '../../services/models';
@observer
@ -43,7 +43,7 @@ export class DiscriminatorDropdown extends React.Component<{
this.sortOptions(options, enumValues);
return (
<StyledDropdown
<Dropdown
value={activeValue}
options={options}
onChange={this.changeActiveChild}
@ -53,6 +53,8 @@ export class DiscriminatorDropdown extends React.Component<{
}
changeActiveChild = (option: DropdownOption) => {
this.props.parent.activateOneOf(option.idx);
if (option.idx !== undefined) {
this.props.parent.activateOneOf(option.idx);
}
};
}

View File

@ -8,6 +8,7 @@ import {
} from '../../common-elements/schema';
import { Badge } from '../../common-elements/shelfs';
import { SchemaModel } from '../../services/models';
import { ConstraintsView } from '../Fields/FieldContstraints';
import { Schema, SchemaProps } from './Schema';
export interface OneOfButtonProps {
@ -47,6 +48,8 @@ export class OneOfSchema extends React.Component<SchemaProps> {
if (oneOf === undefined) {
return null;
}
const activeSchema = oneOf[schema.activeOneOf];
return (
<div>
<OneOfLabel> {schema.oneOfType} </OneOfLabel>
@ -58,7 +61,8 @@ export class OneOfSchema extends React.Component<SchemaProps> {
<div>
{oneOf[schema.activeOneOf].deprecated && <Badge type="warning">Deprecated</Badge>}
</div>
<Schema {...this.props} schema={oneOf[schema.activeOneOf]} />
<ConstraintsView constraints={activeSchema.constraints} />
<Schema {...this.props} schema={activeSchema} />
</div>
);
}

View File

@ -0,0 +1,16 @@
import * as React from 'react';
import { observer } from 'mobx-react';
import { RecursiveLabel, TypeName, TypeTitle } from '../../common-elements/fields';
import { l } from '../../services/Labels';
import type { SchemaProps } from '.';
export const RecursiveSchema = observer(({ schema }: SchemaProps) => {
return (
<div>
<TypeName>{schema.displayType}</TypeName>
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
<RecursiveLabel> {l('recursive')} </RecursiveLabel>
</div>
);
});

View File

@ -1,7 +1,6 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { RecursiveLabel, TypeName, TypeTitle } from '../../common-elements/fields';
import { FieldDetails } from '../Fields/FieldDetails';
import { FieldModel, SchemaModel } from '../../services/models';
@ -9,8 +8,9 @@ import { FieldModel, SchemaModel } from '../../services/models';
import { ArraySchema } from './ArraySchema';
import { ObjectSchema } from './ObjectSchema';
import { OneOfSchema } from './OneOfSchema';
import { RecursiveSchema } from './RecursiveSchema';
import { l } from '../../services/Labels';
import { isArray } from '../../utils/helpers';
export interface SchemaOptions {
showTitle?: boolean;
@ -35,13 +35,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
const { type, oneOf, discriminatorProp, isCircular } = schema;
if (isCircular) {
return (
<div>
<TypeName>{schema.displayType}</TypeName>
{schema.title && <TypeTitle> {schema.title} </TypeTitle>}
<RecursiveLabel> {l('recursive')} </RecursiveLabel>
</div>
);
return <RecursiveSchema schema={schema} />;
}
if (discriminatorProp !== undefined) {
@ -51,11 +45,14 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
);
return null;
}
return (
const activeSchema = oneOf[schema.activeOneOf];
return activeSchema.isCircular ? (
<RecursiveSchema schema={activeSchema} />
) : (
<ObjectSchema
{...rest}
level={level}
schema={oneOf![schema.activeOneOf]}
schema={activeSchema}
discriminator={{
fieldName: discriminatorProp,
parentSchema: schema,
@ -68,7 +65,7 @@ export class Schema extends React.Component<Partial<SchemaProps>> {
return <OneOfSchema schema={schema} {...rest} />;
}
const types = Array.isArray(type) ? type : [type];
const types = isArray(type) ? type : [type];
if (types.includes('object')) {
if (schema.fields?.length) {
return <ObjectSchema {...(this.props as any)} level={level} />;

View File

@ -14,6 +14,7 @@ export interface ObjectDescriptionProps {
exampleRef?: string;
showReadOnly?: boolean;
showWriteOnly?: boolean;
showExample?: boolean;
parser: OpenAPIParser;
options: RedocNormalizedOptions;
}
@ -53,7 +54,7 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
}
render() {
const { showReadOnly = true, showWriteOnly = false } = this.props;
const { showReadOnly = true, showWriteOnly = false, showExample = true } = this.props;
return (
<Section>
<Row>
@ -64,18 +65,30 @@ export class SchemaDefinition extends React.PureComponent<ObjectDescriptionProps
schema={this.mediaModel.schema}
/>
</MiddlePanel>
<DarkRightPanel>
<MediaSamplesWrap>
<MediaTypeSamples renderDropdown={this.renderDropdown} mediaType={this.mediaModel} />
</MediaSamplesWrap>
</DarkRightPanel>
{showExample && (
<DarkRightPanel>
<MediaSamplesWrap>
<MediaTypeSamples
renderDropdown={this.renderDropdown}
mediaType={this.mediaModel}
/>
</MediaSamplesWrap>
</DarkRightPanel>
)}
</Row>
</Section>
);
}
private renderDropdown = props => {
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
return (
<DropdownOrLabel
Label={MimeLabel}
Dropdown={InvertedSimpleDropdown}
{...props}
variant="dark"
/>
);
};
}

View File

@ -1,12 +1,11 @@
import * as React from 'react';
import { IMenuItem } from '../../services/MenuStore';
import { SearchStore } from '../../services/SearchStore';
import type { IMenuItem, SearchResult } from '../../services/types';
import type { SearchStore } from '../../services/SearchStore';
import type { MarkerService } from '../../services/MarkerService';
import { MenuItem } from '../SideMenu/MenuItem';
import { MarkerService } from '../../services/MarkerService';
import { SearchResult } from '../../services/SearchWorker.worker';
import { OptionsContext } from '../OptionsProvider';
import { bind, debounce } from 'decko';
import { PerfectScrollbarWrap } from '../../common-elements/perfect-scrollbar';
import {
@ -37,6 +36,8 @@ export interface SearchBoxState {
export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxState> {
activeItemRef: MenuItem | null = null;
static contextType = OptionsContext;
constructor(props) {
super(props);
this.state = {
@ -114,8 +115,9 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
}
search = (event: React.ChangeEvent<HTMLInputElement>) => {
const { minCharacterLengthToInitSearch } = this.context;
const q = event.target.value;
if (q.length < 3) {
if (q.length < minCharacterLengthToInitSearch) {
this.clearResults(q);
return;
}
@ -130,12 +132,13 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
render() {
const { activeItemIdx } = this.state;
const results = this.state.results.map(res => ({
item: this.props.getItemById(res.meta)!,
score: res.score,
}));
results.sort((a, b) => b.score - a.score);
const results = this.state.results
.filter(res => this.props.getItemById(res.meta))
.map(res => ({
item: this.props.getItemById(res.meta)!,
score: res.score,
}))
.sort((a, b) => b.score - a.score);
return (
<SearchWrap role="search">

View File

@ -0,0 +1,71 @@
import * as React from 'react';
import { OpenAPISecurityScheme } from '../../types';
import { SecurityRow } from './styled.elements';
import { SeeMore } from '../SeeMore/SeeMore';
import { Markdown } from '../Markdown/Markdown';
export interface OAuthFlowProps {
type: string;
flow: OpenAPISecurityScheme['flows'][keyof OpenAPISecurityScheme['flows']];
RequiredScopes?: JSX.Element;
}
export function OAuthFlowComponent(props: OAuthFlowProps) {
const { type, flow, RequiredScopes } = props;
const scopesNames = Object.keys(flow?.scopes || {});
return (
<>
<SecurityRow>
<b>Flow type: </b>
<code>{type} </code>
</SecurityRow>
{(type === 'implicit' || type === 'authorizationCode') && (
<SecurityRow>
<strong> Authorization URL: </strong>
<code>
<a target="_blank" rel="noopener noreferrer" href={(flow as any).authorizationUrl}>
{(flow as any).authorizationUrl}
</a>
</code>
</SecurityRow>
)}
{(type === 'password' || type === 'clientCredentials' || type === 'authorizationCode') && (
<SecurityRow>
<b> Token URL: </b>
<code>{(flow as any).tokenUrl}</code>
</SecurityRow>
)}
{flow!.refreshUrl && (
<SecurityRow>
<strong> Refresh URL: </strong>
{flow!.refreshUrl}
</SecurityRow>
)}
{!!scopesNames.length && (
<>
{RequiredScopes || null}
<SecurityRow>
<b> Scopes: </b>
</SecurityRow>
<SeeMore height="4em">
<ul>
{scopesNames.map(scope => (
<li key={scope}>
<code>{scope}</code> -{' '}
<Markdown
className={'redoc-markdown'}
inline={true}
source={flow!.scopes[scope] || ''}
/>
</li>
))}
</ul>
</SeeMore>
</>
)}
</>
);
}
export const OAuthFlow = React.memo<OAuthFlowProps>(OAuthFlowComponent);

View File

@ -0,0 +1,18 @@
import * as React from 'react';
export const RequiredScopesRow = ({ scopes }: { scopes: string[] }): JSX.Element | null => {
if (!scopes.length) return null;
return (
<div>
<b>Required scopes: </b>
{scopes.map((scope, idx) => {
return (
<React.Fragment key={idx}>
<code>{scope}</code>{' '}
</React.Fragment>
);
})}
</div>
);
};

View File

@ -0,0 +1,65 @@
import * as React from 'react';
import { SecuritySchemeModel } from '../../services';
import { titleize } from '../../utils';
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
import { SecurityRow } from './styled.elements';
import { OAuthFlow } from './OAuthFlow';
interface SecuritySchemaProps {
RequiredScopes?: JSX.Element;
scheme: SecuritySchemeModel;
}
export function SecurityDetails(props: SecuritySchemaProps) {
const { RequiredScopes, scheme } = props;
return (
<StyledMarkdownBlock>
{scheme.apiKey ? (
<>
<SecurityRow>
<b>{titleize(scheme.apiKey.in || '')} parameter name: </b>
<code>{scheme.apiKey.name}</code>
</SecurityRow>
{RequiredScopes}
</>
) : scheme.http ? (
<>
<SecurityRow>
<b>HTTP Authorization Scheme: </b>
<code>{scheme.http.scheme}</code>
</SecurityRow>
<SecurityRow>
{scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
<>
<b>Bearer format: </b>
<code>{scheme.http.bearerFormat}</code>
</>
)}
</SecurityRow>
{RequiredScopes}
</>
) : scheme.openId ? (
<>
<SecurityRow>
<b>Connect URL: </b>
<code>
<a target="_blank" rel="noopener noreferrer" href={scheme.openId.connectUrl}>
{scheme.openId.connectUrl}
</a>
</code>
</SecurityRow>
{RequiredScopes}
</>
) : scheme.flows ? (
Object.keys(scheme.flows).map(type => (
<OAuthFlow
key={type}
type={type}
RequiredScopes={RequiredScopes}
flow={scheme.flows[type]}
/>
))
) : null}
</StyledMarkdownBlock>
);
}

View File

@ -0,0 +1,45 @@
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
import {
ScopeName,
SecurityRequirementAndWrap,
SecurityRequirementOrWrap,
} from './styled.elements';
import * as React from 'react';
import { AUTH_TYPES } from '../SecuritySchemes/SecuritySchemes';
export interface SecurityRequirementProps {
security: SecurityRequirementModel;
showSecuritySchemeType?: boolean;
expanded: boolean;
}
export function SecurityHeader(props: SecurityRequirementProps) {
const { security, showSecuritySchemeType, expanded } = props;
const grouping = security.schemes.length > 1;
if (security.schemes.length === 0)
return <SecurityRequirementOrWrap expanded={expanded}>None</SecurityRequirementOrWrap>;
return (
<SecurityRequirementOrWrap expanded={expanded}>
{grouping && '('}
{security.schemes.map(scheme => {
return (
<SecurityRequirementAndWrap key={scheme.id}>
{showSecuritySchemeType && `${AUTH_TYPES[scheme.type] || scheme.type}: `}
<i>{scheme.displayName}</i>
{expanded && scheme.scopes.length
? [
' (',
scheme.scopes.map<React.ReactNode>(scope => (
<ScopeName key={scope}>{scope}</ScopeName>
)),
') ',
]
: null}
</SecurityRequirementAndWrap>
);
})}
{grouping && ') '}
</SecurityRequirementOrWrap>
);
}

View File

@ -1,153 +1,102 @@
import * as React from 'react';
import styled, { media } from '../../styled-components';
import { Link, UnderlinedHeader } from '../../common-elements/';
import { useState } from 'react';
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
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`
font-size: ${props => props.theme.typography.code.fontSize};
font-family: ${props => props.theme.typography.code.fontFamily};
border: 1px solid ${({ theme }) => theme.colors.border.dark};
margin: 0 3px;
padding: 0.2em;
display: inline-block;
line-height: 1;
`;
const SecurityRequirementAndWrap = styled.span`
&:after {
content: ' AND ';
font-weight: bold;
}
&:last-child:after {
content: none;
}
${linksCss};
`;
const SecurityRequirementOrWrap = styled.span`
&:before {
content: '( ';
font-weight: bold;
}
&:after {
content: ' ) OR ';
font-weight: bold;
}
&:last-child:after {
content: ' )';
}
&:only-child:before,
&:only-child:after {
content: none;
}
${linksCss};
`;
export interface SecurityRequirementProps {
security: SecurityRequirementModel;
}
export class SecurityRequirement extends React.PureComponent<SecurityRequirementProps> {
render() {
const security = this.props.security;
return (
<SecurityRequirementOrWrap>
{security.schemes.length ? (
security.schemes.map(scheme => {
return (
<SecurityRequirementAndWrap key={scheme.id}>
<Link to={scheme.sectionId}>{scheme.displayName}</Link>
{scheme.scopes.length > 0 && ' ('}
<ScopeNameList>
{scheme.scopes.map(scope => (
<li key={scope}>
<ScopeName>{scope}</ScopeName>
</li>
))}
</ScopeNameList>
{scheme.scopes.length > 0 && ') '}
</SecurityRequirementAndWrap>
);
})
) : (
<SecurityRequirementAndWrap>None</SecurityRequirementAndWrap>
)}
</SecurityRequirementOrWrap>
);
}
}
const AuthHeaderColumn = styled.div`
flex: 1 1 auto;
`;
const SecuritiesColumn = styled.div`
width: ${props => props.theme.schema.defaultDetailsWidth};
${media.lessThan('small')`
margin-top: 10px;
`}
`;
const AuthHeader = styled(UnderlinedHeader)`
display: inline-block;
margin: 0;
`;
const Wrap = styled.div`
width: 100%;
display: flex;
margin: 1em 0;
${media.lessThan('small')`
flex-direction: column;
`}
`;
import {
AuthHeader,
AuthHeaderColumn,
SecuritiesColumn,
SecurityDetailsStyle,
Wrap,
} from './styled.elements';
import { useStore } from '../StoreBuilder';
import { SecurityHeader } from './SecurityHeader';
import { RequiredScopesRow } from './RequiredScopesRow';
import { AUTH_TYPES } from '../SecuritySchemes/SecuritySchemes';
import { Markdown } from '../Markdown/Markdown';
import { SecurityDetails } from './SecurityDetails';
import { ShelfIcon } from '../../common-elements';
export interface SecurityRequirementsProps {
securities: SecurityRequirementModel[];
}
export class SecurityRequirements extends React.PureComponent<SecurityRequirementsProps> {
render() {
const securities = this.props.securities;
if (!securities.length) {
return null;
}
return (
<Wrap>
<AuthHeaderColumn>
<AuthHeader>Authorizations: </AuthHeader>
export function SecurityRequirements(props: SecurityRequirementsProps) {
const store = useStore();
const showSecuritySchemeType = store?.options.showSecuritySchemeType;
const [expanded, setExpanded] = useState(false);
const { securities } = props;
if (!securities?.length || store?.options.hideSecuritySection) {
return null;
}
const operationSecuritySchemes = store?.spec.securitySchemes.schemes.filter(({ id }) => {
return securities.find(security => security.schemes.find(scheme => scheme.id === id));
});
return (
<>
<Wrap expanded={expanded}>
<AuthHeaderColumn onClick={() => setExpanded(!expanded)}>
<AuthHeader>Authorizations:</AuthHeader>
<ShelfIcon size={'1.3em'} direction={expanded ? 'down' : 'right'} />
</AuthHeaderColumn>
<SecuritiesColumn>
<SecuritiesColumn expanded={expanded}>
{securities.map((security, idx) => (
<SecurityRequirement key={idx} security={security} />
<SecurityHeader
key={idx}
expanded={expanded}
showSecuritySchemeType={showSecuritySchemeType}
security={security}
/>
))}
</SecuritiesColumn>
</Wrap>
);
}
{expanded &&
operationSecuritySchemes?.length &&
operationSecuritySchemes.map((scheme, idx) => (
<SecurityDetailsStyle key={idx}>
<h5>
<LockIcon /> {AUTH_TYPES[scheme.type] || scheme.type}: {scheme.id}
</h5>
<Markdown source={scheme.description || ''} />
<SecurityDetails
key={scheme.id}
scheme={scheme}
RequiredScopes={
<RequiredScopesRow scopes={getRequiredScopes(scheme.id, securities)} />
}
/>
</SecurityDetailsStyle>
))}
</>
);
}
const LockIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="11" height="11">
<path
fill="currentColor"
d="M18 10V6A6 6 0 0 0 6 6v4H3v14h18V10h-3zM8 6c0-2.206 1.794-4 4-4s4 1.794 4 4v4H8V6zm11 16H5V12h14v10z"
/>
</svg>
);
function getRequiredScopes(id: string, securities: SecurityRequirementModel[]): string[] {
const allScopes: string[] = [];
let securitiesLength = securities.length;
while (securitiesLength--) {
const security = securities[securitiesLength];
let schemesLength = security.schemes.length;
while (schemesLength--) {
const scheme = security.schemes[schemesLength];
if (scheme.id === id && Array.isArray(scheme.scopes)) {
allScopes.push(...scheme.scopes);
}
}
}
return Array.from(new Set(allScopes));
}

View File

@ -0,0 +1,129 @@
import styled from 'styled-components';
import { linksCss } from '../Markdown/styled.elements';
import { media } from '../../styled-components';
import { UnderlinedHeader } from '../../common-elements';
export const Header = styled.div`
background-color: #e4e7eb;
`;
export const ScopeNameList = styled.ul`
display: inline;
list-style: none;
padding: 0;
li {
display: inherit;
&:after {
content: ',';
}
&:last-child:after {
content: none;
}
}
`;
export const ScopeName = styled.code`
font-size: ${props => props.theme.typography.code.fontSize};
font-family: ${props => props.theme.typography.code.fontFamily};
margin: 0 3px;
padding: 0.2em;
display: inline-block;
line-height: 1;
&:after {
content: ',';
font-weight: normal;
}
&:last-child:after {
content: none;
}
`;
export const SecurityRequirementAndWrap = styled.span`
&:after {
content: ' and ';
font-weight: normal;
}
&:last-child:after {
content: none;
}
${linksCss};
`;
export const SecurityRequirementOrWrap = styled.span<{ expanded?: boolean }>`
${p => !p.expanded && `white-space: nowrap;`}
&:after {
content: ' or ';
${p => p.expanded && `content: ' or \\a';`}
white-space: pre;
}
&:last-child:after,
&:only-child:after {
content: none;
}
${linksCss};
`;
export const AuthHeaderColumn = styled.div`
flex: 1 1 auto;
cursor: pointer;
`;
export const SecuritiesColumn = styled.div<{ expanded?: boolean }>`
width: ${props => props.theme.schema.defaultDetailsWidth};
text-overflow: ellipsis;
border-radius: 4px;
overflow: hidden;
${p =>
p.expanded &&
`background: ${p.theme.colors.gray['100']};
padding: 8px 9.6px;
margin: 20px 0;
width: 100%;
`};
${media.lessThan('small')`
margin-top: 10px;
`}
`;
export const AuthHeader = styled(UnderlinedHeader)`
display: inline-block;
margin: 0;
`;
export const Wrap = styled.div<{ expanded?: boolean }>`
width: 100%;
display: flex;
margin: 1em 0;
flex-direction: ${p => (p.expanded ? 'column' : 'row')};
${media.lessThan('small')`
flex-direction: column;
`}
`;
export const SecurityRow = styled.div`
margin: 0.5em 0;
`;
export const SecurityDetailsStyle = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.colors.border.dark};
margin-bottom: 1.5em;
padding-bottom: 0.7em;
h5 {
line-height: 1em;
margin: 0 0 0.6em;
font-size: ${({ theme }) => theme.typography.fontSize};
}
.redoc-markdown p:first-child {
display: inline;
}
`;

View File

@ -1,66 +1,18 @@
import * as React from 'react';
import { SecuritySchemesModel } from '../../services/models';
import { H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
import { OpenAPISecurityScheme } from '../../types';
import { titleize } from '../../utils/helpers';
import { SecuritySchemesModel } from '../../services';
import { H2, Row, ShareLink, MiddlePanel, Section } from '../../common-elements';
import { Markdown } from '../Markdown/Markdown';
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
import { SecurityDetails } from '../SecurityRequirement/SecurityDetails';
import { SecurityDetailsStyle, SecurityRow } from '../SecurityRequirement/styled.elements';
const AUTH_TYPES = {
export const AUTH_TYPES = {
oauth2: 'OAuth2',
apiKey: 'API Key',
http: 'HTTP',
openIdConnect: 'OpenID Connect',
};
export interface OAuthFlowProps {
type: string;
flow: OpenAPISecurityScheme['flows'][keyof OpenAPISecurityScheme['flows']];
}
export class OAuthFlow extends React.PureComponent<OAuthFlowProps> {
render() {
const { type, flow } = this.props;
return (
<tr>
<th> {type} OAuth Flow </th>
<td>
{type === 'implicit' || type === 'authorizationCode' ? (
<div>
<strong> Authorization URL: </strong>
{(flow as any).authorizationUrl}
</div>
) : null}
{type === 'password' || type === 'clientCredentials' || type === 'authorizationCode' ? (
<div>
<strong> Token URL: </strong>
{(flow as any).tokenUrl}
</div>
) : null}
{flow!.refreshUrl && (
<div>
<strong> Refresh URL: </strong>
{flow!.refreshUrl}
</div>
)}
<div>
<strong> Scopes: </strong>
</div>
<ul>
{Object.keys(flow!.scopes || {}).map(scope => (
<li key={scope}>
<code>{scope}</code> - <Markdown inline={true} source={flow!.scopes[scope] || ''} />
</li>
))}
</ul>
</td>
</tr>
);
}
}
export interface SecurityDefsProps {
securitySchemes: SecuritySchemesModel;
}
@ -76,52 +28,13 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
{scheme.displayName}
</H2>
<Markdown source={scheme.description || ''} />
<StyledMarkdownBlock>
<table className="security-details">
<tbody>
<tr>
<th> Security Scheme Type </th>
<td> {AUTH_TYPES[scheme.type] || scheme.type} </td>
</tr>
{scheme.apiKey ? (
<tr>
<th> {titleize(scheme.apiKey.in || '')} parameter name:</th>
<td> {scheme.apiKey.name} </td>
</tr>
) : scheme.http ? (
[
<tr key="scheme">
<th> HTTP Authorization Scheme </th>
<td> {scheme.http.scheme} </td>
</tr>,
scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
<tr key="bearer">
<th> Bearer format </th>
<td> &quot;{scheme.http.bearerFormat}&quot; </td>
</tr>
),
]
) : scheme.openId ? (
<tr>
<th> Connect URL </th>
<td>
<a
target="_blank"
rel="noopener noreferrer"
href={scheme.openId.connectUrl}
>
{scheme.openId.connectUrl}
</a>
</td>
</tr>
) : scheme.flows ? (
Object.keys(scheme.flows).map(type => (
<OAuthFlow key={type} type={type} flow={scheme.flows[type]} />
))
) : null}
</tbody>
</table>
</StyledMarkdownBlock>
<SecurityDetailsStyle>
<SecurityRow>
<b>Security Scheme Type: </b>
<span>{AUTH_TYPES[scheme.type] || scheme.type}</span>
</SecurityRow>
<SecurityDetails scheme={scheme} />
</SecurityDetailsStyle>
</MiddlePanel>
</Row>
</Section>

View File

@ -0,0 +1,65 @@
import * as React from 'react';
import styled from 'styled-components';
const TOLERANCE_PX = 20;
interface SeeMoreProps {
children?: React.ReactNode;
height: string;
}
export function SeeMore({ children, height }: SeeMoreProps): JSX.Element {
const ref = React.createRef() as React.RefObject<HTMLDivElement>;
const [showMore, setShowMore] = React.useState(false);
const [showLink, setShowLink] = React.useState(false);
React.useEffect(() => {
if (ref.current && ref.current.clientHeight + TOLERANCE_PX < ref.current.scrollHeight) {
setShowLink(true);
}
}, [ref]);
const onClickMore = () => {
setShowMore(!showMore);
};
return (
<>
<Container
ref={ref}
className={showMore ? '' : 'container'}
style={{ height: showMore ? 'auto' : height }}
>
{children}
</Container>
<ButtonContainer dimmed={!showMore}>
{showLink && (
<ButtonLinkStyled onClick={onClickMore}>
{showMore ? 'See less' : 'See more'}
</ButtonLinkStyled>
)}
</ButtonContainer>
</>
);
}
const Container = styled.div`
overflow-y: hidden;
`;
const ButtonContainer = styled.div<{ dimmed?: boolean }>`
text-align: center;
line-height: 1.5em;
${({ dimmed }) =>
dimmed &&
`background-image: linear-gradient(to bottom, transparent,rgb(255 255 255));
position: relative;
top: -0.5em;
padding-top: 0.5em;
background-position-y: -1em;
`}
`;
const ButtonLinkStyled = styled.a`
cursor: pointer;
`;

View File

@ -0,0 +1,18 @@
import { useEffect, useState } from 'react';
import * as React from 'react';
export default function RedoclyLogo(): JSX.Element | null {
const [isDisplay, setDisplay] = useState(false);
useEffect(() => {
setDisplay(true);
}, []);
return isDisplay ? (
<img
alt={'redocly logo'}
onError={() => setDisplay(false)}
src={'https://cdn.redoc.ly/redoc/logo-mini.svg'}
/>
) : null;
}

View File

@ -2,17 +2,20 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { ShelfIcon } from '../../common-elements/shelfs';
import { IMenuItem, OperationModel } from '../../services';
import { OperationModel } from '../../services';
import { shortenHTTPVerb } from '../../utils/openapi';
import { MenuItems } from './MenuItems';
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
import { l } from '../../services/Labels';
import { scrollIntoViewIfNeeded } from '../../utils';
import { OptionsContext } from '../OptionsProvider';
import type { IMenuItem } from '../../services';
export interface MenuItemProps {
item: IMenuItem;
onActivate?: (item: IMenuItem) => void;
withoutChildren?: boolean;
children?: React.ReactChild;
}
@observer
@ -70,37 +73,33 @@ export class MenuItem extends React.Component<MenuItemProps> {
export interface OperationMenuItemContentProps {
item: OperationModel;
children?: React.ReactChild;
}
@observer
export class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {
ref = React.createRef<HTMLLabelElement>();
export const OperationMenuItemContent = observer((props: OperationMenuItemContentProps) => {
const { item } = props;
const ref = React.createRef<HTMLLabelElement>();
const { showWebhookVerb } = React.useContext(OptionsContext);
componentDidUpdate() {
if (this.props.item.active && this.ref.current) {
scrollIntoViewIfNeeded(this.ref.current);
React.useEffect(() => {
if (props.item.active && ref.current) {
scrollIntoViewIfNeeded(ref.current);
}
}
}, [props.item.active, ref]);
render() {
const { item } = this.props;
return (
<MenuItemLabel
depth={item.depth}
active={item.active}
deprecated={item.deprecated}
ref={this.ref}
>
{item.isWebhook ? (
<OperationBadge type="hook">{l('webhook')}</OperationBadge>
) : (
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
)}
<MenuItemTitle width="calc(100% - 38px)">
{item.sidebarLabel}
{this.props.children}
</MenuItemTitle>
</MenuItemLabel>
);
}
}
return (
<MenuItemLabel depth={item.depth} active={item.active} deprecated={item.deprecated} ref={ref}>
{item.isWebhook ? (
<OperationBadge type="hook">
{showWebhookVerb ? item.httpVerb : l('webhook')}
</OperationBadge>
) : (
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
)}
<MenuItemTitle width="calc(100% - 38px)">
{item.sidebarLabel}
{props.children}
</MenuItemTitle>
</MenuItemLabel>
);
});

View File

@ -1,7 +1,7 @@
import { observer } from 'mobx-react';
import * as React from 'react';
import { IMenuItem } from '../../services';
import type { IMenuItem } from '../../services';
import { MenuItem } from './MenuItem';
import { MenuItemUl } from './styled.elements';
@ -26,7 +26,7 @@ export class MenuItems extends React.Component<MenuItemsProps> {
className={className}
style={this.props.style}
expanded={expanded}
{...(root ? { role: 'navigation' } : {})}
{...(root ? { role: 'menu' } : {})}
>
{items.map((item, idx) => (
<MenuItem key={idx} item={item} onActivate={this.props.onActivate} />

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