mirror of
https://github.com/Redocly/redoc.git
synced 2025-07-10 16:22:27 +03:00
Merge remote-tracking branch 'origin/main' into woosmap-custom
# Conflicts: # src/components/Fields/Field.tsx
This commit is contained in:
commit
98ec5f072c
6
.github/sync.yml
vendored
6
.github/sync.yml
vendored
|
@ -1,6 +0,0 @@
|
|||
group:
|
||||
- files:
|
||||
- source: docs/
|
||||
dest: docs/redoc
|
||||
repos: |
|
||||
Redocly/docs
|
37
.github/workflows/docs-tests.yaml
vendored
Normal file
37
.github/workflows/docs-tests.yaml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: Documentation tests
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
markdownlint:
|
||||
name: markdownlint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: DavidAnson/markdownlint-cli2-action@v15
|
||||
with:
|
||||
config: .markdownlint.yaml
|
||||
globs: |
|
||||
docs/**/*.md
|
||||
README.md
|
||||
|
||||
vale:
|
||||
name: vale action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: errata-ai/vale-action@reviewdog
|
||||
with:
|
||||
files: '["README.md", "docs"]'
|
||||
filter_mode: file
|
||||
|
||||
linkcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
- name: Markup Link Checker (mlc)
|
||||
uses: becheran/mlc@v0.16.1
|
||||
with:
|
||||
args: ./docs
|
37
.github/workflows/publish.yml
vendored
37
.github/workflows/publish.yml
vendored
|
@ -2,8 +2,7 @@ name: Publish
|
|||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]*.[0-9]*.[0-9]*
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
bundle:
|
||||
|
@ -22,7 +21,7 @@ jobs:
|
|||
- run: npm ci
|
||||
- run: npm run bundle
|
||||
- name: Store bundle artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bundles
|
||||
path: bundles
|
||||
|
@ -40,14 +39,32 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
- run: npm ci
|
||||
- name: Download bundled artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bundles
|
||||
path: bundles
|
||||
- run: npm run e2e
|
||||
check-version:
|
||||
name: Check Version
|
||||
runs-on: ubuntu-latest
|
||||
needs: [bundle, unit-tests, e2e-tests]
|
||||
outputs:
|
||||
changed: ${{ steps.check.outputs.changed }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
- name: Check if version has been updated
|
||||
id: check
|
||||
uses: EndBug/version-check@v2.0.1
|
||||
with:
|
||||
file-url: https://cdn.jsdelivr.net/npm/redoc/package.json
|
||||
static-checking: localIsNew
|
||||
publish:
|
||||
name: Publish to NPM
|
||||
needs: [bundle, unit-tests, e2e-tests]
|
||||
needs: [check-version]
|
||||
if: needs.check-version.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v3
|
||||
|
@ -56,7 +73,7 @@ jobs:
|
|||
registry-url: 'https://registry.npmjs.org'
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download bundled artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bundles
|
||||
path: bundles
|
||||
|
@ -77,7 +94,8 @@ jobs:
|
|||
|
||||
publish-cdn:
|
||||
name: Publish to CDN
|
||||
needs: [bundle, unit-tests, e2e-tests]
|
||||
needs: [check-version]
|
||||
if: needs.check-version.outputs.changed == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
@ -89,14 +107,15 @@ jobs:
|
|||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: us-east-1
|
||||
- name: Download all artifact
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
- name: Publish to S3
|
||||
run: npm run publish-cdn
|
||||
|
||||
invalidate-cache:
|
||||
name: Clear cache
|
||||
runs-on: ubuntu-latest
|
||||
needs: [publish, publish-cdn]
|
||||
needs: [check-version, publish, publish-cdn]
|
||||
if: needs.check-version.outputs.changed == 'true'
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
|
18
.github/workflows/sync.yml
vendored
18
.github/workflows/sync.yml
vendored
|
@ -1,18 +0,0 @@
|
|||
name: Sync Files
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Run GitHub File Sync
|
||||
uses: Redocly/repo-file-sync-action@main
|
||||
with:
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
COMMIT_PREFIX: 'sync:'
|
||||
SKIP_PR: true
|
16
.github/workflows/vale.yaml
vendored
16
.github/workflows/vale.yaml
vendored
|
@ -1,16 +0,0 @@
|
|||
name: Docs lint
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
vale:
|
||||
name: vale action
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: errata-ai/vale-action@reviewdog
|
||||
with:
|
||||
files: '["README.md", "docs"]'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
54
.markdownlint.yaml
Normal file
54
.markdownlint.yaml
Normal file
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
# Default rules: https://github.com/github/super-linter/blob/master/TEMPLATES/.markdown-lint.yml
|
||||
|
||||
# Rules by id
|
||||
|
||||
# Unordered list style
|
||||
MD004: false
|
||||
|
||||
# Unordered list indentation
|
||||
MD007:
|
||||
indent: 2
|
||||
|
||||
MD013:
|
||||
# TODO: Consider to decrease allowed line length
|
||||
line_length: 800
|
||||
tables: false
|
||||
|
||||
## Allow same headers in siblings
|
||||
MD024:
|
||||
siblings_only: true
|
||||
|
||||
# Multiple top level headings in the same document
|
||||
MD025:
|
||||
front_matter_title: ''
|
||||
|
||||
# Trailing punctuation in heading
|
||||
MD026:
|
||||
punctuation: '.,;:。,;:'
|
||||
|
||||
# Ordered list item prefix
|
||||
MD029: false
|
||||
|
||||
# Unordered lists inside of ordered lists
|
||||
MD030: false
|
||||
|
||||
# Inline HTML
|
||||
MD033: false
|
||||
|
||||
# No bare urls
|
||||
MD034: false
|
||||
|
||||
# Emphasis used instead of a heading
|
||||
MD036: false
|
||||
|
||||
# Disable "First line in file should be a top level heading"
|
||||
# We use uncommon format to add metadata.
|
||||
# TODO: Consider to use "YAML front matter".
|
||||
MD041: false
|
||||
|
||||
# Rules by tags
|
||||
blank_lines: false
|
||||
|
||||
MD046: false
|
||||
# code-block-style
|
4
.mlc.toml
Normal file
4
.mlc.toml
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Ignore these links, we can't check them from this subproject
|
||||
ignore-links=["../*", "/docs/*"]
|
||||
# Path to the root folder used to resolve all relative paths
|
||||
root-dir="./docs"
|
91
CHANGELOG.md
91
CHANGELOG.md
|
@ -1,3 +1,94 @@
|
|||
# [2.5.0](https://github.com/Redocly/redoc/compare/v2.4.0...v2.5.0) (2025-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* enhance accessibility for menu items with keyboard support ([#2655](https://github.com/Redocly/redoc/issues/2655)) ([2db293b](https://github.com/Redocly/redoc/commit/2db293bfb2973497dd33f31dc99e97f5bb90bbe8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add keyboard navigation support to JsonViewer component ([#2654](https://github.com/Redocly/redoc/issues/2654)) ([1b4126f](https://github.com/Redocly/redoc/commit/1b4126fde4531387f49c90f52efbd0c0e5f7b6ea))
|
||||
|
||||
|
||||
|
||||
# [2.4.0](https://github.com/Redocly/redoc/compare/v2.3.0...v2.4.0) (2025-02-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Prototype Pollution Vulnerability Affecting redoc <=2.2.0 ([#2638](https://github.com/Redocly/redoc/issues/2638)) ([153ec7a](https://github.com/Redocly/redoc/commit/153ec7a0b7245639f404c0b038b612ae7377c7db))
|
||||
* unify redoc config ([#2647](https://github.com/Redocly/redoc/issues/2647)) ([53a6afc](https://github.com/Redocly/redoc/commit/53a6afc59624fe4591b0a0f1f20f41c0fbb5f1cf))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add supporting react 19 in package.json ([#2652](https://github.com/Redocly/redoc/issues/2652)) ([3a74802](https://github.com/Redocly/redoc/commit/3a748022be3a7dc7f98669e1645dd5cda72f1abc))
|
||||
|
||||
|
||||
|
||||
# [2.3.0](https://github.com/Redocly/redoc/compare/v2.2.0...v2.3.0) (2025-01-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* displaying json example when showObjectSchemaExamples enabled ([#2635](https://github.com/Redocly/redoc/issues/2635)) ([59ee73f](https://github.com/Redocly/redoc/commit/59ee73fefa8e8edb398940076bdd721fc284caa3))
|
||||
* displaying nested items with type string ([#2634](https://github.com/Redocly/redoc/issues/2634)) ([85b622f](https://github.com/Redocly/redoc/commit/85b622fc581eb96303aeb85056aef36c74ea9f9d))
|
||||
* passing inline parameters after support react 18 for response title ([#2640](https://github.com/Redocly/redoc/issues/2640)) ([d614d2d](https://github.com/Redocly/redoc/commit/d614d2d022df8bd1989cb0eaf76d087b52120d36))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* update pattern styling ([#2196](https://github.com/Redocly/redoc/issues/2196)) ([#2600](https://github.com/Redocly/redoc/issues/2600)) ([aa0879c](https://github.com/Redocly/redoc/commit/aa0879ca0235112918428fdff8f4c48d2c6c4adf))
|
||||
|
||||
|
||||
|
||||
# [2.2.0](https://github.com/Redocly/redoc/compare/v2.1.5...v2.2.0) (2024-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* show siblings schema with oneOf ([#2576](https://github.com/Redocly/redoc/issues/2576)) ([60d131b](https://github.com/Redocly/redoc/commit/60d131b0a9dab4710e900323c9ba81160cecf7d8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support x-badges ([#2605](https://github.com/Redocly/redoc/issues/2605)) ([64f1877](https://github.com/Redocly/redoc/commit/64f18779e5fe7e03f25862463cbc5062e85c867c))
|
||||
|
||||
|
||||
|
||||
## [2.1.5](https://github.com/Redocly/redoc/compare/v2.1.4...v2.1.5) (2024-06-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update react to 18 and react-tabs to 6 ([#2547](https://github.com/Redocly/redoc/issues/2547)) ([c664dd0](https://github.com/Redocly/redoc/commit/c664dd0d56571ce799b8eadd081d86a6b2cdefae))
|
||||
|
||||
|
||||
|
||||
## [2.1.4](https://github.com/Redocly/redoc/compare/v2.1.3...v2.1.4) (2024-04-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add deprecated css to clickable property name ([#2526](https://github.com/Redocly/redoc/issues/2526)) ([b0d03d0](https://github.com/Redocly/redoc/commit/b0d03d02069c1508447ddebc2f8a3fffa9b03ce5))
|
||||
* use h2/h3 for headings instead of h1/h2 for better seo ([#2514](https://github.com/Redocly/redoc/issues/2514)) ([2b72dc0](https://github.com/Redocly/redoc/commit/2b72dc0e90f759a8ee2e47691c844e7f05928a24))
|
||||
* security vulnerability ([#2445](https://github.com/Redocly/redoc/pull/2445)) ([1f11f5](https://github.com/Redocly/redoc/commit/1f11f597c4f10ddd601db247f5034052b6ca689f))
|
||||
|
||||
|
||||
|
||||
## [2.1.3](https://github.com/Redocly/redoc/compare/v2.1.2...v2.1.3) (2023-10-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* default value as object in request body ([#2437](https://github.com/Redocly/redoc/issues/2437)) ([b36a6e2](https://github.com/Redocly/redoc/commit/b36a6e27bb3e03d39ee74c3e71f18a504539d91b))
|
||||
* display string pattern in array items ([#2438](https://github.com/Redocly/redoc/issues/2438)) ([8ddeb6d](https://github.com/Redocly/redoc/commit/8ddeb6dfda686ec8a6948eb2d96efb99bf422429))
|
||||
* hideRequestPayloadSample ([#2436](https://github.com/Redocly/redoc/issues/2436)) ([bf96061](https://github.com/Redocly/redoc/commit/bf960612a47bfe10ff205b9d78f3040515a5467d))
|
||||
* more cases for react18 and cli integration ([#2416](https://github.com/Redocly/redoc/issues/2416)) ([26674e7](https://github.com/Redocly/redoc/commit/26674e70c66b686d0f0baa569b186292c41e5726))
|
||||
|
||||
|
||||
|
||||
## [2.1.2](https://github.com/Redocly/redoc/compare/v2.1.1...v2.1.2) (2023-09-11)
|
||||
|
||||
|
||||
|
|
20
README.md
20
README.md
|
@ -1,11 +1,11 @@
|
|||
<div align="center">
|
||||
<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
|
||||
# Generate beautiful API documentation from OpenAPI
|
||||
|
||||
[](https://www.npmjs.com/package/redoc) [](https://github.com/Redocly/redoc/blob/main/LICENSE)
|
||||
|
||||
[](https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc)
|
||||
[](https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc)
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -34,13 +34,11 @@ enter the URL for your definition and select **TRY IT**.
|
|||
|
||||
- Responsive three-panel design with menu/scrolling synchronization
|
||||
- Support for OpenAPI 3.1, OpenAPI 3.0, and Swagger 2.0
|
||||
- [Multiple deployment options](https://redocly.com/docs/redoc/)
|
||||
- Interactive interface so your users can try the API immediately
|
||||
- Ability to integrate your API introduction into the side menu
|
||||
- High-level grouping in side menu with the [`x-tagGroups`](https://redocly.com/docs/api-reference-docs/specification-extensions/x-tag-groups/) specification extension
|
||||
- [Simple integration with `create-react-app`](https://redocly.com/docs/redoc/quickstart/react/)
|
||||
- Code samples support (with vendor extension) <br>
|
||||

|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -50,8 +48,8 @@ Redoc is provided as a CLI tool (also distributed as a Docker image), HTML tag,
|
|||
|
||||
If you have Node installed, quickly generate documentation using `npx`:
|
||||
|
||||
```
|
||||
npx @redocly/cli build-docs openapi.yaml
|
||||
```bash
|
||||
npx @redocly/cli build-docs openapi.yaml
|
||||
```
|
||||
|
||||
The tool outputs by default to a file named `redoc-static.html` that you can open in your browser.
|
||||
|
@ -73,9 +71,9 @@ Add your own `spec-url` to the `<redoc>` tag; this attribute can also be a local
|
|||
|
||||
### More usage options
|
||||
|
||||
Check out the [deployment documentation](./deploment/index/md) for more options, and detailed documentation for each.
|
||||
Check out the [deployment documentation](./docs/deployment/intro.md) for more options, and detailed documentation for each.
|
||||
|
||||
## Redoc vs. Reference
|
||||
## Redoc vs. Redocly API Reference
|
||||
|
||||
Redoc is Redocly's community-edition product. Looking for something more?
|
||||
We also offer [hosted API reference documentation](https://redocly.com/docs/api-registry/guides/api-registry-quickstart/)
|
||||
|
@ -104,6 +102,7 @@ A sample of the organizations using Redocly tools in the wild:
|
|||
- [Commbox](https://www.commbox.io/api/)
|
||||
- [APIs.guru](https://apis.guru/api-doc/)
|
||||
- [BoxKnight](https://www.docs.boxknight.com/)
|
||||
- [Quaderno API](https://developers.quaderno.io/api)
|
||||
|
||||
_Pull requests to add your own API page to the list are welcome_
|
||||
|
||||
|
@ -117,15 +116,14 @@ Redoc uses the following [specification extensions](https://redocly.com/docs/api
|
|||
* [`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 tags that refer to non-navigation properties like Pagination, Rate-Limits, etc
|
||||
* [`x-codeSamples`](docs/redoc-vendor-extensions.md#x-codeSamples) - specify operation code samples
|
||||
* [`x-badges`](docs/redoc-vendor-extensions.md#x-badges) - specify operation badges
|
||||
* [`x-examples`](docs/redoc-vendor-extensions.md#x-examples) - specify JSON example for requests
|
||||
* [`x-nullable`](docs/redoc-vendor-extensions.md#x-nullable) - mark schema param as a nullable
|
||||
* [`x-displayName`](docs/redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
|
||||
* [`x-tagGroups`](docs/redoc-vendor-extensions.md#x-tagGroups) - group tags by categories in the side menu
|
||||
* [`x-servers`](docs/redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
||||
* [`x-ignoredHeaderParameters`](docs/redoc-vendor-extensions.md#x-ignoredHeaderParameters) - ability to specify header parameter names to ignore
|
||||
* [`x-additionalPropertiesName`](docs/redoc-vendor-extensions.md#x-additionalPropertiesName) - ability to supply a descriptive name for the additional property keys
|
||||
* [`x-summary`](docs/redoc-vendor-extensions.md#x-summary) - for Response object, use as the response button text, with description rendered under the button
|
||||
* [`x-extendedDiscriminator`](docs/redoc-vendor-extensions.md#x-extendedDiscriminator) - in Schemas, uses this to solve name-clash issues with the standard discriminator
|
||||
* [`x-explicitMappingOnly`](docs/redoc-vendor-extensions.md#x-explicitMappingOnly) - in Schemas, display a more descriptive property name in objects with additionalProperties when viewing the property list with an object
|
||||
|
||||
## Releases
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# npm i -g http-server
|
||||
# http-server -p 8000 --cors
|
||||
|
||||
FROM node:12-alpine
|
||||
FROM node:18-alpine
|
||||
|
||||
RUN apk update && apk add --no-cache git
|
||||
|
||||
|
@ -13,6 +13,7 @@ RUN apk update && apk add --no-cache git
|
|||
WORKDIR /build
|
||||
COPY package.json package-lock.json /build/
|
||||
RUN npm ci --no-optional --ignore-scripts
|
||||
RUN npm explore esbuild -- npm run postinstall
|
||||
|
||||
# copy only required for the build files
|
||||
COPY src /build/src
|
||||
|
|
|
@ -1,46 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Redoc Interactive Demo</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Redoc Interactive Demo. OpenAPI-generated API Reference Documentation"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>ReDoc Interactive Demo</title>
|
||||
<meta name="description" content="ReDoc Interactive Demo. OpenAPI/Swagger-generated API Reference Documentation" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta property="og:title" content="Redoc Interactive Demo" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Redoc Interactive Demo. OpenAPI-generated API Reference Documentation"
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://user-images.githubusercontent.com/3975738/37729752-8a9ea38a-2d46-11e8-8438-42ed26bf1751.png"
|
||||
/>
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
|
||||
<meta property="og:title" content="ReDoc Interactive Demo">
|
||||
<meta property="og:description" content="ReDoc Interactive Demo. OpenAPI/Swagger-generated API Reference Documentation">
|
||||
<meta property="og:image" content="https://user-images.githubusercontent.com/3975738/37729752-8a9ea38a-2d46-11e8-8438-42ed26bf1751.png">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
redoc {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
|
||||
redoc {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
|
||||
<body>
|
||||
<div id="container"> </div>
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r;
|
||||
(i[r] =
|
||||
i[r] ||
|
||||
function () {
|
||||
(i[r].q = i[r].q || []).push(arguments);
|
||||
}),
|
||||
(i[r].l = 1 * new Date());
|
||||
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
|
||||
a.async = 1;
|
||||
a.src = g;
|
||||
m.parentNode.insertBefore(a, m);
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments)
|
||||
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
|
||||
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
if (window.location.host === 'rebilly.github.io') {
|
||||
ga('create', 'UA-81703547-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
if (window.location.host === 'rebilly.github.io') {
|
||||
ga('create', 'UA-81703547-1', 'auto');
|
||||
ga('send', 'pageview');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import * as React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import styled from 'styled-components';
|
||||
import { RedocStandalone } from '../src';
|
||||
import ComboBox from './ComboBox';
|
||||
import FileInput from './components/FileInput';
|
||||
|
||||
const DEFAULT_SPEC = 'openapi.yaml';
|
||||
const NEW_VERSION_SPEC = 'openapi-3-1.yaml';
|
||||
const DEFAULT_SPEC = 'museum.yaml';
|
||||
const NEW_VERSION_PETSTORE = 'openapi-3-1.yaml';
|
||||
|
||||
const demos = [
|
||||
{ value: NEW_VERSION_SPEC, label: 'Petstore OpenAPI 3.1' },
|
||||
{ value: DEFAULT_SPEC, label: 'Museum API' },
|
||||
{ value: NEW_VERSION_PETSTORE, label: 'Petstore OpenAPI 3.1' },
|
||||
{ value: 'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml', label: 'Instagram' },
|
||||
{
|
||||
value: 'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/openapi.yaml',
|
||||
|
@ -54,7 +55,7 @@ class DemoApp extends React.Component<
|
|||
};
|
||||
|
||||
handleChange = (url: string) => {
|
||||
if (url === NEW_VERSION_SPEC) {
|
||||
if (url === NEW_VERSION_PETSTORE) {
|
||||
this.setState({ cors: false });
|
||||
0;
|
||||
}
|
||||
|
@ -121,7 +122,7 @@ class DemoApp extends React.Component<
|
|||
<RedocStandalone
|
||||
spec={this.state.spec}
|
||||
specUrl={proxiedUrl}
|
||||
options={{ scrollYOffset: 'nav', untrustedSpec: true }}
|
||||
options={{ scrollYOffset: 'nav', sanitize: true }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -178,7 +179,9 @@ const Logo = styled.img`
|
|||
}
|
||||
`;
|
||||
|
||||
render(<DemoApp />, document.getElementById('container'));
|
||||
const container = document.getElementById('container');
|
||||
const root = createRoot(container!);
|
||||
root.render(<DemoApp />);
|
||||
|
||||
/* ====== Helpers ====== */
|
||||
function updateQueryStringParameter(uri, key, value) {
|
||||
|
|
BIN
demo/museum-logo.png
Normal file
BIN
demo/museum-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
787
demo/museum.yaml
Normal file
787
demo/museum.yaml
Normal file
|
@ -0,0 +1,787 @@
|
|||
openapi: 3.1.0
|
||||
info:
|
||||
title: Redocly Museum API
|
||||
description: An imaginary, but delightful Museum API for interacting with museum services and information. Built with love by Redocly.
|
||||
version: 1.0.0
|
||||
contact:
|
||||
email: team@redocly.com
|
||||
url: 'https://redocly.com/docs/cli/'
|
||||
x-logo:
|
||||
url: 'https://redocly.github.io/redoc/museum-logo.png'
|
||||
altText: Museum logo
|
||||
license:
|
||||
name: MIT
|
||||
url: 'https://opensource.org/license/mit/ '
|
||||
servers:
|
||||
- url: 'https://api.fake-museum-example.com/v1'
|
||||
paths:
|
||||
/museum-hours:
|
||||
get:
|
||||
summary: Get museum hours
|
||||
description: Get upcoming museum operating hours
|
||||
operationId: getMuseumHours
|
||||
tags:
|
||||
- Operations
|
||||
x-badges:
|
||||
- name: 'Beta'
|
||||
position: before
|
||||
color: purple
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/StartDate'
|
||||
- $ref: '#/components/parameters/PaginationPage'
|
||||
- $ref: '#/components/parameters/PaginationLimit'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetMuseumHoursResponse'
|
||||
examples:
|
||||
default:
|
||||
summary: Museum opening hours
|
||||
value:
|
||||
- date: '2023-09-11'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-12'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-13'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-17'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
closed:
|
||||
summary: The museum is closed
|
||||
value: []
|
||||
|
||||
'400':
|
||||
description: Bad request
|
||||
'404':
|
||||
description: Not found
|
||||
/special-events:
|
||||
post:
|
||||
security: []
|
||||
operationId: CreateSpecialEvent
|
||||
summary: Create special event
|
||||
tags:
|
||||
- Events
|
||||
x-badges:
|
||||
- name: 'Alpha'
|
||||
color: purple
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/CreateSpecialEventRequest'
|
||||
examples:
|
||||
default_example:
|
||||
$ref: '#/components/examples/CreateSpecialEventRequestExample'
|
||||
responses:
|
||||
'200':
|
||||
description: success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpecialEventResponse'
|
||||
examples:
|
||||
default_example:
|
||||
$ref: '#/components/examples/CreateSpecialEventResponseExample'
|
||||
'400':
|
||||
description: Bad request
|
||||
'404':
|
||||
description: Not found
|
||||
get:
|
||||
summary: List special events
|
||||
description: Return a list of upcoming special events at the museum.
|
||||
security: []
|
||||
operationId: listSpecialEvents
|
||||
x-badges:
|
||||
- name: 'Gamma'
|
||||
tags:
|
||||
- Events
|
||||
parameters:
|
||||
- name: startDate
|
||||
in: query
|
||||
description: The starting date to retrieve future operating hours from. Defaults to today's date.
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
example: 2023-02-23
|
||||
- name: endDate
|
||||
in: query
|
||||
description: The end of a date range to retrieve special events for. Defaults to 7 days after `startDate`.
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
example: 2023-04-18
|
||||
- name: page
|
||||
in: query
|
||||
description: The page number to retrieve.
|
||||
schema:
|
||||
type: integer
|
||||
default: 1
|
||||
example: 2
|
||||
- name: limit
|
||||
in: query
|
||||
description: The number of days per page.
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
maximum: 30
|
||||
example: 15
|
||||
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ListSpecialEventsResponse'
|
||||
examples:
|
||||
default_example:
|
||||
$ref: '#/components/examples/ListSpecialEventsResponseExample'
|
||||
'400':
|
||||
description: Bad request
|
||||
'404':
|
||||
description: Not found
|
||||
/special-events/{eventId}:
|
||||
get:
|
||||
summary: Get special event
|
||||
description: Get details about a special event.
|
||||
operationId: getSpecialEvent
|
||||
tags:
|
||||
- Events
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/EventId'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpecialEventResponse'
|
||||
examples:
|
||||
default_example:
|
||||
$ref: '#/components/examples/GetSpecialEventResponseExample'
|
||||
'400':
|
||||
description: Bad request
|
||||
'404':
|
||||
description: Not found
|
||||
patch:
|
||||
summary: Update special event
|
||||
description: Update the details of a special event
|
||||
operationId: updateSpecialEvent
|
||||
tags:
|
||||
- Events
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/EventId'
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UpdateSpecialEventRequest'
|
||||
examples:
|
||||
default_example:
|
||||
$ref: '#/components/examples/UpdateSpecialEventRequestExample'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpecialEventResponse'
|
||||
examples:
|
||||
default_example:
|
||||
$ref: '#/components/examples/UpdateSpecialEventResponseExample'
|
||||
'400':
|
||||
description: Bad request
|
||||
'404':
|
||||
description: Not found
|
||||
delete:
|
||||
summary: Delete special event
|
||||
description: Delete a special event from the collection. Allows museum to cancel planned events.
|
||||
operationId: deleteSpecialEvent
|
||||
tags:
|
||||
- Events
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/EventId'
|
||||
responses:
|
||||
'204':
|
||||
description: Success - no content
|
||||
'400':
|
||||
description: Bad request
|
||||
'401':
|
||||
description: Unauthorized
|
||||
'404':
|
||||
description: Not found
|
||||
/tickets:
|
||||
post:
|
||||
summary: Buy museum tickets
|
||||
description: Purchase museum tickets for general entry or special events.
|
||||
operationId: buyMuseumTickets
|
||||
tags:
|
||||
- Tickets
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BuyMuseumTicketsRequest'
|
||||
examples:
|
||||
general_entry:
|
||||
$ref: '#/components/examples/BuyGeneralTicketsRequestExample'
|
||||
event_entry:
|
||||
$ref: '#/components/examples/BuyEventTicketsRequestExample'
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/BuyMuseumTicketsResponse'
|
||||
examples:
|
||||
general_entry:
|
||||
$ref: '#/components/examples/BuyGeneralTicketsResponseExample'
|
||||
event_entry:
|
||||
$ref: '#/components/examples/BuyEventTicketsResponseExample'
|
||||
'400':
|
||||
description: Bad request
|
||||
'404':
|
||||
description: Not found
|
||||
/tickets/{ticketId}/qr:
|
||||
get:
|
||||
summary: Get ticket QR code
|
||||
description: Return an image of your ticket with scannable QR code. Used for event entry.
|
||||
operationId: getTicketCode
|
||||
tags:
|
||||
- Tickets
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TicketId'
|
||||
responses:
|
||||
'200':
|
||||
description: Scannable event ticket in image format
|
||||
content:
|
||||
image/png:
|
||||
schema:
|
||||
$ref: '#/components/schemas/GetTicketCodeResponse'
|
||||
'400':
|
||||
description: Bad request
|
||||
'404':
|
||||
description: Not found
|
||||
components:
|
||||
schemas:
|
||||
SpecialEvent:
|
||||
description: Request payload for creating new special events at the museum.
|
||||
properties:
|
||||
name:
|
||||
description: Name of the special event
|
||||
type: string
|
||||
example: Fossil lecture
|
||||
location:
|
||||
description: Location where the special event is held
|
||||
type: string
|
||||
example: Lecture theatre
|
||||
eventDescription:
|
||||
description: Description of the special event
|
||||
type: string
|
||||
example: Our panel of experts will share their favorite fossils and explain why they are so great.
|
||||
dates:
|
||||
description: List of planned dates for the special event
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: date
|
||||
example: 2024-03-29
|
||||
price:
|
||||
description: Price of a ticket for the special event
|
||||
type: number
|
||||
format: float
|
||||
example: 12.50
|
||||
|
||||
TicketType:
|
||||
description: Type of ticket being purchased. Use `general` for regular museum entry and `event` for tickets to special events.
|
||||
type: string
|
||||
enum:
|
||||
- event
|
||||
- general
|
||||
x-enumDescriptions:
|
||||
event: Special event ticket
|
||||
general: General museum entry ticket
|
||||
example: event
|
||||
Date:
|
||||
type: string
|
||||
format: date
|
||||
example: 2023-10-29
|
||||
Email:
|
||||
description: Email address for ticket purchaser.
|
||||
type: string
|
||||
format: email
|
||||
example: museum-lover@example.com
|
||||
Phone:
|
||||
description: Phone number for the ticket purchaser (optional).
|
||||
type: string
|
||||
example: +1(234)-567-8910
|
||||
BuyMuseumTicketsRequest:
|
||||
description: Request payload used for purchasing museum tickets.
|
||||
type: object
|
||||
properties:
|
||||
ticketType:
|
||||
$ref: '#/components/schemas/TicketType'
|
||||
eventId:
|
||||
description: Unique identifier for a special event. Required if purchasing tickets for the museum's special events.
|
||||
$ref: '#/components/schemas/EventId'
|
||||
ticketDate:
|
||||
description: Date that the ticket is valid for.
|
||||
$ref: '#/components/schemas/Date'
|
||||
email:
|
||||
$ref: '#/components/schemas/Email'
|
||||
phone:
|
||||
$ref: '#/components/schemas/Phone'
|
||||
required:
|
||||
- ticketType
|
||||
- ticketDate
|
||||
- email
|
||||
TicketMessage:
|
||||
description: Confirmation message after a ticket purchase.
|
||||
type: string
|
||||
example: Museum general entry ticket purchased
|
||||
TicketId:
|
||||
description: Unique identifier for museum ticket. Generated when purchased.
|
||||
type: string
|
||||
format: uuid
|
||||
example: a54a57ca-36f8-421b-a6b4-2e8f26858a4c
|
||||
TicketConfirmation:
|
||||
description: Unique confirmation code used to verify ticket purchase.
|
||||
type: string
|
||||
example: ticket-event-a98c8f-7eb12
|
||||
BuyMuseumTicketsResponse:
|
||||
description: Details for a museum ticket after a successful purchase.
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
$ref: '#/components/schemas/TicketMessage'
|
||||
eventName:
|
||||
$ref: '#/components/schemas/EventName'
|
||||
ticketId:
|
||||
$ref: '#/components/schemas/TicketId'
|
||||
ticketType:
|
||||
$ref: '#/components/schemas/TicketType'
|
||||
ticketDate:
|
||||
description: Date the ticket is valid for.
|
||||
$ref: '#/components/schemas/Date'
|
||||
confirmationCode:
|
||||
$ref: '#/components/schemas/TicketConfirmation'
|
||||
required:
|
||||
- message
|
||||
- ticketId
|
||||
- ticketType
|
||||
- ticketDate
|
||||
- confirmationCode
|
||||
GetTicketCodeResponse:
|
||||
description: An image of a ticket with a QR code used for museum or event entry.
|
||||
type: string
|
||||
format: binary
|
||||
GetMuseumHoursResponse:
|
||||
description: List of museum operating hours for consecutive days.
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/MuseumDailyHours'
|
||||
MuseumDailyHours:
|
||||
description: Daily operating hours for the museum.
|
||||
type: object
|
||||
properties:
|
||||
date:
|
||||
description: Date the operating hours apply to.
|
||||
$ref: '#/components/schemas/Date'
|
||||
example: 2024-12-31
|
||||
timeOpen:
|
||||
type: string
|
||||
pattern: '^([01]\d|2[0-3]):?([0-5]\d)$'
|
||||
description: Time the museum opens on a specific date. Uses 24 hour time format (`HH:mm`).
|
||||
example: 09:00
|
||||
timeClose:
|
||||
description: Time the museum closes on a specific date. Uses 24 hour time format (`HH:mm`).
|
||||
type: string
|
||||
pattern: '^([01]\d|2[0-3]):?([0-5]\d)$'
|
||||
example: 18:00
|
||||
required:
|
||||
- date
|
||||
- timeOpen
|
||||
- timeClose
|
||||
EventId:
|
||||
description: Identifier for a special event.
|
||||
type: string
|
||||
format: uuid
|
||||
example: 3be6453c-03eb-4357-ae5a-984a0e574a54
|
||||
EventName:
|
||||
type: string
|
||||
description: Name of the special event
|
||||
example: Pirate Coding Workshop
|
||||
EventLocation:
|
||||
type: string
|
||||
description: Location where the special event is held
|
||||
example: Computer Room
|
||||
EventDescription:
|
||||
type: string
|
||||
description: Description of the special event
|
||||
example: Captain Blackbeard shares his love of the C...language. And possibly Arrrrr (R lang).
|
||||
EventDates:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Date'
|
||||
description: List of planned dates for the special event
|
||||
EventPrice:
|
||||
description: Price of a ticket for the special event
|
||||
type: number
|
||||
format: float
|
||||
example: 25
|
||||
CreateSpecialEventRequest:
|
||||
description: Request payload for creating new special events at the museum.
|
||||
properties:
|
||||
name:
|
||||
$ref: '#/components/schemas/EventName'
|
||||
location:
|
||||
$ref: '#/components/schemas/EventLocation'
|
||||
eventDescription:
|
||||
$ref: '#/components/schemas/EventDescription'
|
||||
dates:
|
||||
$ref: '#/components/schemas/EventDates'
|
||||
price:
|
||||
$ref: '#/components/schemas/EventPrice'
|
||||
required:
|
||||
- name
|
||||
- location
|
||||
- eventDescription
|
||||
- dates
|
||||
- price
|
||||
UpdateSpecialEventRequest:
|
||||
description: Request payload for updating an existing special event. Only included fields are updated in the event.
|
||||
properties:
|
||||
name:
|
||||
$ref: '#/components/schemas/EventName'
|
||||
location:
|
||||
$ref: '#/components/schemas/EventLocation'
|
||||
eventDescription:
|
||||
$ref: '#/components/schemas/EventDescription'
|
||||
dates:
|
||||
$ref: '#/components/schemas/EventDates'
|
||||
price:
|
||||
$ref: '#/components/schemas/EventPrice'
|
||||
ListSpecialEventsResponse:
|
||||
description: A list of upcoming special events
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/SpecialEventResponse'
|
||||
SpecialEventResponse:
|
||||
description: Information about a special event.
|
||||
properties:
|
||||
eventId:
|
||||
$ref: '#/components/schemas/EventId'
|
||||
name:
|
||||
$ref: '#/components/schemas/EventName'
|
||||
location:
|
||||
$ref: '#/components/schemas/EventLocation'
|
||||
eventDescription:
|
||||
$ref: '#/components/schemas/EventDescription'
|
||||
dates:
|
||||
$ref: '#/components/schemas/EventDates'
|
||||
price:
|
||||
$ref: '#/components/schemas/EventPrice'
|
||||
required:
|
||||
- eventId
|
||||
- name
|
||||
- location
|
||||
- eventDescription
|
||||
- dates
|
||||
- price
|
||||
securitySchemes:
|
||||
MuseumPlaceholderAuth:
|
||||
type: http
|
||||
scheme: basic
|
||||
examples:
|
||||
BuyGeneralTicketsRequestExample:
|
||||
summary: General entry ticket
|
||||
value:
|
||||
ticketType: general
|
||||
ticketDate: 2023-09-07
|
||||
email: todd@example.com
|
||||
BuyEventTicketsRequestExample:
|
||||
summary: Special event ticket
|
||||
value:
|
||||
ticketType: general
|
||||
eventId: dad4bce8-f5cb-4078-a211-995864315e39
|
||||
ticketDate: 2023-09-05
|
||||
email: todd@example.com
|
||||
BuyGeneralTicketsResponseExample:
|
||||
summary: General entry ticket
|
||||
value:
|
||||
message: Museum general entry ticket purchased
|
||||
ticketId: 382c0820-0530-4f4b-99af-13811ad0f17a
|
||||
ticketType: general
|
||||
ticketDate: 2023-09-07
|
||||
confirmationCode: ticket-general-e5e5c6-dce78
|
||||
BuyEventTicketsResponseExample:
|
||||
summary: Special event ticket
|
||||
value:
|
||||
message: Museum special event ticket purchased
|
||||
ticketId: b811f723-17b2-44f7-8952-24b03e43d8a9
|
||||
eventName: Mermaid Treasure Identification and Analysis
|
||||
ticketType: event
|
||||
ticketDate: 2023-09-05
|
||||
confirmationCode: ticket-event-9c55eg-8v82a
|
||||
CreateSpecialEventRequestExample:
|
||||
summary: Create special event
|
||||
value:
|
||||
name: Mermaid Treasure Identification and Analysis
|
||||
location: Under the seaaa 🦀 🎶 🌊.
|
||||
eventDescription: Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.
|
||||
dates:
|
||||
- 2023-09-05
|
||||
- 2023-09-08
|
||||
price: 0
|
||||
CreateSpecialEventResponseExample:
|
||||
summary: Special event created
|
||||
value:
|
||||
eventId: dad4bce8-f5cb-4078-a211-995864315e39
|
||||
name: Mermaid Treasure Identification and Analysis
|
||||
location: Under the seaaa 🦀 🎶 🌊.
|
||||
eventDescription: Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.
|
||||
dates:
|
||||
- 2023-09-05
|
||||
- 2023-09-08
|
||||
price: 30
|
||||
GetSpecialEventResponseExample:
|
||||
summary: Get special event
|
||||
value:
|
||||
eventId: 6744a0da-4121-49cd-8479-f8cc20526495
|
||||
name: Time Traveler Tea Party
|
||||
location: Temporal Tearoom
|
||||
eventDescription: Sip tea with important historical figures.
|
||||
dates:
|
||||
- 2023-11-18
|
||||
- 2023-11-25
|
||||
- 2023-12-02
|
||||
price: 60
|
||||
ListSpecialEventsResponseExample:
|
||||
summary: List of special events
|
||||
value:
|
||||
- eventId: f3e0e76e-e4a8-466e-ab9c-ae36c15b8e97
|
||||
name: Sasquatch Ballet
|
||||
location: Seattle... probably
|
||||
eventDescription: They're big, they're hairy, but they're also graceful. Come learn how the biggest feet can have the lightest touch.
|
||||
dates:
|
||||
- 2023-12-15
|
||||
- 2023-12-22
|
||||
price: 40
|
||||
- eventId: 2f14374a-9c65-4ee5-94b7-fba66d893483
|
||||
name: Solar Telescope Demonstration
|
||||
location: Far from the sun.
|
||||
eventDescription: Look at the sun without going blind!
|
||||
dates:
|
||||
- 2023-09-07
|
||||
- 2023-09-14
|
||||
price: 50
|
||||
- eventId: 6aaa61ba-b2aa-4868-b803-603dbbf7bfdb
|
||||
name: Cook like a Caveman
|
||||
location: Fire Pit on East side
|
||||
eventDescription: Learn to cook on an open flame.
|
||||
dates:
|
||||
- 2023-11-10
|
||||
- 2023-11-17
|
||||
- 2023-11-24
|
||||
price: 5
|
||||
- eventId: 602b75e1-5696-4ab8-8c7a-f9e13580f910
|
||||
name: Underwater Basket Weaving
|
||||
location: Rec Center Pool next door.
|
||||
eventDescription: Learn to weave baskets underwater.
|
||||
dates:
|
||||
- 2023-09-12
|
||||
- 2023-09-15
|
||||
price: 15
|
||||
- eventId: dad4bce8-f5cb-4078-a211-995864315e39
|
||||
name: Mermaid Treasure Identification and Analysis
|
||||
location: Room Sea-12
|
||||
eventDescription: Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits — kindly donated by Ariel.
|
||||
dates:
|
||||
- 2023-09-05
|
||||
- 2023-09-08
|
||||
price: 30
|
||||
- eventId: 6744a0da-4121-49cd-8479-f8cc20526495
|
||||
name: Time Traveler Tea Party
|
||||
location: Temporal Tearoom
|
||||
eventDescription: Sip tea with important historical figures.
|
||||
dates:
|
||||
- 2023-11-18
|
||||
- 2023-11-25
|
||||
- 2023-12-02
|
||||
price: 60
|
||||
- eventId: 3be6453c-03eb-4357-ae5a-984a0e574a54
|
||||
name: Pirate Coding Workshop
|
||||
location: Computer Room
|
||||
eventDescription: Captain Blackbeard shares his love of the C...language. And possibly Arrrrr (R lang).
|
||||
dates:
|
||||
- 2023-10-29
|
||||
- 2023-10-30
|
||||
- 2023-10-31
|
||||
price: 45
|
||||
- eventId: 9d90d29a-2af5-4206-97d9-9ea9ceadcb78
|
||||
name: Llama Street Art Through the Ages
|
||||
location: Auditorium
|
||||
eventDescription: Llama street art?! Alpaca my bags -- let's go!
|
||||
dates:
|
||||
- 2023-10-29
|
||||
- 2023-10-30
|
||||
- 2023-10-31
|
||||
price: 45
|
||||
- eventId: a3c7b2c4-b5fb-4ef7-9322-00a919864957
|
||||
name: The Great Parrot Debate
|
||||
location: Outdoor Amphitheatre
|
||||
eventDescription: See leading parrot minds discuss important geopolitical issues.
|
||||
dates:
|
||||
- 2023-11-03
|
||||
- 2023-11-10
|
||||
price: 35
|
||||
- eventId: b92d46b7-4c5d-422b-87a5-287767e26f29
|
||||
name: Eat a Bunch of Corn
|
||||
location: Cafeteria
|
||||
eventDescription: We accidentally bought too much corn. Please come eat it.
|
||||
dates:
|
||||
- 2023-11-10
|
||||
- 2023-11-17
|
||||
- 2023-11-24
|
||||
price: 5
|
||||
UpdateSpecialEventRequestExample:
|
||||
summary: Update special event request
|
||||
value:
|
||||
location: On the beach.
|
||||
price: 15
|
||||
UpdateSpecialEventResponseExample:
|
||||
summary: Update special event
|
||||
value:
|
||||
eventId: dad4bce8-f5cb-4078-a211-995864315e39
|
||||
name: Mermaid Treasure Identification and Analysis
|
||||
location: On the beach.
|
||||
eventDescription: Join us as we review and classify a rare collection of 20 thingamabobs, gadgets, gizmos, whoosits, and whatsits, kindly donated by Ariel.
|
||||
dates:
|
||||
- 2023-09-05
|
||||
- 2023-09-08
|
||||
price: 15
|
||||
GetMuseumHours:
|
||||
summary: Museum opening hours
|
||||
value:
|
||||
- date: '2023-09-11'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-12'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-13'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-14'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-15'
|
||||
timeOpen: '10:00'
|
||||
timeClose: '16:00'
|
||||
- date: '2023-09-18'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-19'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-20'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-21'
|
||||
timeOpen: '09:00'
|
||||
timeClose: '18:00'
|
||||
- date: '2023-09-22'
|
||||
timeOpen: '10:00'
|
||||
timeClose: '16:00'
|
||||
ClosedMuseumHours:
|
||||
summary: The museum is closed
|
||||
value: []
|
||||
parameters:
|
||||
PaginationPage:
|
||||
name: page
|
||||
in: query
|
||||
description: The page number to retrieve.
|
||||
schema:
|
||||
type: integer
|
||||
default: 1
|
||||
example: 2
|
||||
PaginationLimit:
|
||||
name: limit
|
||||
in: query
|
||||
description: The number of days per page.
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
maximum: 30
|
||||
example: 15
|
||||
EventId:
|
||||
name: eventId
|
||||
in: path
|
||||
description: An identifier for a special event.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: dad4bce8-f5cb-4078-a211-995864315e39
|
||||
StartDate:
|
||||
name: startDate
|
||||
in: query
|
||||
description: The starting date to retrieve future operating hours from. Defaults to today's date.
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
example: 2023-02-23
|
||||
EndDate:
|
||||
name: endDate
|
||||
in: query
|
||||
description: The end of a date range to retrieve special events for. Defaults to 7 days after `startDate`.
|
||||
schema:
|
||||
type: string
|
||||
format: date
|
||||
example: 2023-04-18
|
||||
TicketId:
|
||||
name: ticketId
|
||||
in: path
|
||||
description: An identifier for a ticket to a museum event. Used to generate ticket image.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
example: a54a57ca-36f8-421b-a6b4-2e8f26858a4c
|
||||
|
||||
tags:
|
||||
- name: Operations
|
||||
x-displayName: About the museum
|
||||
description: Operational information about the museum.
|
||||
- name: Events
|
||||
x-displayName: Upcoming events
|
||||
description: Special events hosted by the Museum.
|
||||
- name: Tickets
|
||||
x-displayName: Buy tickets
|
||||
description: Museum tickets for general entrance or special events.
|
||||
|
||||
x-tagGroups:
|
||||
- name: Plan your visit
|
||||
tags:
|
||||
- Operations
|
||||
- Events
|
||||
- name: Purchases
|
||||
tags:
|
||||
- Tickets
|
||||
- name: Entities
|
||||
tags:
|
||||
- Schemas
|
||||
|
||||
security:
|
||||
- MuseumPlaceholderAuth: []
|
|
@ -106,6 +106,10 @@ paths:
|
|||
post:
|
||||
tags:
|
||||
- pet
|
||||
x-badges:
|
||||
- name: 'Beta'
|
||||
position: before
|
||||
color: purple
|
||||
summary: Add a new pet to the store
|
||||
description: Add new pet to the store inventory.
|
||||
operationId: addPet
|
||||
|
@ -150,6 +154,9 @@ paths:
|
|||
put:
|
||||
tags:
|
||||
- pet
|
||||
x-badges:
|
||||
- name: 'Alpha'
|
||||
color: purple
|
||||
summary: Update an existing pet
|
||||
description: ''
|
||||
operationId: updatePet
|
||||
|
@ -183,6 +190,8 @@ paths:
|
|||
get:
|
||||
tags:
|
||||
- pet
|
||||
x-badges:
|
||||
- name: 'Gamma'
|
||||
summary: Find pet by ID
|
||||
description: Returns a single pet
|
||||
operationId: getPetById
|
||||
|
@ -1074,6 +1083,10 @@ components:
|
|||
- available
|
||||
- pending
|
||||
- sold
|
||||
x-enumDescriptions:
|
||||
available: Available status
|
||||
pending: Pending status
|
||||
sold: Sold status
|
||||
petType:
|
||||
description: Type of a pet
|
||||
type: string
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import type { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
|
||||
import RedocStandalone from './hot';
|
||||
import { RedocStandalone } from '../../src';
|
||||
|
||||
const big = window.location.search.indexOf('big') > -1;
|
||||
const swagger = window.location.search.indexOf('swagger') > -1;
|
||||
|
@ -9,8 +9,14 @@ const swagger = window.location.search.indexOf('swagger') > -1;
|
|||
const userUrl = window.location.search.match(/url=(.*)$/);
|
||||
|
||||
const specUrl =
|
||||
(userUrl && userUrl[1]) || (swagger ? 'swagger.yaml' : big ? 'big-openapi.json' : 'openapi.yaml');
|
||||
(userUrl && userUrl[1]) || (swagger ? 'museum.yaml' : big ? 'big-openapi.json' : 'museum.yaml');
|
||||
|
||||
const options: RedocRawOptions = { nativeScrollbars: false, maxDisplayedEnumValues: 3 };
|
||||
const options: RedocRawOptions = {
|
||||
nativeScrollbars: false,
|
||||
maxDisplayedEnumValues: 3,
|
||||
schemaDefinitionsTagName: 'schemas',
|
||||
};
|
||||
|
||||
render(<RedocStandalone specUrl={specUrl} options={options} />, document.getElementById('example'));
|
||||
const container = document.getElementById('example');
|
||||
const root = createRoot(container!);
|
||||
root.render(<RedocStandalone specUrl={specUrl} options={options} />);
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import * as React from 'react';
|
||||
// eslint-disable-next-line import/no-internal-modules
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
import { RedocStandalone as RedocStandaloneOrig, RedocStandaloneProps } from '../../src';
|
||||
|
||||
const RedocStandalone = function (props: RedocStandaloneProps) {
|
||||
return <RedocStandaloneOrig {...props} />;
|
||||
}
|
||||
|
||||
export default hot(RedocStandalone);
|
|
@ -1,25 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Redoc</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>ReDoc</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
redoc {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</head>
|
||||
|
||||
redoc {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<redoc id="example"></redoc>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<body>
|
||||
<redoc id="example"></redoc>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -14,7 +14,7 @@ function root(filename) {
|
|||
return resolve(__dirname + '/' + filename);
|
||||
}
|
||||
|
||||
export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) => ({
|
||||
export default (env: { playground?: boolean; bench?: boolean } = {}) => ({
|
||||
entry: [
|
||||
root('../src/polyfills.ts'),
|
||||
root(
|
||||
|
@ -51,12 +51,6 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
fs: false,
|
||||
os: false,
|
||||
},
|
||||
alias:
|
||||
mode !== 'production'
|
||||
? {
|
||||
'react-dom': '@hot-loader/react-dom',
|
||||
}
|
||||
: {},
|
||||
},
|
||||
|
||||
performance: false,
|
||||
|
@ -121,7 +115,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
webpackIgnore(/json-schema-ref-parser\/lib\/dereference\.js/),
|
||||
webpackIgnore(/^\.\/SearchWorker\.worker$/),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: ['demo/openapi.yaml'],
|
||||
patterns: ['demo/museum.yaml'],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
187
docs/config.md
187
docs/config.md
|
@ -6,12 +6,12 @@ Each deployment type has documentation on how to configure options for that type
|
|||
|
||||
**Versions: 2.x**
|
||||
|
||||
:::success Client-side configuration
|
||||
{% admonition type="success" name="Client-side configuration" %}
|
||||
|
||||
Using Redoc as a standalone (HTML or React) tool, these options must be presented in [kebab case](https://en.wikipedia.org/wiki/Letter_case#Kebab_case).
|
||||
Using Redoc as a standalone (HTML or React) tool, these options must be presented in [kebab case](https://en.wikipedia.org/wiki/Letter_case#Kebab_case).
|
||||
For example, `scrollYOffset` becomes `scroll-y-offset`, and `expandResponses` becomes `expand-responses`.
|
||||
|
||||
:::
|
||||
{% /admonition %}
|
||||
|
||||
## Functional settings
|
||||
|
||||
|
@ -26,59 +26,19 @@ Sets the minimum amount of characters that need to be typed into the search dial
|
|||
|
||||
_Default: 3_
|
||||
|
||||
### expandDefaultServerVariables
|
||||
|
||||
Enables or disables expanding default server variables.
|
||||
|
||||
### expandResponses
|
||||
|
||||
Controls which responses to expand by default. Specify one or more responses by providing their response codes as a comma-separated list without spaces, for example `expandResponses='200,201'`. Special value 'all' expands all responses by default. Be careful: this option can slow down documentation rendering time.
|
||||
|
||||
### expandSingleSchemaField
|
||||
|
||||
Automatically expands the single field in a schema.
|
||||
|
||||
### hideDownloadButton
|
||||
### hideDownloadButtons
|
||||
|
||||
Hides the 'Download' button for saving the API definition source file. **This setting does not make the API definition private**; it just hides the button.
|
||||
|
||||
### hideHostname
|
||||
|
||||
If set to `true`, the protocol and hostname are not shown in the operation definition.
|
||||
|
||||
### hideLoading
|
||||
|
||||
Hides the loading animation. Does not apply to CLI or Workflows-rendered docs.
|
||||
|
||||
### hideRequestPayloadSample
|
||||
|
||||
Hides request payload examples.
|
||||
|
||||
### hideOneOfDescription
|
||||
|
||||
If set to `true`, the description for `oneOf`/`anyOf` object is not shown in the schema.
|
||||
|
||||
### hideSchemaPattern
|
||||
|
||||
If set to `true`, the pattern is not shown in the schema.
|
||||
|
||||
### hideSchemaTitles
|
||||
|
||||
Hides the schema title next to to the type.
|
||||
|
||||
### hideSecuritySection
|
||||
|
||||
Hides the Security panel section.
|
||||
|
||||
### hideSingleRequestSampleTab
|
||||
|
||||
Hides the request sample tab for requests with only one sample.
|
||||
|
||||
### htmlTemplate
|
||||
|
||||
Sets the path to the optional HTML file used to modify the layout of the reference docs page.
|
||||
|
||||
### jsonSampleExpandLevel
|
||||
### jsonSamplesExpandLevel
|
||||
|
||||
Sets the default expand level for JSON payload samples (response and request body). The default value is 2, and the maximum supported value is '+Infinity'. It can also be configured as a string with the special value `all` that expands all levels.
|
||||
|
||||
|
@ -88,35 +48,17 @@ _Default: 2_
|
|||
|
||||
Displays only the specified number of enum values. The remaining values are hidden in an expandable area. If not set, all values are displayed.
|
||||
|
||||
### menuToggle
|
||||
|
||||
If set to `true`, selecting an expanded item in the sidebar twice collapses it.
|
||||
|
||||
_Default: true_
|
||||
|
||||
### nativeScrollbars
|
||||
|
||||
If set to `true`, the sidebar uses the native scrollbar instead of perfect-scroll. This setting is a scrolling performance optimization for big API definitions.
|
||||
|
||||
### onlyRequiredInSamples
|
||||
|
||||
Shows only required fields in request samples.
|
||||
|
||||
### pathInMiddlePanel
|
||||
|
||||
Shows the path link and HTTP verb in the middle panel instead of the right panel.
|
||||
|
||||
### payloadSampleIdx
|
||||
|
||||
If set, the payload sample is inserted at the specified index. If there are `N` payload samples and the value configured here is bigger than `N`, the payload sample is inserted last. Indexes start from 0.
|
||||
|
||||
### requiredPropsFirst
|
||||
### sortRequiredPropsFirst
|
||||
|
||||
Shows required properties in schemas first, ordered in the same order as in the required array.
|
||||
|
||||
### schemaExpansionLevel
|
||||
### schemasExpansionLevel
|
||||
|
||||
Specifies whether to automatically expand schemas in Reference docs. Set it to `all` to expand all schemas regardless of their level, or set it to a number to expand schemas up to the specified level. For example, `schemaExpansionLevel: 3` expands schemas up to three levels deep. The default value is `0`, meaning no schemas are expanded automatically.
|
||||
Specifies whether to automatically expand schemas in Reference docs. Set it to `all` to expand all schemas regardless of their level, or set it to a number to expand schemas up to the specified level. For example, `schemasExpansionLevel: 3` expands schemas up to three levels deep. The default value is `0`, meaning no schemas are expanded automatically.
|
||||
|
||||
### scrollYOffset
|
||||
|
||||
|
@ -132,6 +74,111 @@ Note that you can specify the `scrollYOffset` value in any of the following ways
|
|||
|
||||
Shows specification extensions ('x-' fields). Extensions used by Redoc are ignored. The value can be boolean or an array of strings with names of extensions to display. When used as boolean and set to `true`, all specification extensions are shown.
|
||||
|
||||
### sanitize
|
||||
|
||||
If set to `true`, the API definition is considered untrusted and all HTML/Markdown is sanitized to prevent XSS.
|
||||
|
||||
### downloadUrls
|
||||
|
||||
Set the URLs used to download the OpenAPI description or other documentation related files from the API documentation page.
|
||||
|
||||
### schemaDefinitionsTagName
|
||||
|
||||
If a value is set, all of the schemas are rendered with the designated tag name. The schemas then render and display in the sidebar navigation (with that associated tag name).
|
||||
|
||||
### generatedSamplesMaxDepth
|
||||
|
||||
The generatedSamplesMaxDepth option controls how many schema levels automatically generated for payload samples. The default is 8 which works well for most APIs, but you can adjust it if necessary for your use case.
|
||||
|
||||
### hidePropertiesPrefix
|
||||
|
||||
In complex data structures or object schemas where properties are nested within parent objects the hidePropertiesPrefix option enables the hiding of parent names for nested properties within the documentation.
|
||||
|
||||
_Default: true_
|
||||
|
||||
## Deprecated Functional settings
|
||||
|
||||
### hideDownloadButton
|
||||
|
||||
Hides the 'Download' button for saving the API definition source file. **This setting does not make the API definition private**; it just hides the button.
|
||||
|
||||
### downloadFileName
|
||||
|
||||
Sets the filename for the downloaded API definition source file.
|
||||
|
||||
### downloadDefinitionUrl
|
||||
|
||||
Sets the URL for the downloaded API definition source file.
|
||||
|
||||
### requiredPropsFirst
|
||||
|
||||
Shows required properties in schemas first, ordered in the same order as in the required array.
|
||||
|
||||
### jsonSampleExpandLevel
|
||||
|
||||
Sets the default expand level for JSON payload samples (response and request body). The default value is 2, and the maximum supported value is '+Infinity'. It can also be configured as a string with the special value `all` that expands all levels.
|
||||
|
||||
_Default: 2_
|
||||
|
||||
### schemaExpansionLevel
|
||||
|
||||
Specifies whether to automatically expand schemas in Reference docs. Set it to `all` to expand all schemas regardless of their level, or set it to a number to expand schemas up to the specified level. For example, `schemaExpansionLevel: 3` expands schemas up to three levels deep. The default value is `0`, meaning no schemas are expanded automatically.
|
||||
|
||||
|
||||
### expandDefaultServerVariables
|
||||
|
||||
Enables or disables expanding default server variables.
|
||||
|
||||
### expandResponses
|
||||
|
||||
Controls which responses to expand by default. Specify one or more responses by providing their response codes as a comma-separated list without spaces, for example `expandResponses='200,201'`. Special value 'all' expands all responses by default. Be careful: this option can slow down documentation rendering time.
|
||||
|
||||
### expandSingleSchemaField
|
||||
|
||||
Automatically expands the single field in a schema.
|
||||
|
||||
### hideHostname
|
||||
|
||||
If set to `true`, the protocol and hostname are not shown in the operation definition.
|
||||
|
||||
### hideRequestPayloadSample
|
||||
|
||||
Hides request payload examples.
|
||||
|
||||
### hideOneOfDescription
|
||||
|
||||
If set to `true`, the description for `oneOf`/`anyOf` object is not shown in the schema.
|
||||
|
||||
### hideSchemaPattern
|
||||
|
||||
If set to `true`, the pattern is not shown in the schema.
|
||||
|
||||
### hideSecuritySection
|
||||
|
||||
Hides the Security panel section.
|
||||
|
||||
### hideSingleRequestSampleTab
|
||||
|
||||
Hides the request sample tab for requests with only one sample.
|
||||
|
||||
### menuToggle
|
||||
|
||||
If set to `true`, selecting an expanded item in the sidebar twice collapses it.
|
||||
|
||||
_Default: true_
|
||||
|
||||
### nativeScrollbars
|
||||
|
||||
If set to `true`, the sidebar uses the native scrollbar instead of perfect-scroll. This setting is a scrolling performance optimization for big API definitions.
|
||||
|
||||
### pathInMiddlePanel
|
||||
|
||||
Shows the path link and HTTP verb in the middle panel instead of the right panel.
|
||||
|
||||
### payloadSampleIdx
|
||||
|
||||
If set, the payload sample is inserted at the specified index. If there are `N` payload samples and the value configured here is bigger than `N`, the payload sample is inserted last. Indexes start from 0.
|
||||
|
||||
### showObjectSchemaExamples
|
||||
|
||||
Shows object schema example in the properties; default `false`.
|
||||
|
@ -162,12 +209,12 @@ When set to true, sorts tags in the navigation sidebar and in the middle panel a
|
|||
|
||||
_Default: false_
|
||||
|
||||
### untrustedDefinition
|
||||
### untrustedSpec
|
||||
|
||||
If set to `true`, the API definition is considered untrusted and all HTML/Markdown is sanitized to prevent XSS.
|
||||
|
||||
## Theme settings
|
||||
|
||||
Change styles for the API documentation page. **Supported in Redoc CE 2.x**.
|
||||
* `spacing`
|
||||
* `unit`: 5 # main spacing unit used in autocomputed theme values later
|
||||
* `sectionHorizontal`: 40 # Horizontal section padding. COMPUTED: spacing.unit * 8
|
||||
|
@ -248,7 +295,7 @@ For more information, refer to [Security definitions injection](./security-defin
|
|||
|
||||
### OpenAPI specification extensions
|
||||
|
||||
Redoc uses the following [specification extensions](https://redocly.com/docs/api-reference-docs/spec-extensions/):
|
||||
Redoc uses the following [specification extensions](https://redocly.com/docs-legacy/api-reference-docs/spec-extensions/):
|
||||
* [`x-logo`](./redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
||||
* [`x-traitTag`](./redoc-vendor-extensions.md#x-traittag) - useful for handling out common things like Pagination, Rate-Limits, etc
|
||||
* [`x-codeSamples`](./redoc-vendor-extensions.md#x-codesamples) - specify operation code samples
|
||||
|
@ -257,10 +304,8 @@ Redoc uses the following [specification extensions](https://redocly.com/docs/api
|
|||
* [`x-displayName`](./redoc-vendor-extensions.md#x-displayname) - specify human-friendly names for the menu categories
|
||||
* [`x-tagGroups`](./redoc-vendor-extensions.md#x-taggroups) - group tags by categories in the side menu
|
||||
* [`x-servers`](./redoc-vendor-extensions.md#x-servers) - ability to specify different servers for API (backported from OpenAPI 3.0)
|
||||
* [`x-ignoredHeaderParameters`](./redoc-vendor-extensions.md#x-ignoredheaderparameters) - ability to specify header parameter names to ignore
|
||||
* [`x-additionalPropertiesName`](./redoc-vendor-extensions.md#x-additionalpropertiesname) - ability to supply a descriptive name for the additional property keys
|
||||
* [`x-summary`](./redoc-vendor-extensions.md#x-summary) - For Response object, use as the response button text, with description rendered under the button
|
||||
* [`x-extendedDiscriminator`](./redoc-vendor-extensions.md#x-extendeddiscriminator) - In Schemas, uses this to solve name-clash issues with the standard discriminator
|
||||
* [`x-explicitMappingOnly`](./redoc-vendor-extensions.md#x-explicitmappingonly) - In Schemas, display a more descriptive property name in objects with additionalProperties when viewing the property list with an object
|
||||
|
||||
|
||||
|
|
|
@ -6,21 +6,21 @@ seo:
|
|||
# How to use the Redocly CLI
|
||||
|
||||
With Redocly CLI, you can bundle your OpenAPI definition and API documentation
|
||||
(made with Redoc) into a zero-dependency HTML file and render it locally.
|
||||
(made with Redoc) into an HTML file and render it locally.
|
||||
|
||||
## Step 1 - Install Redocly CLI
|
||||
|
||||
First, you need to install the `@redocly/cli` package.
|
||||
|
||||
You can install it [globally](/docs/cli/installation.md#install-globally) using npm or Yarn.
|
||||
You can install it [globally](../../cli/installation.md#install-globally) using npm or Yarn.
|
||||
|
||||
Or you can install it during [runtime](/docs/cli/installation.md#use-npx-at-runtime) using npx or Docker.
|
||||
Or you can install it during [runtime](../../cli/installation.md#use-npx-at-runtime) using npx or Docker.
|
||||
|
||||
## Step 2 - Build the HTML file
|
||||
|
||||
The Redocly CLI `build-docs` command builds Redoc into a zero-dependency HTML file.
|
||||
The Redocly CLI `build-docs` command builds Redoc into an HTML file.
|
||||
|
||||
To build a zero-dependency HTML file using Redocly CLI, enter the following command,
|
||||
To build an HTML file using Redocly CLI, enter the following command,
|
||||
replacing `apis/openapi.yaml` with your API definition file's name and path:
|
||||
|
||||
```bash
|
||||
|
|
|
@ -36,9 +36,9 @@ description.
|
|||
</html>
|
||||
```
|
||||
|
||||
{ % admonition type="success" name="URL or local file" % }
|
||||
{% admonition type="success" name="URL or local file" %}
|
||||
Set `spec-url` to a relative path if the file is local, e.g. `spec-url=my-api.yaml`. Use a full URL like the example above if it's hosted elsewhere.
|
||||
{ % /admonition % }
|
||||
{% /admonition %}
|
||||
|
||||
Open the HTML file in your browser to see the HTML documentation rendering. You may want to read the next section and add some configuration to make your documentation your own.
|
||||
|
||||
|
@ -126,7 +126,7 @@ The main example shows using the CDN:
|
|||
|
||||
If you would instead prefer to host the depdencies yourself, first install `redoc` using `npm`:
|
||||
|
||||
```
|
||||
```sh
|
||||
npm install redoc
|
||||
```
|
||||
|
||||
|
|
|
@ -30,8 +30,8 @@ The following options are supported:
|
|||
You 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)
|
||||
- [Museum Example API](https://github.com/Redocly/museum-openapi-example/blob/main/openapi.yaml)
|
||||
- [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)
|
||||
|
@ -104,7 +104,7 @@ npm install -g http-server
|
|||
Then, `cd` into your project directory and run the following command:
|
||||
|
||||
```node
|
||||
http - server;
|
||||
http-server
|
||||
```
|
||||
|
||||
The output after entering the command provides the local URL where the preview can be accessed.
|
||||
|
|
|
@ -28,7 +28,7 @@ Redoc is provided as a CLI tool (also distributed as a Docker image), HTML tag,
|
|||
|
||||
If you have Node installed, quickly generate documentation using `npx`:
|
||||
|
||||
```
|
||||
```sh
|
||||
npx @redocly/cli build-docs openapi.yaml
|
||||
```
|
||||
|
||||
|
@ -67,7 +67,7 @@ theme:
|
|||
openapi:
|
||||
disableSearch: true
|
||||
expandResponses: 200,202
|
||||
jsonSampleExpandLevel: 1
|
||||
jsonSamplesExpandLevel: 1
|
||||
|
||||
theme:
|
||||
sidebar:
|
||||
|
@ -84,7 +84,7 @@ theme:
|
|||
|
||||
Redocly CLI detects a file named `redocly.yaml` in the same directory as you run the command and uses it. See the documentation with a command like this:
|
||||
|
||||
```
|
||||
```sh
|
||||
redocly preview-docs openapi.yaml
|
||||
```
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ replace the `spec-url` attribute with the URL or local file address to your defi
|
|||
</html>
|
||||
```
|
||||
|
||||
{% admonition type="attention" name="Redoc requires an HTTP server to run locally" %}
|
||||
{% admonition type="info" name="Redoc requires an HTTP server to run locally" %}
|
||||
Loading local OpenAPI definitions is impossible without running a web server because of issues with
|
||||
[same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy) and
|
||||
other security reasons. Refer to [Running Redoc locally](./deployment/intro.md#how-to-run-redoc-locally) for more information.
|
||||
|
|
|
@ -3,78 +3,76 @@
|
|||
You can use the following [vendor extensions](https://redocly.com/docs/openapi-visual-reference/specification-extensions/) with Redoc.
|
||||
|
||||
- [Redoc vendor extensions](#redoc-vendor-extensions)
|
||||
- [Swagger Object](#swagger-object)
|
||||
- [x-servers](#x-servers)
|
||||
- [x-tagGroups](#x-taggroups)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc)
|
||||
- [Tag Group Object](#tag-group-object)
|
||||
- [Fixed fields](#fixed-fields)
|
||||
- [x-tagGroups example](#x-taggroups-example)
|
||||
- [x-ignoredHeaderParameters](#x-ignoredheaderparameters)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-1)
|
||||
- [x-ignoredHeaderParameters example](#x-ignoredheaderparameters-example)
|
||||
- [Info Object](#info-object)
|
||||
- [x-logo](#x-logo)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-2)
|
||||
- [Logo Object](#logo-object)
|
||||
- [Fixed fields](#fixed-fields-1)
|
||||
- [x-logo example](#x-logo-example)
|
||||
- [Tag Object](#tag-object)
|
||||
- [x-traitTag](#x-traittag)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-3)
|
||||
- [x-traitTag example](#x-traittag-example)
|
||||
- [x-displayName](#x-displayname)
|
||||
- [Operation Object vendor extensions](#operation-object-vendor-extensions)
|
||||
- [x-codeSamples](#x-codesamples)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-4)
|
||||
- [Code Sample Object](#code-sample-object)
|
||||
- [Fixed fields](#fixed-fields-2)
|
||||
- [Code Sample Object example](#code-sample-object-example)
|
||||
- [Parameter Object](#parameter-object)
|
||||
- [x-examples](#x-examples)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-5)
|
||||
- [Response Object vendor extensions](#response-object-vendor-extensions)
|
||||
- [x-summary](#x-summary)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-6)
|
||||
- [Schema Object](#schema-object)
|
||||
- [x-nullable](#x-nullable)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-7)
|
||||
- [x-extendedDiscriminator](#x-extendeddiscriminator)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-8)
|
||||
- [x-extendedDiscriminator example](#x-extendeddiscriminator-example)
|
||||
- [x-additionalPropertiesName](#x-additionalpropertiesname)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-9)
|
||||
- [x-additionalPropertiesName example](#x-additionalpropertiesname-example)
|
||||
- [x-explicitMappingOnly](#x-explicitmappingonly)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-10)
|
||||
- [x-explicitMappingOnly example](#x-explicitmappingonly-example)
|
||||
- [Swagger Object](#swagger-object)
|
||||
- [x-servers](#x-servers)
|
||||
- [x-tagGroups](#x-taggroups)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc)
|
||||
- [Tag Group Object](#tag-group-object)
|
||||
- [Fixed fields](#fixed-fields)
|
||||
- [x-tagGroups example](#x-taggroups-example)
|
||||
- [Info Object](#info-object)
|
||||
- [x-logo](#x-logo)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-2)
|
||||
- [Logo Object](#logo-object)
|
||||
- [Fixed fields](#fixed-fields-1)
|
||||
- [x-logo example](#x-logo-example)
|
||||
- [Tag Object](#tag-object)
|
||||
- [x-traitTag](#x-traittag)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-3)
|
||||
- [x-traitTag example](#x-traittag-example)
|
||||
- [x-displayName](#x-displayname)
|
||||
- [Operation Object vendor extensions](#operation-object-vendor-extensions)
|
||||
- [x-codeSamples](#x-codesamples)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-4)
|
||||
- [Code Sample Object](#code-sample-object)
|
||||
- [Fixed fields](#fixed-fields-2)
|
||||
- [Code Sample Object example](#code-sample-object-example)
|
||||
- [Parameter Object](#parameter-object)
|
||||
- [x-examples](#x-examples)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-5)
|
||||
- [Response Object vendor extensions](#response-object-vendor-extensions)
|
||||
- [x-summary](#x-summary)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-6)
|
||||
- [Schema Object](#schema-object)
|
||||
- [x-nullable](#x-nullable)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-7)
|
||||
- [x-additionalPropertiesName](#x-additionalpropertiesname)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-9)
|
||||
- [x-additionalPropertiesName example](#x-additionalpropertiesname-example)
|
||||
- [x-explicitMappingOnly](#x-explicitmappingonly)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-10)
|
||||
- [x-explicitMappingOnly example](#x-explicitmappingonly-example)
|
||||
- [x-enumDescriptions](#x-enumdescriptions)
|
||||
- [How to use with Redoc](#how-to-use-with-redoc-11)
|
||||
- [x-enumDescriptions example](#x-enumdescriptions-example)
|
||||
|
||||
### Swagger Object
|
||||
Extends the OpenAPI root [OpenAPI Object](https://redocly.com/docs/openapi-visual-reference/openapi/)
|
||||
## Swagger Object
|
||||
Extends the OpenAPI root [OpenAPI Object](https://redocly.com/docs/openapi-visual-reference/openapi)
|
||||
|
||||
#### x-servers
|
||||
Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#serverObject). Currently doesn't support templates.
|
||||
### x-servers
|
||||
Backported from OpenAPI 3.0 [`servers`](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#server-object). Currently doesn't support templates.
|
||||
|
||||
#### x-tagGroups
|
||||
### x-tagGroups
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :-----------: | :---------- |
|
||||
| x-tagGroups | [ [Tag Group Object](#tag-group-object) ] | A list of tag groups |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
`x-tagGroups` is used to group tags in the side menu.
|
||||
Before you use `x-tagGroups`, make sure you **add all tags to a group**, since a tag that is not in a group, **is not displayed** at all!
|
||||
|
||||
<a name="tagGroupObject"></a>
|
||||
|
||||
#### Tag Group Object
|
||||
Information about tags group
|
||||
###### Fixed fields
|
||||
| Field Name | Type | Description |
|
||||
##### Fixed fields
|
||||
| Field Name | Type | Description |
|
||||
| :---------- | :--------: | :---------- |
|
||||
| name | string | The group name |
|
||||
| tags | [ string ] | List of tags to include in this group
|
||||
| tags | [ string ] | List of tags to include in this group |
|
||||
|
||||
###### x-tagGroups example
|
||||
#### x-tagGroups example
|
||||
json
|
||||
```json
|
||||
{
|
||||
|
@ -104,54 +102,33 @@ x-tagGroups:
|
|||
- Secondary Stats
|
||||
```
|
||||
|
||||
#### x-ignoredHeaderParameters
|
||||
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| :-------------------------- | :-----------: | :---------- |
|
||||
| x-ignoredHeaderParameters | [ string ] | A list of ignored headers |
|
||||
|
||||
|
||||
###### How to use with Redoc
|
||||
Use `x-ignoredHeaderParameters` to specify header parameter names which are ignored by Redoc.
|
||||
|
||||
###### x-ignoredHeaderParameters example
|
||||
```yaml
|
||||
swagger: '2.0'
|
||||
info:
|
||||
...
|
||||
tags: [...]
|
||||
x-ignoredHeaderParameters:
|
||||
- Accept
|
||||
- User-Agent
|
||||
- X-Test-Header
|
||||
```
|
||||
|
||||
### Info Object
|
||||
## Info Object
|
||||
Extends the OpenAPI [Info Object](https://redocly.com/docs/openapi-visual-reference/info/)
|
||||
#### x-logo
|
||||
|
||||
| Field Name | Type | Description |
|
||||
### x-logo
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :-----------: | :---------- |
|
||||
| x-logo | [Logo Object](#logo-object) | The information about API logo |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
`x-logo` is used to specify API logo. The corresponding image is displayed just above the side-menu.
|
||||
|
||||
<a name="logoObject"></a>
|
||||
|
||||
#### Logo Object
|
||||
The information about API logo
|
||||
|
||||
###### Fixed fields
|
||||
| Field Name | Type | Description |
|
||||
#### Fixed fields
|
||||
| Field Name | Type | Description |
|
||||
| :-------------- | :------: | :---------- |
|
||||
| url | string | The URL pointing to the spec logo. MUST be in the format of a URL. It SHOULD be an absolute URL so your API definition is usable from any location
|
||||
| backgroundColor | string | background color to be used. MUST be RGB color in [hexadecimal format] (https://en.wikipedia.org/wiki/Web_colors#Hex_triplet)
|
||||
| altText | string | Text to use for alt tag on the logo. Defaults to 'logo' if nothing is provided.
|
||||
| href | string | The URL pointing to the contact page. Default to 'info.contact.url' field of the OAS.
|
||||
| url | string | The URL pointing to the spec logo. MUST be in the format of a URL. It SHOULD be an absolute URL so your API definition is usable from any location |
|
||||
| backgroundColor | string | background color to be used. MUST be RGB color in [hexadecimal format] (https://en.wikipedia.org/wiki/Web_colors#Hex_triplet) |
|
||||
| altText | string | Text to use for alt tag on the logo. Defaults to 'logo' if nothing is provided. |
|
||||
| href | string | The URL pointing to the contact page. Default to 'info.contact.url' field of the OAS. |
|
||||
|
||||
|
||||
###### x-logo example
|
||||
#### x-logo example
|
||||
json
|
||||
```json
|
||||
{
|
||||
|
@ -177,19 +154,19 @@ info:
|
|||
altText: "Petstore logo"
|
||||
```
|
||||
|
||||
### Tag Object
|
||||
## Tag Object
|
||||
Extends the OpenAPI [Tag Object](https://redocly.com/docs/openapi-visual-reference/tags/)
|
||||
|
||||
#### x-traitTag
|
||||
| Field Name | Type | Description |
|
||||
### x-traitTag
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-traitTag | boolean | In Swagger two operations can have multiple tags. This property distinguishes between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
Tags that have `x-traitTag` set to `true` are listed in the side-menu but don't have any subitems (operations). It also renders the `description` tag.
|
||||
This is useful for handling out common things like Pagination, Rate-Limits, etc.
|
||||
|
||||
###### x-traitTag example
|
||||
#### x-traitTag example
|
||||
json
|
||||
```json
|
||||
{
|
||||
|
@ -205,36 +182,37 @@ description: Pagination description (can use markdown syntax)
|
|||
x-traitTag: true
|
||||
```
|
||||
|
||||
#### x-displayName
|
||||
### x-displayName
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-displayName | string | Defines the text that is used for this tag in the menu and in section headings |
|
||||
|
||||
### Operation Object vendor extensions
|
||||
## Operation Object vendor extensions
|
||||
Extends the OpenAPI [Operation Object](https://redocly.com/docs/openapi-visual-reference/operation/)
|
||||
|
||||
#### x-codeSamples
|
||||
| Field Name | Type | Description |
|
||||
### x-codeSamples
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-codeSamples | [ [Code Sample Object](#code-sample-object) ] | A list of code samples associated with operation |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
`x-codeSamples` are rendered on the right panel in Redoc.
|
||||
|
||||
<a name="codeSampleObject"></a>
|
||||
#### Code Sample Object
|
||||
|
||||
### Code Sample Object
|
||||
Operation code sample
|
||||
|
||||
###### Fixed fields
|
||||
| Field Name | Type | Description |
|
||||
#### Fixed fields
|
||||
| Field Name | Type | Description |
|
||||
| :---------- | :------: | :----------- |
|
||||
| lang | string | Code sample language. Value should be one of the following [list](https://github.com/github/linguist/blob/master/lib/linguist/popular.yml) |
|
||||
| label | string? | Code sample label, for example `Node` or `Python2.7`, _optional_, `lang` is used by default |
|
||||
| source | string | Code sample source code |
|
||||
|
||||
|
||||
###### Code Sample Object example
|
||||
#### Code Sample Object example
|
||||
json
|
||||
```json
|
||||
{
|
||||
|
@ -248,105 +226,57 @@ lang: JavaScript
|
|||
source: console.log('Hello World');
|
||||
```
|
||||
|
||||
### Parameter Object
|
||||
### x-badges
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-badges | [[Badge Object](https://redocly.com/docs/realm/author/reference/openapi-extensions/x-badges#badge-object)] | A list of badges associated with the operation |
|
||||
|
||||
## Parameter Object
|
||||
Extends the OpenAPI [Parameter Object](https://redocly.com/docs/openapi-visual-reference/parameter/)
|
||||
|
||||
#### x-examples
|
||||
### x-examples
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-examples | [Example Object](https://redocly.com/docs/openapi-visual-reference/example/) | Object that contains examples for the request. Applies when `in` is `body` and mime-type is `application/json` |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
`x-examples` are rendered in the JSON tab on the right panel in Redoc.
|
||||
|
||||
### Response Object vendor extensions
|
||||
## Response Object vendor extensions
|
||||
Extends the OpenAPI [Response Object](https://redocly.com/docs/openapi-visual-reference/response/).
|
||||
|
||||
#### x-summary
|
||||
| Field Name | Type | Description |
|
||||
### x-summary
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-summary | string | a short summary of the response |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
If specified, you can use `x-summary` as the response button text, with description rendered under the button.
|
||||
|
||||
### Schema Object
|
||||
## Schema Object
|
||||
Extends the OpenAPI [Schema Object](https://redocly.com/docs/openapi-visual-reference/schemas/)
|
||||
|
||||
#### x-nullable
|
||||
| Field Name | Type | Description |
|
||||
### x-nullable
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-nullable | boolean | marks schema as a nullable |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
Schemas marked as `x-nullable` are marked in Redoc with the label Nullable.
|
||||
|
||||
#### x-extendedDiscriminator
|
||||
**ATTENTION**: This is a Redoc-specific vendor extension, and is not supported by other tools.
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-extendedDiscriminator | string | specifies extended discriminator |
|
||||
|
||||
###### How to use with Redoc
|
||||
Redoc uses this vendor extension to solve name-clash issues with the standard `discriminator`.
|
||||
Value of this field specifies the field which is used as an extended discriminator.
|
||||
Redoc displays definition with selectpicker using which user can select value of the `x-extendedDiscriminator`-marked field.
|
||||
Redoc displays the definition derived from the current (using `allOf`) and has `enum` with only one value which is the same as the selected value of the field specified as `x-extendedDiscriminator`.
|
||||
|
||||
###### x-extendedDiscriminator example
|
||||
|
||||
```yaml
|
||||
|
||||
Payment:
|
||||
x-extendedDiscriminator: type
|
||||
type: object
|
||||
required:
|
||||
- type
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
|
||||
CashPayment:
|
||||
allOf:
|
||||
- $ref: "#/definitions/Payment"
|
||||
- properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- cash
|
||||
currency:
|
||||
type: string
|
||||
|
||||
PayPalPayment:
|
||||
allOf:
|
||||
- $ref: "#/definitions/Payment"
|
||||
- properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- paypal
|
||||
userEmail:
|
||||
type: string
|
||||
```
|
||||
|
||||
In the example above, the names of definitions (`PayPalPayment`) are named differently than names in the payload (`paypal`) which is not supported by default `discriminator`.
|
||||
|
||||
#### x-additionalPropertiesName
|
||||
**ATTENTION**: This is a Redoc-specific vendor extension, and is not supported by other tools.
|
||||
### x-additionalPropertiesName
|
||||
**Attention**: This is a Redoc-specific vendor extension, and is not supported by other tools.
|
||||
|
||||
Extends the `additionalProperties` property of the schema object.
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-additionalPropertiesName | string | descriptive name of additional properties keys |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
Redoc uses this extension to display a more descriptive property name in objects with `additionalProperties` when viewing the property list with an `object`.
|
||||
|
||||
###### x-additionalPropertiesName example
|
||||
#### x-additionalPropertiesName example
|
||||
|
||||
```yaml
|
||||
Player:
|
||||
|
@ -362,20 +292,20 @@ Player:
|
|||
type: string
|
||||
```
|
||||
|
||||
#### x-explicitMappingOnly
|
||||
**ATTENTION**: This is Redoc-specific vendor extension, and is not supported by other tools.
|
||||
### x-explicitMappingOnly
|
||||
**Attention**: This is Redoc-specific vendor extension, and is not supported by other tools.
|
||||
|
||||
Extends the `discriminator` property of the schema object.
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-explicitMappingOnly | boolean | limit the discriminator selectpicker to the explicit mappings only |
|
||||
|
||||
###### How to use with Redoc
|
||||
#### How to use with Redoc
|
||||
Redoc uses this extension to filter the `discriminator` mappings shown in the selectpicker.
|
||||
When set to `true`, the selectpicker lists only the explicitly defined mappings. When `false`, the default behavior is kept, in other words, explicit and implicit mappings are shown.
|
||||
|
||||
###### x-explicitMappingOnly example
|
||||
#### x-explicitMappingOnly example
|
||||
|
||||
|
||||
```yaml
|
||||
|
@ -393,3 +323,31 @@ Pet:
|
|||
```
|
||||
|
||||
Shows in the selectpicker only the items `cat` and `bee`, even though the `Dog` class inherits from the `Pet` class.
|
||||
|
||||
### x-enumDescriptions
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-enumDescriptions | [[Enum Description Object](https://redocly.com/docs/realm/author/reference/openapi-extensions/x-enum-descriptions#enum-description-object)] | A list of the enum values and descriptions to include in the documentation. |
|
||||
|
||||
#### How to use with Redoc
|
||||
The enum (short for "enumeration") fields in OpenAPI allow you to restrict the value of a field to a list of allowed values. These values need to be short and machine-readable, but that can make them harder for humans to parse and work with.
|
||||
|
||||
Add x-enumDescriptions to your OpenAPI description to show a helpful table of enum options and an explanation of what each one means. This field supports Markdown.
|
||||
|
||||
#### x-enumDescriptions example
|
||||
The following example shows a schema with two short-named options, and the x-enumDescriptions entry to list all enum entries and give additional context for each:
|
||||
|
||||
```yaml
|
||||
components:
|
||||
schemas:
|
||||
TicketType:
|
||||
description: Type of ticket being purchased. Use `general` for regular museum entry and `event` for tickets to special events.
|
||||
type: string
|
||||
enum:
|
||||
- event
|
||||
- general
|
||||
x-enumDescriptions:
|
||||
event: Event Tickets _(timed entry)_
|
||||
general: General Admission
|
||||
example: event
|
||||
```
|
||||
|
|
|
@ -4,7 +4,7 @@ You can inject the Security Definitions widget anywhere in your specification `d
|
|||
|
||||
```markdown
|
||||
...
|
||||
# Authorization
|
||||
## Authorization
|
||||
|
||||
Some description
|
||||
|
||||
|
@ -14,7 +14,7 @@ Some description
|
|||
The inject instruction is wrapped in an HTML comment,
|
||||
so it is **visible only in Redoc** and not visible, for instance, in the SwaggerUI.
|
||||
|
||||
# Default behavior
|
||||
## Default behavior
|
||||
|
||||
If the injection tag is not found in the description, it is appended to the end
|
||||
of description under the `Authentication` header.
|
||||
|
|
|
@ -8,7 +8,7 @@ describe('Menu', () => {
|
|||
});
|
||||
|
||||
it('should sync active menu items while scroll', () => {
|
||||
cy.contains('h1', 'Introduction')
|
||||
cy.contains('h2', 'Introduction')
|
||||
.scrollIntoView()
|
||||
.get('[role=menuitem] > label.active')
|
||||
.should('have.text', 'Introduction');
|
||||
|
@ -35,7 +35,7 @@ describe('Menu', () => {
|
|||
|
||||
cy.contains('h1', 'Swagger Petstore').scrollIntoView().wait(100);
|
||||
|
||||
cy.contains('h1', 'Introduction')
|
||||
cy.contains('h2', 'Introduction')
|
||||
.scrollIntoView()
|
||||
.wait(100)
|
||||
.get('[role=menuitem] > label.active')
|
||||
|
@ -52,6 +52,31 @@ describe('Menu', () => {
|
|||
cy.location('hash').should('equal', '#schema/Cat');
|
||||
});
|
||||
|
||||
it('should contains badge schema from x-badges', () => {
|
||||
cy.contains('h2', 'Add a new pet to the store').scrollIntoView();
|
||||
|
||||
cy.contains('h2 > span', 'Beta')
|
||||
.scrollIntoView()
|
||||
.wait(100)
|
||||
.get('[role=menuitem] > label.active')
|
||||
.children('span[type="badge"]')
|
||||
.should('have.text', 'Beta');
|
||||
|
||||
cy.contains('h2 > span', 'Alpha')
|
||||
.scrollIntoView()
|
||||
.wait(100)
|
||||
.get('[role=menuitem] > label.active')
|
||||
.children('span[type="badge"]')
|
||||
.should('have.text', 'Alpha');
|
||||
|
||||
cy.contains('h2 > span', 'Gamma')
|
||||
.scrollIntoView()
|
||||
.wait(100)
|
||||
.get('[role=menuitem] > label.active')
|
||||
.children('span[type="badge"]')
|
||||
.should('have.text', 'Gamma');
|
||||
});
|
||||
|
||||
it('should contains Cat schema in Pet using x-tags', () => {
|
||||
cy.contains('[role=menuitem] > label.-depth1', 'pet').click({ force: true });
|
||||
cy.location('hash').should('equal', '#tag/pet');
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
module.exports = function(config) {
|
||||
const testWebpackConfig = require('./build/webpack.test.js');
|
||||
const travis = process.env.TRAVIS;
|
||||
|
||||
config.set({
|
||||
frameworks: ['jasmine', 'sinon', 'should'],
|
||||
preprocessors: {
|
||||
'./tests/spec-bundle.js': ['coverage', 'webpack', 'sourcemap'],
|
||||
},
|
||||
|
||||
coverageReporter: {
|
||||
type: 'in-memory',
|
||||
},
|
||||
|
||||
remapCoverageReporter: {
|
||||
'text-summary': null,
|
||||
'text-lcov': './coverage/lcov.info',
|
||||
html: './coverage/html',
|
||||
},
|
||||
webpack: testWebpackConfig,
|
||||
webpackMiddleware: {
|
||||
stats: 'errors-only',
|
||||
state: true,
|
||||
},
|
||||
client: {
|
||||
chai: {
|
||||
truncateThreshold: 0,
|
||||
},
|
||||
},
|
||||
files: [
|
||||
{ pattern: './tests/spec-bundle.js', watched: false },
|
||||
{ pattern: 'tests/schemas/**/*.json', included: false },
|
||||
{ pattern: 'tests/schemas/**/*.yml', included: false },
|
||||
{ pattern: 'lib/**/*.html', included: false },
|
||||
],
|
||||
|
||||
proxies: {
|
||||
'/tests/schemas': '/base/tests/schemas',
|
||||
'/lib/': '/base/lib/',
|
||||
'/node_modules/': '/base/node_modules/',
|
||||
},
|
||||
colors: true,
|
||||
singleRun: true,
|
||||
reporters: travis
|
||||
? ['mocha', 'coverage', 'remap-coverage', 'coveralls']
|
||||
: ['mocha', 'coverage', 'remap-coverage'],
|
||||
|
||||
browsers: ['ChromeHeadlessNoSandbox'],
|
||||
customLaunchers: {
|
||||
ChromeHeadlessNoSandbox: {
|
||||
base: 'ChromeHeadless',
|
||||
flags: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
|
||||
browserNoActivityTimeout: 60000,
|
||||
});
|
||||
};
|
12959
package-lock.json
generated
12959
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
74
package.json
74
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"version": "2.1.2",
|
||||
"version": "2.5.0",
|
||||
"description": "ReDoc",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -55,20 +55,20 @@
|
|||
"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",
|
||||
"license-check": "license-checker --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause;CC-BY-4.0;CC0-1.0;Python-2.0 ' --summary",
|
||||
"docker:build": "docker build -f config/docker/Dockerfile -t redoc .",
|
||||
"prepare": "husky install",
|
||||
"pre-commit": "pretty-quick --staged && npm run lint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
|
||||
"@cypress/webpack-preprocessor": "^5.17.1",
|
||||
"@hot-loader/react-dom": "^17.0.2",
|
||||
"@size-limit/preset-app": "^8.2.6",
|
||||
"@size-limit/file": "^11.1.4",
|
||||
"@types/chai": "^4.2.18",
|
||||
"@types/dompurify": "^2.2.2",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/enzyme-to-json": "^1.5.3",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/jest": "^29.5.6",
|
||||
"@types/json-pointer": "^1.0.30",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/mark.js": "^8.11.5",
|
||||
|
@ -76,57 +76,55 @@
|
|||
"@types/node": "^15.6.1",
|
||||
"@types/prismjs": "^1.16.5",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/react": "^17.0.8",
|
||||
"@types/react-dom": "^17.0.5",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@types/styled-components": "^5.1.1",
|
||||
"@types/tapable": "^2.2.2",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-env": "^1.18.0",
|
||||
"@types/yargs": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.26.0",
|
||||
"@typescript-eslint/parser": "^4.26.0",
|
||||
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
||||
"@typescript-eslint/parser": "^5.55.0",
|
||||
"beautify-benchmark": "^0.2.4",
|
||||
"conventional-changelog-cli": "^3.0.0",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"core-js": "^3.13.1",
|
||||
"coveralls": "^3.1.1",
|
||||
"css-loader": "^5.2.6",
|
||||
"cypress": "^13.1.0",
|
||||
"cypress": "^13.8.1",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-to-json": "^3.6.2",
|
||||
"esbuild-loader": "^3.0.1",
|
||||
"esbuild-loader": "^4.3.0",
|
||||
"eslint": "^7.27.0",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-react": "^7.25.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-plugin-react": "^7.34.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"fork-ts-checker-webpack-plugin": "^6.2.10",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"husky": "^7.0.0",
|
||||
"jest": "^27.0.3",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"lodash.noop": "^3.0.1",
|
||||
"mobx": "^6.3.2",
|
||||
"mobx": "^6.10.2",
|
||||
"outdent": "^0.8.0",
|
||||
"prettier": "^2.3.2",
|
||||
"pretty-quick": "^3.0.0",
|
||||
"raf": "^3.4.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"shelljs": "^0.8.4",
|
||||
"size-limit": "^8.2.6",
|
||||
"size-limit": "^11.1.4",
|
||||
"style-loader": "^3.3.1",
|
||||
"styled-components": "^5.3.0",
|
||||
"ts-jest": "^27.0.2",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.8.4",
|
||||
"typescript": "^4.9.0",
|
||||
"unfetch": "^4.2.0",
|
||||
"url": "^0.11.1",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
|
@ -135,31 +133,31 @@
|
|||
"peerDependencies": {
|
||||
"core-js": "^3.1.4",
|
||||
"mobx": "^6.0.4",
|
||||
"react": "^16.8.4 || ^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"styled-components": "^4.1.1 || ^5.1.1 || ^6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@redocly/openapi-core": "^1.0.0-rc.2",
|
||||
"classnames": "^2.3.1",
|
||||
"@redocly/openapi-core": "^1.4.0",
|
||||
"classnames": "^2.3.2",
|
||||
"decko": "^1.2.0",
|
||||
"dompurify": "^2.2.8",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"dompurify": "^3.2.4",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"json-pointer": "^0.6.2",
|
||||
"lunr": "^2.3.9",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "^4.0.15",
|
||||
"mobx-react": "^7.2.0",
|
||||
"openapi-sampler": "^1.3.1",
|
||||
"marked": "^4.3.0",
|
||||
"mobx-react": "^9.1.1",
|
||||
"openapi-sampler": "^1.5.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"perfect-scrollbar": "^1.5.5",
|
||||
"polished": "^4.1.3",
|
||||
"prismjs": "^1.27.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-tabs": "^4.3.0",
|
||||
"polished": "^4.2.2",
|
||||
"prismjs": "^1.29.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-tabs": "^6.0.2",
|
||||
"slugify": "~1.4.7",
|
||||
"stickyfill": "^1.1.1",
|
||||
"swagger2openapi": "^7.0.6",
|
||||
"swagger2openapi": "^7.0.8",
|
||||
"url-template": "^2.0.8"
|
||||
},
|
||||
"size-limit": [
|
||||
|
|
|
@ -49,7 +49,7 @@ const Gap = styled.div`
|
|||
bottom: -20px;
|
||||
`;
|
||||
|
||||
export interface TooltipProps {
|
||||
export interface TooltipProps extends React.PropsWithChildren<any> {
|
||||
open: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
import { transparentize } from 'polished';
|
||||
|
||||
import styled, { extensionsHook, css } from '../styled-components';
|
||||
import styled, { css, extensionsHook } from '../styled-components';
|
||||
import { PropertyNameCell } from './fields-layout';
|
||||
import { deprecatedCss } from './mixins';
|
||||
import { ShelfIcon } from './shelfs';
|
||||
|
||||
export const ClickablePropertyNameCell = styled(PropertyNameCell)`
|
||||
&.deprecated {
|
||||
span.property-name {
|
||||
${deprecatedCss}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
|
@ -89,9 +96,11 @@ export const RecursiveLabel = styled(FieldLabel)`
|
|||
|
||||
export const PatternLabel = styled(FieldLabel)`
|
||||
color: #0e7c86;
|
||||
font-family: ${props => props.theme.typography.code.fontFamily};
|
||||
font-size: 12px;
|
||||
&::before,
|
||||
&::after {
|
||||
font-weight: bold;
|
||||
content: ' ';
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -33,7 +33,9 @@ export interface PerfectScrollbarProps {
|
|||
updateFn?: (fn) => void;
|
||||
}
|
||||
|
||||
export class PerfectScrollbar extends React.Component<PerfectScrollbarProps> {
|
||||
export class PerfectScrollbar extends React.Component<
|
||||
React.PropsWithChildren<PerfectScrollbarProps>
|
||||
> {
|
||||
private _container: HTMLElement;
|
||||
private inst: PerfectScrollbarType;
|
||||
|
||||
|
|
|
@ -47,11 +47,11 @@ export const ShelfIcon = styled(IntShelfIcon)`
|
|||
}
|
||||
`;
|
||||
|
||||
export const Badge = styled.span<{ type: string }>`
|
||||
export const Badge = styled.span<{ type: string; color?: string }>`
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
margin: 0;
|
||||
background-color: ${props => props.theme.colors[props.type].main};
|
||||
background-color: ${props => props.color || props.theme.colors[props.type].main};
|
||||
color: ${props => props.theme.colors[props.type].contrastText};
|
||||
font-size: ${props => props.theme.typography.code.fontSize};
|
||||
vertical-align: middle;
|
||||
|
|
|
@ -22,20 +22,13 @@ export interface ApiInfoProps {
|
|||
|
||||
@observer
|
||||
export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||
handleDownloadClick = e => {
|
||||
if (!e.target.href) {
|
||||
e.target.href = this.props.store.spec.info.downloadLink;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { store } = this.props;
|
||||
const { info, externalDocs } = store.spec;
|
||||
const hideDownloadButton = store.options.hideDownloadButton;
|
||||
|
||||
const downloadFilename = info.downloadFileName;
|
||||
const downloadLink = info.downloadLink;
|
||||
const hideDownloadButtons = store.options.hideDownloadButtons;
|
||||
|
||||
const downloadUrls = info.downloadUrls;
|
||||
const downloadFileName = info.downloadFileName;
|
||||
const license =
|
||||
(info.license && (
|
||||
<InfoSpan>
|
||||
|
@ -83,17 +76,22 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
<ApiHeader>
|
||||
{info.title} {version}
|
||||
</ApiHeader>
|
||||
{!hideDownloadButton && (
|
||||
{!hideDownloadButtons && (
|
||||
<p>
|
||||
{l('downloadSpecification')}:
|
||||
<DownloadButton
|
||||
download={downloadFilename || true}
|
||||
target="_blank"
|
||||
href={downloadLink}
|
||||
onClick={this.handleDownloadClick}
|
||||
>
|
||||
{l('download')}
|
||||
</DownloadButton>
|
||||
{downloadUrls?.map(({ title, url }) => {
|
||||
return (
|
||||
<DownloadButton
|
||||
download={downloadFileName || true}
|
||||
target="_blank"
|
||||
href={url}
|
||||
rel="noreferrer"
|
||||
key={url}
|
||||
>
|
||||
{title}
|
||||
</DownloadButton>
|
||||
);
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
<StyledMarkdownBlock>
|
||||
|
|
|
@ -3,7 +3,7 @@ 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 { H2, H3, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||
import type { ContentItemModel } from '../../services';
|
||||
import type { GroupModel, OperationModel } from '../../services/models';
|
||||
import { Operation } from '../Operation/Operation';
|
||||
|
@ -68,7 +68,7 @@ export class SectionItem extends React.Component<ContentItemProps> {
|
|||
render() {
|
||||
const { name, description, externalDocs, level } = this.props.item as GroupModel;
|
||||
|
||||
const Header = level === 2 ? H2 : H1;
|
||||
const Header = level === 2 ? H3 : H2;
|
||||
return (
|
||||
<>
|
||||
<Row>
|
||||
|
|
|
@ -10,11 +10,10 @@ export function ArrayItemDetails({ schema }: { schema: SchemaModel }) {
|
|||
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
|
||||
!schema.constraints?.length) // return null for cases where all constraints are empty
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -5,17 +5,29 @@ import { l } from '../../services/Labels';
|
|||
import { OptionsContext } from '../OptionsProvider';
|
||||
import styled from '../../styled-components';
|
||||
import { RedocRawOptions } from '../../services/RedocNormalizedOptions';
|
||||
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
|
||||
export interface EnumValuesProps {
|
||||
values: string[];
|
||||
isArrayType: boolean;
|
||||
values?: string[] | { [name: string]: string };
|
||||
type: string | string[];
|
||||
}
|
||||
|
||||
export interface EnumValuesState {
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
const DescriptionEnumsBlock = styled(StyledMarkdownBlock)`
|
||||
table {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
`;
|
||||
|
||||
export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesState> {
|
||||
constructor(props: EnumValuesProps) {
|
||||
super(props);
|
||||
this.toggle = this.toggle.bind(this);
|
||||
}
|
||||
state: EnumValuesState = {
|
||||
collapsed: true,
|
||||
};
|
||||
|
@ -27,54 +39,94 @@ export class EnumValues extends React.PureComponent<EnumValuesProps, EnumValuesS
|
|||
}
|
||||
|
||||
render() {
|
||||
const { values, isArrayType } = this.props;
|
||||
const { values, type } = this.props;
|
||||
const { collapsed } = this.state;
|
||||
const isDescriptionEnum = !Array.isArray(values);
|
||||
const enums =
|
||||
(Array.isArray(values) && values) ||
|
||||
Object.entries(values || {}).map(([value, description]) => ({
|
||||
value,
|
||||
description,
|
||||
}));
|
||||
|
||||
// TODO: provide context interface in more elegant way
|
||||
const { enumSkipQuotes, maxDisplayedEnumValues } = this.context as RedocRawOptions;
|
||||
|
||||
if (!values.length) {
|
||||
if (!enums.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const displayedItems =
|
||||
this.state.collapsed && maxDisplayedEnumValues
|
||||
? values.slice(0, maxDisplayedEnumValues)
|
||||
: values;
|
||||
? enums.slice(0, maxDisplayedEnumValues)
|
||||
: enums;
|
||||
|
||||
const showToggleButton = maxDisplayedEnumValues
|
||||
? values.length > maxDisplayedEnumValues
|
||||
: false;
|
||||
const showToggleButton = maxDisplayedEnumValues ? enums.length > maxDisplayedEnumValues : false;
|
||||
|
||||
const toggleButtonText = maxDisplayedEnumValues
|
||||
? collapsed
|
||||
? `… ${values.length - maxDisplayedEnumValues} more`
|
||||
? `… ${enums.length - maxDisplayedEnumValues} more`
|
||||
: 'Hide'
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FieldLabel>
|
||||
{isArrayType ? l('enumArray') : ''}{' '}
|
||||
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||
</FieldLabel>{' '}
|
||||
{displayedItems.map((value, idx) => {
|
||||
const exampleValue = enumSkipQuotes ? String(value) : JSON.stringify(value);
|
||||
return (
|
||||
<React.Fragment key={idx}>
|
||||
<ExampleValue>{exampleValue}</ExampleValue>{' '}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
{showToggleButton ? (
|
||||
<ToggleButton
|
||||
onClick={() => {
|
||||
this.toggle();
|
||||
}}
|
||||
>
|
||||
{toggleButtonText}
|
||||
</ToggleButton>
|
||||
) : null}
|
||||
{isDescriptionEnum ? (
|
||||
<>
|
||||
<DescriptionEnumsBlock>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<FieldLabel>
|
||||
{type === 'array' ? l('enumArray') : ''}{' '}
|
||||
{enums.length === 1 ? l('enumSingleValue') : l('enum')}
|
||||
</FieldLabel>{' '}
|
||||
</th>
|
||||
<th>
|
||||
<strong>Description</strong>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{(displayedItems as { value: string; description: string }[]).map(
|
||||
({ description, value }) => {
|
||||
return (
|
||||
<tr key={value}>
|
||||
<td>{value}</td>
|
||||
<td>
|
||||
<Markdown source={description} compact inline />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</DescriptionEnumsBlock>
|
||||
{showToggleButton ? (
|
||||
<ToggleButton onClick={this.toggle}>{toggleButtonText}</ToggleButton>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FieldLabel>
|
||||
{type === 'array' ? l('enumArray') : ''}{' '}
|
||||
{values.length === 1 ? l('enumSingleValue') : l('enum')}:
|
||||
</FieldLabel>{' '}
|
||||
{displayedItems.map((value, idx) => {
|
||||
const exampleValue = enumSkipQuotes ? String(value) : JSON.stringify(value);
|
||||
return (
|
||||
<React.Fragment key={idx}>
|
||||
<ExampleValue>{exampleValue}</ExampleValue>{' '}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
{showToggleButton ? (
|
||||
<ToggleButton onClick={this.toggle}>{toggleButtonText}</ToggleButton>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
import { FieldDetails } from './FieldDetails';
|
||||
import {
|
||||
InnerPropertiesWrap,
|
||||
PropertyBullet,
|
||||
PropertyCellWithInner,
|
||||
PropertyDetailsCell,
|
||||
PropertyNameCell,
|
||||
|
@ -18,6 +19,8 @@ import { Schema } from '../Schema/Schema';
|
|||
|
||||
import type { SchemaOptions } from '../Schema/Schema';
|
||||
import type { FieldModel } from '../../services/models';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import { RedocNormalizedOptions } from '../../services/RedocNormalizedOptions';
|
||||
|
||||
export interface FieldProps extends SchemaOptions {
|
||||
className?: string;
|
||||
|
@ -26,12 +29,15 @@ export interface FieldProps extends SchemaOptions {
|
|||
|
||||
field: FieldModel;
|
||||
expandByDefault?: boolean;
|
||||
|
||||
fieldParentsName?: string[];
|
||||
renderDiscriminatorSwitch?: (opts: FieldProps) => JSX.Element;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Field extends React.Component<FieldProps> {
|
||||
static contextType = OptionsContext;
|
||||
context: RedocNormalizedOptions;
|
||||
|
||||
toggle = () => {
|
||||
if (this.props.field.expanded === undefined && this.props.expandByDefault) {
|
||||
this.props.field.collapse();
|
||||
|
@ -48,12 +54,12 @@ export class Field extends React.Component<FieldProps> {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { className = '', field, isLast, expandByDefault } = this.props;
|
||||
const { hidePropertiesPrefix } = this.context;
|
||||
const { className = '', field, isLast, expandByDefault, fieldParentsName = [] } = 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>}
|
||||
|
@ -68,11 +74,16 @@ export class Field extends React.Component<FieldProps> {
|
|||
kind={kind}
|
||||
title={name}
|
||||
>
|
||||
<PropertyBullet />
|
||||
<button
|
||||
onClick={this.toggle}
|
||||
onKeyPress={this.handleKeyPress}
|
||||
aria-label={`expand ${name}`}
|
||||
>
|
||||
{!hidePropertiesPrefix &&
|
||||
fieldParentsName.map(
|
||||
name => name + '.\u200B', // zero-width space, a special character is used for correct line breaking
|
||||
)}
|
||||
<span className="property-name">{name}</span>
|
||||
<ShelfIcon direction={expanded ? 'down' : 'right'} />
|
||||
</button>
|
||||
|
@ -80,6 +91,11 @@ export class Field extends React.Component<FieldProps> {
|
|||
</ClickablePropertyNameCell>
|
||||
) : (
|
||||
<PropertyNameCell className={deprecated ? 'deprecated' : undefined} kind={kind} title={name}>
|
||||
<PropertyBullet />
|
||||
{!hidePropertiesPrefix &&
|
||||
fieldParentsName.map(
|
||||
name => name + '.\u200B', // zero-width space, a special character is used for correct line breaking
|
||||
)}
|
||||
<span className="property-name">{name}</span>
|
||||
{labels}
|
||||
</PropertyNameCell>
|
||||
|
@ -99,6 +115,7 @@ export class Field extends React.Component<FieldProps> {
|
|||
<InnerPropertiesWrap>
|
||||
<Schema
|
||||
schema={field.schema}
|
||||
fieldParentsName={[...(fieldParentsName || []), field.name]}
|
||||
skipReadOnly={this.props.skipReadOnly}
|
||||
skipWriteOnly={this.props.skipWriteOnly}
|
||||
showTitle={this.props.showTitle}
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
TypePrefix,
|
||||
TypeTitle,
|
||||
} from '../../common-elements/fields';
|
||||
import { getSerializedValue, isObject } from '../../utils';
|
||||
import { getSerializedValue, isArray, isObject } from '../../utils';
|
||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||
import { Markdown } from '../Markdown/Markdown';
|
||||
import { EnumValues } from './EnumValues';
|
||||
|
@ -30,7 +30,8 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
|
|||
|
||||
const { showExamples, field, renderDiscriminatorSwitch } = props;
|
||||
const { schema, description, deprecated, extensions, in: _in, const: _const } = field;
|
||||
const isArrayType = schema.type === 'array';
|
||||
const isArrayType =
|
||||
schema.type === 'array' || (isArray(schema.type) && schema.type.includes('array'));
|
||||
|
||||
const rawDefault = enumSkipQuotes || _in === 'header'; // having quotes around header field default values is confusing and inappropriate
|
||||
|
||||
|
@ -51,10 +52,10 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
|
|||
|
||||
return null;
|
||||
}, [field, showExamples]);
|
||||
|
||||
const defaultValue = isObject(schema.default)
|
||||
? getSerializedValue(field, schema.default).replace(`${field.name}=`, '')
|
||||
: schema.default;
|
||||
const defaultValue =
|
||||
isObject(schema.default) && field.in
|
||||
? getSerializedValue(field, schema.default).replace(`${field.name}=`, '')
|
||||
: schema.default;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -98,7 +99,7 @@ export const FieldDetailsComponent = observer((props: FieldProps) => {
|
|||
)}
|
||||
<FieldDetail raw={rawDefault} label={l('default') + ':'} value={defaultValue} />
|
||||
{!renderDiscriminatorSwitch && (
|
||||
<EnumValues isArrayType={isArrayType} values={schema.enum} />
|
||||
<EnumValues type={schema.type} values={schema['x-enumDescriptions'] || schema.enum} />
|
||||
)}{' '}
|
||||
{renderedExamples}
|
||||
<Extensions extensions={{ ...extensions, ...schema.extensions }} />
|
||||
|
|
|
@ -41,11 +41,12 @@ const Json = (props: JsonProps) => {
|
|||
<OptionsContext.Consumer>
|
||||
{options => (
|
||||
<PrismDiv
|
||||
tabIndex={0}
|
||||
className={props.className}
|
||||
// tslint:disable-next-line
|
||||
ref={node => setNode(node!)}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: jsonToHTML(props.data, options.jsonSampleExpandLevel),
|
||||
__html: jsonToHTML(props.data, options.jsonSamplesExpandLevel),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -6,11 +6,14 @@ import { StylingMarkdownProps } from './Markdown';
|
|||
import { StyledMarkdownBlock } from './styled.elements';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const StyledMarkdownSpan = styled(props => <StyledMarkdownBlock {...props} />)`
|
||||
// Workaround for DOMPurify type issues (https://github.com/cure53/DOMPurify/issues/1034)
|
||||
const dompurify = DOMPurify['default'] as DOMPurify.DOMPurify;
|
||||
|
||||
const StyledMarkdownSpan = styled(StyledMarkdownBlock)`
|
||||
display: inline;
|
||||
`;
|
||||
|
||||
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
|
||||
const sanitize = (sanitize, html) => (sanitize ? dompurify.sanitize(html) : html);
|
||||
|
||||
export function SanitizedMarkdownHTML({
|
||||
inline,
|
||||
|
@ -25,7 +28,7 @@ export function SanitizedMarkdownHTML({
|
|||
<Wrap
|
||||
className={'redoc-markdown ' + (rest.className || '')}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: sanitize(options.untrustedSpec, rest.html),
|
||||
__html: sanitize(options.sanitize, rest.html),
|
||||
}}
|
||||
data-role={rest['data-role']}
|
||||
{...rest}
|
||||
|
|
|
@ -28,9 +28,20 @@ export interface OperationProps {
|
|||
}
|
||||
|
||||
export const Operation = observer(({ operation }: OperationProps): JSX.Element => {
|
||||
const { name: summary, description, deprecated, externalDocs, isWebhook, httpVerb } = operation;
|
||||
const {
|
||||
name: summary,
|
||||
description,
|
||||
deprecated,
|
||||
externalDocs,
|
||||
isWebhook,
|
||||
httpVerb,
|
||||
badges,
|
||||
} = operation;
|
||||
const hasDescription = !!(description || externalDocs);
|
||||
const { showWebhookVerb } = React.useContext(OptionsContext);
|
||||
const badgesBefore = badges.filter(({ position }) => position === 'before');
|
||||
const badgesAfter = badges.filter(({ position }) => position === 'after');
|
||||
|
||||
return (
|
||||
<OptionsContext.Consumer>
|
||||
{options => (
|
||||
|
@ -38,6 +49,11 @@ export const Operation = observer(({ operation }: OperationProps): JSX.Element =
|
|||
<MiddlePanel>
|
||||
<H2>
|
||||
<ShareLink to={operation.id} />
|
||||
{badgesBefore.map(({ name, color }) => (
|
||||
<Badge type="primary" key={name} color={color}>
|
||||
{name}
|
||||
</Badge>
|
||||
))}
|
||||
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
|
||||
{isWebhook && (
|
||||
<Badge type="primary">
|
||||
|
@ -45,6 +61,11 @@ export const Operation = observer(({ operation }: OperationProps): JSX.Element =
|
|||
Webhook {showWebhookVerb && httpVerb && '| ' + httpVerb.toUpperCase()}
|
||||
</Badge>
|
||||
)}
|
||||
{badgesAfter.map(({ name, color }) => (
|
||||
<Badge type="primary" key={name} color={color}>
|
||||
{name}
|
||||
</Badge>
|
||||
))}
|
||||
</H2>
|
||||
{options.pathInMiddlePanel && !isWebhook && (
|
||||
<Endpoint operation={operation} inverted={true} />
|
||||
|
|
|
@ -17,14 +17,24 @@ export class ArraySchema extends React.PureComponent<SchemaProps> {
|
|||
render() {
|
||||
const schema = this.props.schema;
|
||||
const itemsSchema = schema.items;
|
||||
const fieldParentsName = this.props.fieldParentsName;
|
||||
|
||||
const minMaxItems =
|
||||
schema.minItems === undefined && schema.maxItems === undefined
|
||||
? ''
|
||||
: `(${humanizeConstraints(schema)})`;
|
||||
|
||||
const updatedParentsArray = fieldParentsName
|
||||
? [...fieldParentsName.slice(0, -1), fieldParentsName[fieldParentsName.length - 1] + '[]']
|
||||
: fieldParentsName;
|
||||
if (schema.fields) {
|
||||
return <ObjectSchema {...(this.props as any)} level={this.props.level} />;
|
||||
return (
|
||||
<ObjectSchema
|
||||
{...(this.props as any)}
|
||||
level={this.props.level}
|
||||
fieldParentsName={updatedParentsArray}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (schema.displayType && !itemsSchema && !minMaxItems.length) {
|
||||
return (
|
||||
|
@ -38,7 +48,7 @@ export class ArraySchema extends React.PureComponent<SchemaProps> {
|
|||
<div>
|
||||
<ArrayOpenningLabel> Array {minMaxItems}</ArrayOpenningLabel>
|
||||
<PaddedSchema>
|
||||
<Schema {...this.props} schema={itemsSchema} />
|
||||
<Schema {...this.props} schema={itemsSchema} fieldParentsName={updatedParentsArray} />
|
||||
</PaddedSchema>
|
||||
<ArrayClosingLabel />
|
||||
</div>
|
||||
|
|
|
@ -16,6 +16,7 @@ export interface ObjectSchemaProps extends SchemaProps {
|
|||
fieldName: string;
|
||||
parentSchema: SchemaModel;
|
||||
};
|
||||
fieldParentsName?: string[];
|
||||
}
|
||||
|
||||
export const ObjectSchema = observer(
|
||||
|
@ -26,8 +27,9 @@ export const ObjectSchema = observer(
|
|||
skipReadOnly,
|
||||
skipWriteOnly,
|
||||
level,
|
||||
fieldParentsName,
|
||||
}: ObjectSchemaProps) => {
|
||||
const { expandSingleSchemaField, showObjectSchemaExamples, schemaExpansionLevel } =
|
||||
const { expandSingleSchemaField, showObjectSchemaExamples, schemasExpansionLevel } =
|
||||
React.useContext(OptionsContext);
|
||||
|
||||
const filteredFields = React.useMemo(
|
||||
|
@ -45,7 +47,7 @@ export const ObjectSchema = observer(
|
|||
);
|
||||
|
||||
const expandByDefault =
|
||||
(expandSingleSchemaField && filteredFields.length === 1) || schemaExpansionLevel >= level!;
|
||||
(expandSingleSchemaField && filteredFields.length === 1) || schemasExpansionLevel >= level!;
|
||||
|
||||
return (
|
||||
<PropertiesTable>
|
||||
|
@ -58,6 +60,7 @@ export const ObjectSchema = observer(
|
|||
isLast={isLast}
|
||||
field={field}
|
||||
expandByDefault={expandByDefault}
|
||||
fieldParentsName={Number(level) > 1 ? fieldParentsName : []}
|
||||
renderDiscriminatorSwitch={
|
||||
discriminator?.fieldName === field.name
|
||||
? () => (
|
||||
|
|
|
@ -21,6 +21,7 @@ export interface SchemaOptions {
|
|||
|
||||
export interface SchemaProps extends SchemaOptions {
|
||||
schema: SchemaModel;
|
||||
fieldParentsName?: string[];
|
||||
}
|
||||
|
||||
@observer
|
||||
|
|
|
@ -37,6 +37,7 @@ export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxStat
|
|||
activeItemRef: MenuItem | null = null;
|
||||
|
||||
static contextType = OptionsContext;
|
||||
declare context: React.ContextType<typeof OptionsContext>;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -54,7 +54,7 @@ export function SecurityRequirements(props: SecurityRequirementsProps) {
|
|||
</SecuritiesColumn>
|
||||
</Wrap>
|
||||
{expanded &&
|
||||
operationSecuritySchemes?.length &&
|
||||
!!operationSecuritySchemes?.length &&
|
||||
operationSecuritySchemes.map((scheme, idx) => (
|
||||
<SecurityDetailsStyle key={idx}>
|
||||
<h5>
|
||||
|
|
|
@ -2,7 +2,7 @@ import * as React from 'react';
|
|||
|
||||
import { ClipboardService } from '../../services';
|
||||
|
||||
export class SelectOnClick extends React.PureComponent {
|
||||
export class SelectOnClick extends React.PureComponent<React.PropsWithChildren<any>> {
|
||||
private child: HTMLDivElement | null;
|
||||
selectElement = () => {
|
||||
ClipboardService.selectElement(this.child);
|
||||
|
|
|
@ -2,14 +2,14 @@ import { observer } from 'mobx-react';
|
|||
import * as React from 'react';
|
||||
|
||||
import { ShelfIcon } from '../../common-elements/shelfs';
|
||||
import type { IMenuItem } 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 { shortenHTTPVerb } from '../../utils/openapi';
|
||||
import { OptionsContext } from '../OptionsProvider';
|
||||
import type { IMenuItem } from '../../services';
|
||||
import { MenuItems } from './MenuItems';
|
||||
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
|
||||
|
||||
export interface MenuItemProps {
|
||||
item: IMenuItem;
|
||||
|
@ -47,9 +47,18 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
|||
<MenuItemLi
|
||||
tabIndex={0}
|
||||
onClick={this.activate}
|
||||
onKeyDown={evt => {
|
||||
// Space or Enter key will activate the menu item
|
||||
if (evt.key === 'Enter' || evt.key === ' ') {
|
||||
this.props.onActivate!(this.props.item);
|
||||
evt.stopPropagation();
|
||||
}
|
||||
}}
|
||||
depth={item.depth}
|
||||
data-item-id={item.id}
|
||||
role="menuitem"
|
||||
aria-label={item.sidebarLabel}
|
||||
aria-expanded={item.expanded}
|
||||
>
|
||||
{item.type === 'operation' ? (
|
||||
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
||||
|
@ -101,6 +110,12 @@ export const OperationMenuItemContent = observer((props: OperationMenuItemConten
|
|||
$deprecated={item.deprecated}
|
||||
ref={ref}
|
||||
>
|
||||
{item.badges &&
|
||||
item.badges?.map(({ name, color }) => (
|
||||
<OperationBadge type="badge" color={color} key={name}>
|
||||
{name}
|
||||
</OperationBadge>
|
||||
))}
|
||||
{item.isWebhook ? (
|
||||
<OperationBadge type="hook">
|
||||
{showWebhookVerb ? item.httpVerb : l('webhook')}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { RedocAttribution } from './styled.elements';
|
|||
@observer
|
||||
export class SideMenu extends React.Component<{ menu: MenuStore; className?: string }> {
|
||||
static contextType = OptionsContext;
|
||||
declare context: React.ContextType<typeof OptionsContext>;
|
||||
private _updateScroll?: () => void;
|
||||
|
||||
render() {
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { default as classnames } from 'classnames';
|
||||
import * as classnames from 'classnames';
|
||||
import { darken } from 'polished';
|
||||
|
||||
import { deprecatedCss, ShelfIcon } from '../../common-elements';
|
||||
import styled, { css, media, ResolvedThemeInterface } from '../../styled-components';
|
||||
|
||||
export const OperationBadge = styled.span.attrs((props: { type: string }) => ({
|
||||
export const OperationBadge = styled.span.attrs((props: { type: string; color?: string }) => ({
|
||||
className: `operation-type ${props.type}`,
|
||||
}))<{ type: string }>`
|
||||
}))<{ type: string; color?: string }>`
|
||||
width: 9ex;
|
||||
display: inline-block;
|
||||
height: ${props => props.theme.typography.code.fontSize};
|
||||
line-height: ${props => props.theme.typography.code.fontSize};
|
||||
background-color: #333;
|
||||
background-color: ${props => props.color || '#333'};
|
||||
border-radius: 3px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 6px 4px;
|
||||
|
|
|
@ -85,7 +85,7 @@ const FloatingButton = styled.div`
|
|||
|
||||
@observer
|
||||
export class StickyResponsiveSidebar extends React.Component<
|
||||
StickySidebarProps,
|
||||
React.PropsWithChildren<StickySidebarProps>,
|
||||
StickySidebarState
|
||||
> {
|
||||
static contextType = OptionsContext;
|
||||
|
|
|
@ -56,6 +56,7 @@ export function StoreBuilder(props: StoreBuilderProps) {
|
|||
}
|
||||
}
|
||||
load();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [spec, specUrl]);
|
||||
|
||||
const store = React.useMemo(() => {
|
||||
|
|
|
@ -44,4 +44,73 @@ describe('FieldDetailsComponent', () => {
|
|||
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when default value is object in request body', () => {
|
||||
const mockFieldProps = {
|
||||
showExamples: true,
|
||||
field: {
|
||||
schema: {
|
||||
type: 'object',
|
||||
default: { properties: {} },
|
||||
displayType: 'object',
|
||||
title: 'test title',
|
||||
externalDocs: undefined,
|
||||
constraints: [''],
|
||||
} as SchemaModel,
|
||||
example: 'example',
|
||||
name: 'name',
|
||||
expanded: false,
|
||||
required: false,
|
||||
kind: '',
|
||||
deprecated: false,
|
||||
collapse: jest.fn(),
|
||||
toggle: jest.fn(),
|
||||
explode: false,
|
||||
expand: jest.fn(),
|
||||
description: 'test description',
|
||||
in: undefined,
|
||||
},
|
||||
renderDiscriminatorSwitch: jest.fn(),
|
||||
};
|
||||
const wrapper = shallow(withTheme(<FieldDetails {...mockFieldProps} />));
|
||||
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders correctly when field items have string type and pattern', () => {
|
||||
const mockFieldProps = {
|
||||
showExamples: true,
|
||||
field: {
|
||||
schema: {
|
||||
type: 'array',
|
||||
displayType: 'Array of strings',
|
||||
title: 'test title',
|
||||
externalDocs: undefined,
|
||||
constraints: [''],
|
||||
items: {
|
||||
type: 'string',
|
||||
pattern: '^see regex[0-9]$',
|
||||
constraints: ['<= 128 characters'],
|
||||
externalDocs: undefined,
|
||||
},
|
||||
} as any as SchemaModel,
|
||||
example: 'example',
|
||||
name: 'name',
|
||||
expanded: false,
|
||||
required: false,
|
||||
kind: '',
|
||||
deprecated: false,
|
||||
collapse: jest.fn(),
|
||||
toggle: jest.fn(),
|
||||
explode: false,
|
||||
expand: jest.fn(),
|
||||
description: 'test description',
|
||||
in: undefined,
|
||||
},
|
||||
renderDiscriminatorSwitch: jest.fn(),
|
||||
};
|
||||
const wrapper = shallow(withTheme(<FieldDetails {...mockFieldProps} />));
|
||||
|
||||
expect(wrapper.render()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { mount, ReactWrapper } from 'enzyme';
|
||||
import * as React from 'react';
|
||||
import { act } from 'react';
|
||||
|
||||
import { JsonViewer } from '../';
|
||||
import { withTheme } from '../testProviders';
|
||||
|
@ -50,5 +51,54 @@ describe('Components', () => {
|
|||
expect(flatDataComponent.html()).not.toContain('Expand all');
|
||||
expect(flatDataComponent.html()).not.toContain('Collapse all');
|
||||
});
|
||||
|
||||
describe('Keyboard Navigation', () => {
|
||||
let component: ReactWrapper;
|
||||
const data = {
|
||||
a: 1,
|
||||
b: {
|
||||
c:
|
||||
// Long string to test horizontal scrolling
|
||||
Array(100).fill('hello').join(''),
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
component = mount(withTheme(<JsonViewer data={data} />));
|
||||
ClipboardService.copySelected = origCopySelected;
|
||||
});
|
||||
|
||||
test('should handle arrow key navigation', () => {
|
||||
const prismDiv = component.find('div[tabIndex=0]');
|
||||
const divElement = prismDiv.getDOMNode();
|
||||
|
||||
// Mock scrollLeft before events
|
||||
Object.defineProperty(divElement, 'scrollLeft', {
|
||||
get: jest.fn(() => 0),
|
||||
set: jest.fn(),
|
||||
});
|
||||
|
||||
// Trigger events inside act()
|
||||
act(() => {
|
||||
divElement.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowRight',
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
divElement.dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'ArrowLeft',
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(divElement.scrollLeft).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -37,7 +37,145 @@ exports[`FieldDetailsComponent renders correctly 1`] = `
|
|||
<span
|
||||
class="sc-kpDqfm sc-eldPxv cGRfjn ehWiAn"
|
||||
>
|
||||
""
|
||||
[]
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="sc-kpDqfm cGRfjn"
|
||||
>
|
||||
Example:
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="sc-kpDqfm sc-eldPxv cGRfjn ehWiAn"
|
||||
>
|
||||
"example"
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="sc-lcIPJg sc-hknOHE gBHqkN jFBMaE"
|
||||
>
|
||||
<p>
|
||||
test description
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`FieldDetailsComponent renders correctly when default value is object in request body 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<span
|
||||
class="sc-kpDqfm sc-dAlyuH cGRfjn gHomYR"
|
||||
/>
|
||||
<span
|
||||
class="sc-kpDqfm sc-jlZhew cGRfjn dYtiIA"
|
||||
>
|
||||
object
|
||||
</span>
|
||||
<span
|
||||
class="sc-kpDqfm sc-cwHptR cGRfjn gyVIPr"
|
||||
>
|
||||
(test title)
|
||||
</span>
|
||||
<span>
|
||||
|
||||
<span
|
||||
class="sc-kpDqfm sc-gFqAkR cGRfjn fYEICH"
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span
|
||||
class="sc-kpDqfm cGRfjn"
|
||||
>
|
||||
Default:
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="sc-kpDqfm sc-eldPxv cGRfjn ehWiAn"
|
||||
>
|
||||
{"properties":{}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span
|
||||
class="sc-kpDqfm cGRfjn"
|
||||
>
|
||||
Example:
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="sc-kpDqfm sc-eldPxv cGRfjn ehWiAn"
|
||||
>
|
||||
"example"
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="sc-lcIPJg sc-hknOHE gBHqkN jFBMaE"
|
||||
>
|
||||
<p>
|
||||
test description
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`FieldDetailsComponent renders correctly when field items have string type and pattern 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<span
|
||||
class="sc-kpDqfm sc-dAlyuH cGRfjn gHomYR"
|
||||
/>
|
||||
<span
|
||||
class="sc-kpDqfm sc-jlZhew cGRfjn dYtiIA"
|
||||
>
|
||||
Array of strings
|
||||
</span>
|
||||
<span
|
||||
class="sc-kpDqfm sc-cwHptR cGRfjn gyVIPr"
|
||||
>
|
||||
(test title)
|
||||
</span>
|
||||
<span>
|
||||
|
||||
<span
|
||||
class="sc-kpDqfm sc-gFqAkR cGRfjn fYEICH"
|
||||
>
|
||||
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="sc-kpDqfm sc-dAlyuH sc-gvZAcH cGRfjn gHomYR eXivNJ"
|
||||
>
|
||||
[ items
|
||||
<span>
|
||||
|
||||
<span
|
||||
class="sc-kpDqfm sc-gFqAkR cGRfjn fYEICH"
|
||||
>
|
||||
<= 128 characters
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="sc-kpDqfm sc-eDPEul cGRfjn cCKYVD"
|
||||
>
|
||||
^see regex[0-9]$
|
||||
</span>
|
||||
]
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SecurityRequirement should render SecurityDefs 1`] = `
|
||||
"<div id=\\"section/Authentication/petstore_auth\\" data-section-id=\\"section/Authentication/petstore_auth\\" class=\\"sc-dcJsrY bBkGhy\\"><div class=\\"sc-kAyceB hBQWIZ\\"><div class=\\"sc-fqkvVR oJKYx\\"><h2 class=\\"sc-jXbUNg fWnwAh\\">petstore_auth</h2><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><p>Get access to data while protecting your account credentials.
|
||||
"<div id="section/Authentication/petstore_auth" data-section-id="section/Authentication/petstore_auth" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">petstore_auth</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>Get access to data while protecting your account credentials.
|
||||
OAuth2 is also a safer and more secure way to give you access.</p>
|
||||
</div><div class=\\"sc-ejfMa-d a-DjBE\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Security Scheme Type: </b><span>OAuth2</span></div><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Flow type: </b><code>implicit </code></div><div class=\\"sc-dkmUuB hFwAIA\\"><strong> Authorization URL: </strong><code><a target=\\"_blank\\" rel=\\"noopener noreferrer\\" href=\\"http://petstore.swagger.io/api/oauth/dialog\\">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div class=\\"sc-dkmUuB hFwAIA\\"><b> Scopes: </b></div><div class=\\"sc-iEXKAA blExNw container\\" style=\\"height: 4em;\\"><ul><li><code>write:pets</code> - <div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru sc-fhzFiK hXtrri redoc-markdown\\"><p>modify pets in your account</p>
|
||||
</div></li><li><code>read:pets</code> - <div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru sc-fhzFiK hXtrri redoc-markdown\\"><p>read your pets</p>
|
||||
</div></li></ul></div><div class=\\"sc-EgOXT bNSpXO\\"></div></div></div></div></div></div><div id=\\"section/Authentication/GitLab_PersonalAccessToken\\" data-section-id=\\"section/Authentication/GitLab_PersonalAccessToken\\" class=\\"sc-dcJsrY bBkGhy\\"><div class=\\"sc-kAyceB hBQWIZ\\"><div class=\\"sc-fqkvVR oJKYx\\"><h2 class=\\"sc-jXbUNg fWnwAh\\">GitLab_PersonalAccessToken</h2><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><p>GitLab Personal Access Token description</p>
|
||||
</div><div class=\\"sc-ejfMa-d a-DjBE\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Security Scheme Type: </b><span>API Key</span></div><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div></div></div></div><div id=\\"section/Authentication/GitLab_OpenIdConnect\\" data-section-id=\\"section/Authentication/GitLab_OpenIdConnect\\" class=\\"sc-dcJsrY bBkGhy\\"><div class=\\"sc-kAyceB hBQWIZ\\"><div class=\\"sc-fqkvVR oJKYx\\"><h2 class=\\"sc-jXbUNg fWnwAh\\">GitLab_OpenIdConnect</h2><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><p>GitLab OpenIdConnect description</p>
|
||||
</div><div class=\\"sc-ejfMa-d a-DjBE\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Security Scheme Type: </b><span>OpenID Connect</span></div><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Connect URL: </b><code><a target=\\"_blank\\" rel=\\"noopener noreferrer\\" href=\\"https://gitlab.com/.well-known/openid-configuration\\">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div></div></div></div><div id=\\"section/Authentication/basicAuth\\" data-section-id=\\"section/Authentication/basicAuth\\" class=\\"sc-dcJsrY bBkGhy\\"><div class=\\"sc-kAyceB hBQWIZ\\"><div class=\\"sc-fqkvVR oJKYx\\"><h2 class=\\"sc-jXbUNg fWnwAh\\">basicAuth</h2><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"></div><div class=\\"sc-ejfMa-d a-DjBE\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Security Scheme Type: </b><span>HTTP</span></div><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class=\\"sc-dkmUuB hFwAIA\\"></div></div></div></div></div></div>"
|
||||
</div><div class="sc-iEXKAA ebCiwb"><div class="sc-ejfMa-d bdDYxc"><b>Security Scheme Type: </b><span>OAuth2</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Flow type: </b><code>implicit </code></div><div class="sc-ejfMa-d bdDYxc"><strong> Authorization URL: </strong><code><a target="_blank" rel="noopener noreferrer" href="http://petstore.swagger.io/api/oauth/dialog">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div class="sc-ejfMa-d bdDYxc"><b> Scopes: </b></div><div class="sc-EgOXT kRIdPi container" style="height: 4em;"><ul><li><code>write:pets</code> - <div class="sc-eeDRCY sc-eBMEME sc-fhzFiK gTGgei iCmQdS hXtrri redoc-markdown"><p>modify pets in your account</p>
|
||||
</div></li><li><code>read:pets</code> - <div class="sc-eeDRCY sc-eBMEME sc-fhzFiK gTGgei iCmQdS hXtrri redoc-markdown"><p>read your pets</p>
|
||||
</div></li></ul></div><div class="sc-eZYNyq dIKkVb"></div></div></div></div></div></div><div id="section/Authentication/GitLab_PersonalAccessToken" data-section-id="section/Authentication/GitLab_PersonalAccessToken" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">GitLab_PersonalAccessToken</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab Personal Access Token description</p>
|
||||
</div><div class="sc-iEXKAA ebCiwb"><div class="sc-ejfMa-d bdDYxc"><b>Security Scheme Type: </b><span>API Key</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div></div></div></div><div id="section/Authentication/GitLab_OpenIdConnect" data-section-id="section/Authentication/GitLab_OpenIdConnect" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">GitLab_OpenIdConnect</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab OpenIdConnect description</p>
|
||||
</div><div class="sc-iEXKAA ebCiwb"><div class="sc-ejfMa-d bdDYxc"><b>Security Scheme Type: </b><span>OpenID Connect</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Connect URL: </b><code><a target="_blank" rel="noopener noreferrer" href="https://gitlab.com/.well-known/openid-configuration">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div></div></div></div><div id="section/Authentication/basicAuth" data-section-id="section/Authentication/basicAuth" class="sc-dcJsrY bBkGhy"><div class="sc-kAyceB hBQWIZ"><div class="sc-fqkvVR oJKYx"><h2 class="sc-jXbUNg fWnwAh">basicAuth</h2><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"></div><div class="sc-iEXKAA ebCiwb"><div class="sc-ejfMa-d bdDYxc"><b>Security Scheme Type: </b><span>HTTP</span></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class="sc-ejfMa-d bdDYxc"></div></div></div></div></div></div>"
|
||||
`;
|
||||
|
||||
exports[`SecurityRequirement should render authDefinition 1`] = `"<div class=\\"sc-bDumWk iWBBny\\"><div class=\\"sc-sLsrZ hgeUJn\\"><h5 class=\\"sc-dAlyuH sc-fifgRP jbQuod kWJur\\">Authorizations:</h5><svg class=\\"sc-cwHptR iZRiKW\\" 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 \\"></polygon></svg></div><div class=\\"sc-dBmzty eoFcYg\\"><span class=\\"sc-kbousE cpXQuZ\\">(<span class=\\"sc-gfoqjT kbvnry\\">API Key: <i>GitLab_PersonalAccessToken</i></span><span class=\\"sc-gfoqjT kbvnry\\">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class=\\"sc-gfoqjT kbvnry\\">HTTP: <i>basicAuth</i></span>) </span><span class=\\"sc-kbousE cpXQuZ\\"><span class=\\"sc-gfoqjT kbvnry\\">OAuth2: <i>petstore_auth</i></span></span></div></div>,"`;
|
||||
exports[`SecurityRequirement should render authDefinition 1`] = `"<div class="sc-dkmUuB fUBzjk"><div class="sc-dBmzty iDyBRL"><h5 class="sc-dAlyuH sc-bDumWk jbQuod feBYnB">Authorizations:</h5><svg class="sc-cwHptR iZRiKW" 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 "></polygon></svg></div><div class="sc-fifgRP eqIYDA"><span class="sc-sLsrZ jmro">(<span class="sc-kbousE iMnLRS">API Key: <i>GitLab_PersonalAccessToken</i></span><span class="sc-kbousE iMnLRS">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class="sc-kbousE iMnLRS">HTTP: <i>basicAuth</i></span>) </span><span class="sc-sLsrZ jmro"><span class="sc-kbousE iMnLRS">OAuth2: <i>petstore_auth</i></span></span></div></div>,"`;
|
||||
|
||||
exports[`SecurityRequirement should render authDefinition 2`] = `
|
||||
"<div class=\\"sc-bDumWk gtsPcy\\"><div class=\\"sc-sLsrZ hgeUJn\\"><h5 class=\\"sc-dAlyuH sc-fifgRP jbQuod kWJur\\">Authorizations:</h5><svg class=\\"sc-cwHptR dSJqIk\\" 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 \\"></polygon></svg></div><div class=\\"sc-dBmzty llvZdI\\"><span class=\\"sc-kbousE dOwJQz\\">(<span class=\\"sc-gfoqjT kbvnry\\">API Key: <i>GitLab_PersonalAccessToken</i></span><span class=\\"sc-gfoqjT kbvnry\\">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class=\\"sc-gfoqjT kbvnry\\">HTTP: <i>basicAuth</i></span>) </span><span class=\\"sc-kbousE dOwJQz\\"><span class=\\"sc-gfoqjT kbvnry\\">OAuth2: <i>petstore_auth</i> (<code class=\\"sc-eyvILC bzHwfc\\">write:pets</code><code class=\\"sc-eyvILC bzHwfc\\">read:pets</code>) </span></span></div></div><div class=\\"sc-ejfMa-d a-DjBE\\"><h5><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\\"></path></svg> OAuth2: petstore_auth</h5><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><p>Get access to data while protecting your account credentials.
|
||||
"<div class="sc-dkmUuB KTEsk"><div class="sc-dBmzty iDyBRL"><h5 class="sc-dAlyuH sc-bDumWk jbQuod feBYnB">Authorizations:</h5><svg class="sc-cwHptR dSJqIk" 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 "></polygon></svg></div><div class="sc-fifgRP gNcumo"><span class="sc-sLsrZ iTheFK">(<span class="sc-kbousE iMnLRS">API Key: <i>GitLab_PersonalAccessToken</i></span><span class="sc-kbousE iMnLRS">OpenID Connect: <i>GitLab_OpenIdConnect</i></span><span class="sc-kbousE iMnLRS">HTTP: <i>basicAuth</i></span>) </span><span class="sc-sLsrZ iTheFK"><span class="sc-kbousE iMnLRS">OAuth2: <i>petstore_auth</i> (<code class="sc-gfoqjT dapMvh">write:pets</code><code class="sc-gfoqjT dapMvh">read:pets</code>) </span></span></div></div><div class="sc-iEXKAA ebCiwb"><h5><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"></path></svg> OAuth2: petstore_auth</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>Get access to data while protecting your account credentials.
|
||||
OAuth2 is also a safer and more secure way to give you access.</p>
|
||||
</div><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Flow type: </b><code>implicit </code></div><div class=\\"sc-dkmUuB hFwAIA\\"><strong> Authorization URL: </strong><code><a target=\\"_blank\\" rel=\\"noopener noreferrer\\" href=\\"http://petstore.swagger.io/api/oauth/dialog\\">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div><b>Required scopes: </b><code>write:pets</code> <code>read:pets</code> </div><div class=\\"sc-dkmUuB hFwAIA\\"><b> Scopes: </b></div><div class=\\"sc-iEXKAA blExNw container\\" style=\\"height: 4em;\\"><ul><li><code>write:pets</code> - <div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru sc-fhzFiK hXtrri redoc-markdown\\"><p>modify pets in your account</p>
|
||||
</div></li><li><code>read:pets</code> - <div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru sc-fhzFiK hXtrri redoc-markdown\\"><p>read your pets</p>
|
||||
</div></li></ul></div><div class=\\"sc-EgOXT bNSpXO\\"></div></div></div><div class=\\"sc-ejfMa-d a-DjBE\\"><h5><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\\"></path></svg> API Key: GitLab_PersonalAccessToken</h5><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><p>GitLab Personal Access Token description</p>
|
||||
</div><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div><div class=\\"sc-ejfMa-d a-DjBE\\"><h5><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\\"></path></svg> OpenID Connect: GitLab_OpenIdConnect</h5><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><p>GitLab OpenIdConnect description</p>
|
||||
</div><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>Connect URL: </b><code><a target=\\"_blank\\" rel=\\"noopener noreferrer\\" href=\\"https://gitlab.com/.well-known/openid-configuration\\">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div><div class=\\"sc-ejfMa-d a-DjBE\\"><h5><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\\"></path></svg> HTTP: basicAuth</h5><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"></div><div class=\\"sc-eeDRCY sc-eBMEME gTGgei fMmru\\"><div class=\\"sc-dkmUuB hFwAIA\\"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class=\\"sc-dkmUuB hFwAIA\\"></div></div></div>,"
|
||||
</div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Flow type: </b><code>implicit </code></div><div class="sc-ejfMa-d bdDYxc"><strong> Authorization URL: </strong><code><a target="_blank" rel="noopener noreferrer" href="http://petstore.swagger.io/api/oauth/dialog">http://petstore.swagger.io/api/oauth/dialog</a></code></div><div><b>Required scopes: </b><code>write:pets</code> <code>read:pets</code> </div><div class="sc-ejfMa-d bdDYxc"><b> Scopes: </b></div><div class="sc-EgOXT kRIdPi container" style="height: 4em;"><ul><li><code>write:pets</code> - <div class="sc-eeDRCY sc-eBMEME sc-fhzFiK gTGgei iCmQdS hXtrri redoc-markdown"><p>modify pets in your account</p>
|
||||
</div></li><li><code>read:pets</code> - <div class="sc-eeDRCY sc-eBMEME sc-fhzFiK gTGgei iCmQdS hXtrri redoc-markdown"><p>read your pets</p>
|
||||
</div></li></ul></div><div class="sc-eZYNyq dIKkVb"></div></div></div><div class="sc-iEXKAA ebCiwb"><h5><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"></path></svg> API Key: GitLab_PersonalAccessToken</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab Personal Access Token description</p>
|
||||
</div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Header parameter name: </b><code>PRIVATE-TOKEN</code></div></div></div><div class="sc-iEXKAA ebCiwb"><h5><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"></path></svg> OpenID Connect: GitLab_OpenIdConnect</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><p>GitLab OpenIdConnect description</p>
|
||||
</div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>Connect URL: </b><code><a target="_blank" rel="noopener noreferrer" href="https://gitlab.com/.well-known/openid-configuration">https://gitlab.com/.well-known/openid-configuration</a></code></div></div></div><div class="sc-iEXKAA ebCiwb"><h5><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"></path></svg> HTTP: basicAuth</h5><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"></div><div class="sc-eeDRCY sc-eBMEME gTGgei fMmru"><div class="sc-ejfMa-d bdDYxc"><b>HTTP Authorization Scheme: </b><code>basic</code></div><div class="sc-ejfMa-d bdDYxc"></div></div></div>,"
|
||||
`;
|
||||
|
|
|
@ -2,7 +2,9 @@ import * as React from 'react';
|
|||
import { ThemeProvider } from 'styled-components';
|
||||
import defaultTheme, { resolveTheme } from '../theme';
|
||||
|
||||
export default class TestThemeProvider extends React.Component {
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
export default class TestThemeProvider extends React.Component<PropsWithChildren<any>> {
|
||||
render() {
|
||||
return (
|
||||
<ThemeProvider theme={resolveTheme(defaultTheme)}>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { OpenAPISpec, OpenAPIPaths, OpenAPITag, OpenAPISchema } from '../types';
|
||||
import type { OpenAPIPaths, OpenAPITag, OpenAPISchema } from '../types';
|
||||
import { isOperationName, JsonPointer, alphabeticallyByProp } from '../utils';
|
||||
import { MarkdownRenderer } from './MarkdownRenderer';
|
||||
import { GroupModel, OperationModel } from './models';
|
||||
|
@ -17,9 +17,17 @@ export class MenuBuilder {
|
|||
options: RedocNormalizedOptions,
|
||||
): ContentItemModel[] {
|
||||
const spec = parser.spec;
|
||||
const { schemaDefinitionsTagName } = options;
|
||||
|
||||
const items: ContentItemModel[] = [];
|
||||
const tagsMap = MenuBuilder.getTagsWithOperations(parser, spec);
|
||||
const tags = [...(spec.tags || [])];
|
||||
const hasAutogenerated = tags.find(tag => tag?.name === schemaDefinitionsTagName);
|
||||
|
||||
if (!hasAutogenerated && schemaDefinitionsTagName) {
|
||||
tags.push({ name: schemaDefinitionsTagName });
|
||||
}
|
||||
const tagsMap = MenuBuilder.getTagsWithOperations(parser, tags);
|
||||
|
||||
items.push(...MenuBuilder.addMarkdownItems(spec.info.description || '', undefined, 1, options));
|
||||
if (spec['x-tagGroups'] && spec['x-tagGroups'].length > 0) {
|
||||
items.push(
|
||||
|
@ -28,6 +36,7 @@ export class MenuBuilder {
|
|||
} else {
|
||||
items.push(...MenuBuilder.getTagsItems(parser, tagsMap, undefined, undefined, options));
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
|
@ -141,6 +150,7 @@ export class MenuBuilder {
|
|||
parser,
|
||||
tag,
|
||||
parent: item,
|
||||
schemaDefinitionsTagName: options.schemaDefinitionsTagName,
|
||||
});
|
||||
|
||||
item.items = [
|
||||
|
@ -195,10 +205,11 @@ export class MenuBuilder {
|
|||
/**
|
||||
* collects tags and maps each tag to list of operations belonging to this tag
|
||||
*/
|
||||
static getTagsWithOperations(parser: OpenAPIParser, spec: OpenAPISpec): TagsInfoMap {
|
||||
static getTagsWithOperations(parser: OpenAPIParser, explicitTags: OpenAPITag[]): TagsInfoMap {
|
||||
const { spec } = parser;
|
||||
const tags: TagsInfoMap = {};
|
||||
const webhooks = spec['x-webhooks'] || spec.webhooks;
|
||||
for (const tag of spec.tags || []) {
|
||||
for (const tag of explicitTags || []) {
|
||||
tags[tag.name] = { ...tag, operations: [] };
|
||||
}
|
||||
|
||||
|
@ -260,14 +271,18 @@ export class MenuBuilder {
|
|||
parser,
|
||||
tag,
|
||||
parent,
|
||||
schemaDefinitionsTagName,
|
||||
}: {
|
||||
parser: OpenAPIParser;
|
||||
tag: TagInfo;
|
||||
parent: GroupModel;
|
||||
schemaDefinitionsTagName?: string;
|
||||
}): GroupModel[] {
|
||||
const defaultTags = schemaDefinitionsTagName ? [schemaDefinitionsTagName] : [];
|
||||
|
||||
return Object.entries(parser.spec.components?.schemas || {})
|
||||
.map(([schemaName, schema]) => {
|
||||
const schemaTags = schema['x-tags'];
|
||||
const schemaTags = schema['x-tags'] || defaultTags;
|
||||
if (!schemaTags?.includes(tag.name)) return null;
|
||||
|
||||
const item = new GroupModel(
|
||||
|
|
|
@ -50,7 +50,7 @@ export class OpenAPIParser {
|
|||
/**
|
||||
* get spec part by JsonPointer ($ref)
|
||||
*/
|
||||
byRef = <T extends any = any>(ref: string): T | undefined => {
|
||||
byRef = <T = any>(ref: string): T | undefined => {
|
||||
let res;
|
||||
if (!this.spec) {
|
||||
return;
|
||||
|
@ -70,7 +70,7 @@ export class OpenAPIParser {
|
|||
/**
|
||||
* checks if the object is OpenAPI reference (contains $ref property)
|
||||
*/
|
||||
isRef<T extends unknown>(obj: OpenAPIRef | T): obj is OpenAPIRef {
|
||||
isRef<T>(obj: OpenAPIRef | T): obj is OpenAPIRef {
|
||||
if (!obj) {
|
||||
return false;
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ export class OpenAPIParser {
|
|||
* @param forceCircular whether to dereference even if it is circular ref
|
||||
* @param mergeAsAllOf
|
||||
*/
|
||||
deref<T extends unknown>(
|
||||
deref<T>(
|
||||
obj: OpenAPIRef | T,
|
||||
baseRefsStack: string[] = [],
|
||||
mergeAsAllOf = false,
|
||||
|
@ -124,7 +124,7 @@ export class OpenAPIParser {
|
|||
};
|
||||
}
|
||||
|
||||
mergeRefs<T extends unknown>(ref: OpenAPIRef, resolved: T, mergeAsAllOf: boolean): T {
|
||||
mergeRefs<T>(ref: OpenAPIRef, resolved: T, mergeAsAllOf: boolean): T {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { $ref, ...rest } = ref;
|
||||
const keys = Object.keys(rest);
|
||||
|
@ -364,14 +364,18 @@ export class OpenAPIParser {
|
|||
|
||||
const allOf = schema.allOf;
|
||||
for (let i = 0; i < allOf.length; i++) {
|
||||
const sub = allOf[i];
|
||||
if (Array.isArray(sub.oneOf)) {
|
||||
const { oneOf, ...sub } = allOf[i];
|
||||
if (!oneOf) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(oneOf)) {
|
||||
const beforeAllOf = allOf.slice(0, i);
|
||||
const afterAllOf = allOf.slice(i + 1);
|
||||
const siblingValues = Object.keys(sub).length > 0 ? [sub] : [];
|
||||
return {
|
||||
oneOf: sub.oneOf.map((part: OpenAPISchema) => {
|
||||
oneOf: oneOf.map((part: OpenAPISchema) => {
|
||||
return {
|
||||
allOf: [...beforeAllOf, part, ...afterAllOf],
|
||||
allOf: [...beforeAllOf, ...siblingValues, part, ...afterAllOf],
|
||||
'x-refsStack': refsStack,
|
||||
};
|
||||
}),
|
||||
|
|
|
@ -6,35 +6,48 @@ import { setRedocLabels } from './Labels';
|
|||
import { SideNavStyleEnum } from './types';
|
||||
import type { LabelsConfigRaw, MDXComponentMeta } from './types';
|
||||
|
||||
export type DownloadUrlsConfig = {
|
||||
title?: string;
|
||||
url: string;
|
||||
}[];
|
||||
|
||||
export interface RedocRawOptions {
|
||||
theme?: ThemeInterface;
|
||||
scrollYOffset?: number | string | (() => number);
|
||||
hideHostname?: boolean | string;
|
||||
expandResponses?: string | 'all';
|
||||
requiredPropsFirst?: boolean | string;
|
||||
requiredPropsFirst?: boolean | string; // remove in next major release
|
||||
sortRequiredPropsFirst?: boolean | string;
|
||||
sortPropsAlphabetically?: boolean | string;
|
||||
sortEnumValuesAlphabetically?: boolean | string;
|
||||
sortOperationsAlphabetically?: boolean | string;
|
||||
sortTagsAlphabetically?: boolean | string;
|
||||
nativeScrollbars?: boolean | string;
|
||||
pathInMiddlePanel?: boolean | string;
|
||||
untrustedSpec?: boolean | string;
|
||||
untrustedSpec?: boolean | string; // remove in next major release
|
||||
sanitize?: boolean | string;
|
||||
hideLoading?: boolean | string;
|
||||
hideDownloadButton?: boolean | string;
|
||||
hideDownloadButton?: boolean | string; // remove in next major release
|
||||
hideDownloadButtons?: boolean | string;
|
||||
downloadFileName?: string;
|
||||
downloadDefinitionUrl?: string;
|
||||
downloadUrls?: DownloadUrlsConfig;
|
||||
disableSearch?: boolean | string;
|
||||
onlyRequiredInSamples?: boolean | string;
|
||||
showExtensions?: boolean | string | string[];
|
||||
sideNavStyle?: SideNavStyleEnum;
|
||||
hideSingleRequestSampleTab?: boolean | string;
|
||||
hideRequestPayloadSample?: boolean;
|
||||
menuToggle?: boolean | string;
|
||||
jsonSampleExpandLevel?: number | string | 'all';
|
||||
jsonSampleExpandLevel?: number | string | 'all'; // remove in next major release
|
||||
jsonSamplesExpandLevel?: number | string | 'all';
|
||||
hideSchemaTitles?: boolean | string;
|
||||
simpleOneOfTypeLabel?: boolean | string;
|
||||
payloadSampleIdx?: number;
|
||||
expandSingleSchemaField?: boolean | string;
|
||||
schemaExpansionLevel?: number | string | 'all';
|
||||
schemaExpansionLevel?: number | string | 'all'; // remove in next major release
|
||||
schemasExpansionLevel?: number | string | 'all';
|
||||
schemaDefinitionsTagName?: string;
|
||||
showObjectSchemaExamples?: boolean | string;
|
||||
showSecuritySchemeType?: boolean;
|
||||
hideSecuritySection?: boolean;
|
||||
|
@ -51,11 +64,13 @@ export interface RedocRawOptions {
|
|||
maxDisplayedEnumValues?: number;
|
||||
ignoreNamedSchemas?: string[] | string;
|
||||
hideSchemaPattern?: boolean;
|
||||
generatedPayloadSamplesMaxDepth?: number;
|
||||
generatedPayloadSamplesMaxDepth?: number; // remove in next major release
|
||||
generatedSamplesMaxDepth?: number;
|
||||
nonce?: string;
|
||||
hideFab?: boolean;
|
||||
minCharacterLengthToInitSearch?: number;
|
||||
showWebhookVerb?: boolean;
|
||||
hidePropertiesPrefix?: boolean;
|
||||
}
|
||||
|
||||
export function argValueToBoolean(val?: string | boolean, defaultValue?: boolean): boolean {
|
||||
|
@ -215,30 +230,33 @@ export class RedocNormalizedOptions {
|
|||
scrollYOffset: () => number;
|
||||
hideHostname: boolean;
|
||||
expandResponses: { [code: string]: boolean } | 'all';
|
||||
requiredPropsFirst: boolean;
|
||||
sortRequiredPropsFirst: boolean;
|
||||
sortPropsAlphabetically: boolean;
|
||||
sortEnumValuesAlphabetically: boolean;
|
||||
sortOperationsAlphabetically: boolean;
|
||||
sortTagsAlphabetically: boolean;
|
||||
nativeScrollbars: boolean;
|
||||
pathInMiddlePanel: boolean;
|
||||
untrustedSpec: boolean;
|
||||
hideDownloadButton: boolean;
|
||||
sanitize: boolean;
|
||||
hideDownloadButtons: boolean;
|
||||
downloadFileName?: string;
|
||||
downloadDefinitionUrl?: string;
|
||||
downloadUrls?: DownloadUrlsConfig;
|
||||
disableSearch: boolean;
|
||||
onlyRequiredInSamples: boolean;
|
||||
showExtensions: boolean | string[];
|
||||
sideNavStyle: SideNavStyleEnum;
|
||||
hideSingleRequestSampleTab: boolean;
|
||||
hideRequestPayloadSample: boolean;
|
||||
menuToggle: boolean;
|
||||
jsonSampleExpandLevel: number;
|
||||
jsonSamplesExpandLevel: number;
|
||||
enumSkipQuotes: boolean;
|
||||
hideSchemaTitles: boolean;
|
||||
simpleOneOfTypeLabel: boolean;
|
||||
payloadSampleIdx: number;
|
||||
expandSingleSchemaField: boolean;
|
||||
schemaExpansionLevel: number;
|
||||
schemasExpansionLevel: number;
|
||||
schemaDefinitionsTagName?: string;
|
||||
showObjectSchemaExamples: boolean;
|
||||
showSecuritySchemeType?: boolean;
|
||||
hideSecuritySection?: boolean;
|
||||
|
@ -252,10 +270,11 @@ export class RedocNormalizedOptions {
|
|||
|
||||
ignoreNamedSchemas: Set<string>;
|
||||
hideSchemaPattern: boolean;
|
||||
generatedPayloadSamplesMaxDepth: number;
|
||||
generatedSamplesMaxDepth: number;
|
||||
hideFab: boolean;
|
||||
minCharacterLengthToInitSearch: number;
|
||||
showWebhookVerb: boolean;
|
||||
hidePropertiesPrefix?: boolean;
|
||||
|
||||
nonce?: string;
|
||||
|
||||
|
@ -286,32 +305,39 @@ export class RedocNormalizedOptions {
|
|||
this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset);
|
||||
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
|
||||
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
|
||||
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
|
||||
this.sortRequiredPropsFirst = argValueToBoolean(
|
||||
raw.sortRequiredPropsFirst || raw.requiredPropsFirst,
|
||||
);
|
||||
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
|
||||
this.sortEnumValuesAlphabetically = argValueToBoolean(raw.sortEnumValuesAlphabetically);
|
||||
this.sortOperationsAlphabetically = argValueToBoolean(raw.sortOperationsAlphabetically);
|
||||
this.sortTagsAlphabetically = argValueToBoolean(raw.sortTagsAlphabetically);
|
||||
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
|
||||
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
|
||||
this.untrustedSpec = argValueToBoolean(raw.untrustedSpec);
|
||||
this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton);
|
||||
this.sanitize = argValueToBoolean(raw.sanitize || raw.untrustedSpec);
|
||||
this.hideDownloadButtons = argValueToBoolean(raw.hideDownloadButtons || raw.hideDownloadButton);
|
||||
this.downloadFileName = raw.downloadFileName;
|
||||
this.downloadDefinitionUrl = raw.downloadDefinitionUrl;
|
||||
this.downloadUrls = raw.downloadUrls;
|
||||
this.disableSearch = argValueToBoolean(raw.disableSearch);
|
||||
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
|
||||
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);
|
||||
this.sideNavStyle = RedocNormalizedOptions.normalizeSideNavStyle(raw.sideNavStyle);
|
||||
this.hideSingleRequestSampleTab = argValueToBoolean(raw.hideSingleRequestSampleTab);
|
||||
this.hideRequestPayloadSample = argValueToBoolean(raw.hideRequestPayloadSample);
|
||||
this.menuToggle = argValueToBoolean(raw.menuToggle, true);
|
||||
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
||||
raw.jsonSampleExpandLevel,
|
||||
this.jsonSamplesExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
||||
raw.jsonSamplesExpandLevel || raw.jsonSampleExpandLevel,
|
||||
);
|
||||
this.enumSkipQuotes = argValueToBoolean(raw.enumSkipQuotes);
|
||||
this.hideSchemaTitles = argValueToBoolean(raw.hideSchemaTitles);
|
||||
this.simpleOneOfTypeLabel = argValueToBoolean(raw.simpleOneOfTypeLabel);
|
||||
this.payloadSampleIdx = RedocNormalizedOptions.normalizePayloadSampleIdx(raw.payloadSampleIdx);
|
||||
this.expandSingleSchemaField = argValueToBoolean(raw.expandSingleSchemaField);
|
||||
this.schemaExpansionLevel = argValueToExpandLevel(raw.schemaExpansionLevel);
|
||||
this.schemasExpansionLevel = argValueToExpandLevel(
|
||||
raw.schemasExpansionLevel || raw.schemaExpansionLevel,
|
||||
);
|
||||
this.schemaDefinitionsTagName = raw.schemaDefinitionsTagName;
|
||||
this.showObjectSchemaExamples = argValueToBoolean(raw.showObjectSchemaExamples);
|
||||
this.showSecuritySchemeType = argValueToBoolean(raw.showSecuritySchemeType);
|
||||
this.hideSecuritySection = argValueToBoolean(raw.hideSecuritySection);
|
||||
|
@ -327,13 +353,13 @@ export class RedocNormalizedOptions {
|
|||
: raw.ignoreNamedSchemas?.split(',').map(s => s.trim());
|
||||
this.ignoreNamedSchemas = new Set(ignoreNamedSchemas);
|
||||
this.hideSchemaPattern = argValueToBoolean(raw.hideSchemaPattern);
|
||||
this.generatedPayloadSamplesMaxDepth =
|
||||
RedocNormalizedOptions.normalizeGeneratedPayloadSamplesMaxDepth(
|
||||
raw.generatedPayloadSamplesMaxDepth,
|
||||
);
|
||||
this.generatedSamplesMaxDepth = RedocNormalizedOptions.normalizeGeneratedPayloadSamplesMaxDepth(
|
||||
raw.generatedSamplesMaxDepth || raw.generatedPayloadSamplesMaxDepth,
|
||||
);
|
||||
this.nonce = raw.nonce;
|
||||
this.hideFab = argValueToBoolean(raw.hideFab);
|
||||
this.minCharacterLengthToInitSearch = argValueToNumber(raw.minCharacterLengthToInitSearch) || 3;
|
||||
this.showWebhookVerb = argValueToBoolean(raw.showWebhookVerb);
|
||||
this.hidePropertiesPrefix = argValueToBoolean(raw.hidePropertiesPrefix, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { bind } from 'decko';
|
||||
import * as EventEmitter from 'eventemitter3';
|
||||
import { EventEmitter } from 'eventemitter3';
|
||||
|
||||
import { IS_BROWSER, querySelector, Throttle } from '../utils';
|
||||
import type { RedocNormalizedOptions } from './RedocNormalizedOptions';
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Models Schema should correct resolve double $ref if no need sibling 1`] = `
|
||||
Object {
|
||||
"refsStack": Array [
|
||||
{
|
||||
"refsStack": [
|
||||
"#/components/schemas/Parent",
|
||||
],
|
||||
"resolved": Object {
|
||||
"properties": Object {
|
||||
"test": Object {
|
||||
"resolved": {
|
||||
"properties": {
|
||||
"test": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
|
@ -17,38 +17,46 @@ Object {
|
|||
`;
|
||||
|
||||
exports[`Models Schema should hoist oneOfs when mergin allOf 1`] = `
|
||||
Object {
|
||||
"oneOf": Array [
|
||||
Object {
|
||||
"allOf": Array [
|
||||
Object {
|
||||
"properties": Object {
|
||||
"username": Object {
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The user's ID",
|
||||
"type": "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"username": {
|
||||
"description": "The user's name",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"properties": Object {
|
||||
"extra": Object {
|
||||
{
|
||||
"properties": {
|
||||
"extra": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"oneOf": Array [
|
||||
Object {
|
||||
"properties": Object {
|
||||
"password": Object {
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"password": {
|
||||
"description": "The user's password",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"properties": Object {
|
||||
"mobile": Object {
|
||||
{
|
||||
"properties": {
|
||||
"mobile": {
|
||||
"description": "The user's mobile",
|
||||
"type": "string",
|
||||
},
|
||||
|
@ -59,36 +67,93 @@ Object {
|
|||
],
|
||||
"x-refsStack": undefined,
|
||||
},
|
||||
Object {
|
||||
"allOf": Array [
|
||||
Object {
|
||||
"properties": Object {
|
||||
"email": Object {
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The user's ID",
|
||||
"type": "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "The user's email",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"properties": Object {
|
||||
"extra": Object {
|
||||
{
|
||||
"properties": {
|
||||
"extra": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"oneOf": Array [
|
||||
Object {
|
||||
"properties": Object {
|
||||
"password": Object {
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"password": {
|
||||
"description": "The user's password",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"properties": Object {
|
||||
"mobile": Object {
|
||||
{
|
||||
"properties": {
|
||||
"mobile": {
|
||||
"description": "The user's mobile",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
"x-refsStack": undefined,
|
||||
},
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The user's ID",
|
||||
"type": "integer",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The user's ID",
|
||||
"format": "uuid",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"extra": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"password": {
|
||||
"description": "The user's password",
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"mobile": {
|
||||
"description": "The user's mobile",
|
||||
"type": "string",
|
||||
},
|
||||
|
@ -104,11 +169,11 @@ Object {
|
|||
`;
|
||||
|
||||
exports[`Models Schema should override description from $ref of the referenced component, when sibling description exists 1`] = `
|
||||
Object {
|
||||
"refsStack": Array [
|
||||
{
|
||||
"refsStack": [
|
||||
"#/components/schemas/Test",
|
||||
],
|
||||
"resolved": Object {
|
||||
"resolved": {
|
||||
"description": "Overriden description",
|
||||
"type": "object",
|
||||
},
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`prism.js helpers highlight js code 1`] = `"<span class=\\"token keyword\\">const</span> t <span class=\\"token operator\\">=</span> <span class=\\"token number\\">10</span><span class=\\"token punctuation\\">;</span>"`;
|
||||
exports[`prism.js helpers highlight js code 1`] = `"<span class="token keyword">const</span> t <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span>"`;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.0",
|
||||
"title": "Test"
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Test": {
|
||||
"type": "array",
|
||||
"description": "test description",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "test description",
|
||||
"enum": ["authorize", "do-nothing"],
|
||||
"x-enumDescriptions": {
|
||||
"authorize-and-void": "Will create an authorize transaction in the amount/currency of the request, followed by a void",
|
||||
"do-nothing": "Will do nothing, and return an approved `setup` transaction. This is the default behavior."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,12 @@
|
|||
"test": {
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The user's ID",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
|
@ -25,6 +31,15 @@
|
|||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "The user's ID",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -59,4 +74,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,10 +139,18 @@ describe('Models', () => {
|
|||
} as any;
|
||||
|
||||
const opts = new RedocNormalizedOptions({
|
||||
downloadDefinitionUrl: 'https:test.com/filename.yaml',
|
||||
downloadUrls: [{ title: 'Openapi description', url: 'https:test.com/filename.yaml' }],
|
||||
});
|
||||
const info = new ApiInfoModel(parser, opts);
|
||||
expect(info.downloadLink).toEqual('https:test.com/filename.yaml');
|
||||
expect(info.downloadUrls).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"title": "Openapi description",
|
||||
"url": "https:test.com/filename.yaml",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(info.downloadFileName).toMatchInlineSnapshot(`"openapi.json"`);
|
||||
});
|
||||
|
||||
test('should correctly populate download link and download file name', () => {
|
||||
|
@ -158,8 +166,29 @@ describe('Models', () => {
|
|||
downloadFileName: 'test.yaml',
|
||||
});
|
||||
const info = new ApiInfoModel(parser, opts);
|
||||
expect(info.downloadLink).toEqual('https:test.com/filename.yaml');
|
||||
expect(info.downloadFileName).toEqual('test.yaml');
|
||||
expect(info.downloadUrls).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"title": "Download",
|
||||
"url": "https:test.com/filename.yaml",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(info.downloadFileName).toMatchInlineSnapshot(`"test.yaml"`);
|
||||
|
||||
const opts2 = new RedocNormalizedOptions({
|
||||
downloadUrls: [{ title: 'Download file', url: 'https:test.com/filename.yaml' }],
|
||||
});
|
||||
const info2 = new ApiInfoModel(parser, opts2);
|
||||
expect(info2.downloadUrls).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"title": "Download file",
|
||||
"url": "https:test.com/filename.yaml",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(info2.downloadFileName).toMatchInlineSnapshot(`"openapi.json"`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,17 @@ describe('Models', () => {
|
|||
describe('Schema', () => {
|
||||
let parser;
|
||||
|
||||
test('parsing nested x-enumDescription', () => {
|
||||
const spec = require('../fixtures/nestedEnumDescroptionSample.json');
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
const testSchema = spec.components.schemas.Test;
|
||||
const schemaModel = new SchemaModel(parser, testSchema, '', opts);
|
||||
|
||||
expect(schemaModel['x-enumDescriptions']).toStrictEqual(
|
||||
testSchema.items['x-enumDescriptions'],
|
||||
);
|
||||
});
|
||||
|
||||
test('discriminator with one field', () => {
|
||||
const spec = require('../fixtures/discriminator.json');
|
||||
parser = new OpenAPIParser(spec, undefined, opts);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Models Schema schemaDefinition should resolve field with conditional operators 1`] = `
|
||||
Object {
|
||||
{
|
||||
"allOf": undefined,
|
||||
"default": undefined,
|
||||
"description": undefined,
|
||||
"items": Object {
|
||||
"items": {
|
||||
"allOf": undefined,
|
||||
"description": undefined,
|
||||
"format": "url",
|
||||
|
@ -14,7 +14,7 @@ Object {
|
|||
"type": "string",
|
||||
"writeOnly": undefined,
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
"x-parentRefs": [],
|
||||
},
|
||||
"maxItems": 20,
|
||||
"minItems": 1,
|
||||
|
@ -24,16 +24,16 @@ Object {
|
|||
"writeOnly": undefined,
|
||||
"x-circular-ref": undefined,
|
||||
"x-displayName": "isString",
|
||||
"x-parentRefs": Array [],
|
||||
"x-parentRefs": [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Models Schema schemaDefinition should resolve field with conditional operators 2`] = `
|
||||
Object {
|
||||
{
|
||||
"allOf": undefined,
|
||||
"default": undefined,
|
||||
"description": undefined,
|
||||
"items": Object {
|
||||
"items": {
|
||||
"allOf": undefined,
|
||||
"description": undefined,
|
||||
"format": "url",
|
||||
|
@ -42,14 +42,14 @@ Object {
|
|||
"type": "string",
|
||||
"writeOnly": undefined,
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
"x-parentRefs": [],
|
||||
},
|
||||
"maxItems": 10,
|
||||
"minItems": 1,
|
||||
"pattern": "\\\\d+",
|
||||
"pattern": "\\d+",
|
||||
"readOnly": undefined,
|
||||
"title": "notString",
|
||||
"type": Array [
|
||||
"type": [
|
||||
"string",
|
||||
"integer",
|
||||
"null",
|
||||
|
@ -57,23 +57,23 @@ Object {
|
|||
"writeOnly": undefined,
|
||||
"x-circular-ref": undefined,
|
||||
"x-displayName": "notString",
|
||||
"x-parentRefs": Array [],
|
||||
"x-parentRefs": [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 1`] = `
|
||||
Object {
|
||||
{
|
||||
"allOf": undefined,
|
||||
"description": undefined,
|
||||
"maxItems": 2,
|
||||
"properties": Object {
|
||||
"test": Object {
|
||||
"properties": {
|
||||
"test": {
|
||||
"allOf": undefined,
|
||||
"description": "The list of URL to a cute photos featuring pet",
|
||||
"enum": Array [
|
||||
"enum": [
|
||||
10,
|
||||
],
|
||||
"items": Object {
|
||||
"items": {
|
||||
"allOf": undefined,
|
||||
"description": undefined,
|
||||
"format": "url",
|
||||
|
@ -82,21 +82,21 @@ Object {
|
|||
"type": "string",
|
||||
"writeOnly": undefined,
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
"x-parentRefs": [],
|
||||
},
|
||||
"maxItems": 20,
|
||||
"minItems": 1,
|
||||
"readOnly": undefined,
|
||||
"title": undefined,
|
||||
"type": Array [
|
||||
"type": [
|
||||
"string",
|
||||
"integer",
|
||||
"null",
|
||||
],
|
||||
"writeOnly": undefined,
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
"x-refsStack": Array [
|
||||
"x-parentRefs": [],
|
||||
"x-refsStack": [
|
||||
"/oneOf/0",
|
||||
],
|
||||
},
|
||||
|
@ -106,30 +106,30 @@ Object {
|
|||
"type": "object",
|
||||
"writeOnly": undefined,
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
"x-parentRefs": [],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Models Schema schemaDefinition should resolve schema with conditional operators 2`] = `
|
||||
Object {
|
||||
{
|
||||
"allOf": undefined,
|
||||
"description": undefined,
|
||||
"maxItems": 20,
|
||||
"properties": Object {
|
||||
"test": Object {
|
||||
"properties": {
|
||||
"test": {
|
||||
"description": "The list of URL to a cute photos featuring pet",
|
||||
"items": Object {
|
||||
"items": {
|
||||
"format": "url",
|
||||
"type": "string",
|
||||
},
|
||||
"maxItems": 20,
|
||||
"minItems": 1,
|
||||
"type": Array [
|
||||
"type": [
|
||||
"string",
|
||||
"integer",
|
||||
"null",
|
||||
],
|
||||
"x-refsStack": Array [
|
||||
"x-refsStack": [
|
||||
"/oneOf/1",
|
||||
],
|
||||
},
|
||||
|
@ -139,6 +139,6 @@ Object {
|
|||
"type": "object",
|
||||
"writeOnly": undefined,
|
||||
"x-circular-ref": undefined,
|
||||
"x-parentRefs": Array [],
|
||||
"x-parentRefs": [],
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types';
|
||||
import { IS_BROWSER } from '../../utils/';
|
||||
import { l } from '../Labels';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
|
||||
|
@ -13,7 +14,10 @@ export class ApiInfoModel implements OpenAPIInfo {
|
|||
contact?: OpenAPIContact;
|
||||
license?: OpenAPILicense;
|
||||
|
||||
downloadLink?: string;
|
||||
downloadUrls: {
|
||||
title?: string;
|
||||
url?: string;
|
||||
}[];
|
||||
downloadFileName?: string;
|
||||
|
||||
constructor(
|
||||
|
@ -29,13 +33,28 @@ export class ApiInfoModel implements OpenAPIInfo {
|
|||
this.description = this.description.substring(0, firstHeadingLinePos);
|
||||
}
|
||||
|
||||
this.downloadLink = this.getDownloadLink();
|
||||
this.downloadUrls = this.getDownloadUrls();
|
||||
this.downloadFileName = this.getDownloadFileName();
|
||||
}
|
||||
private getDownloadUrls() {
|
||||
return (
|
||||
!this.options.downloadUrls
|
||||
? [
|
||||
{
|
||||
title: l('download'),
|
||||
url: this.getDownloadLink(this.options.downloadDefinitionUrl),
|
||||
},
|
||||
]
|
||||
: this.options.downloadUrls.map(({ title, url }) => ({
|
||||
title: title || l('download'),
|
||||
url: this.getDownloadLink(url),
|
||||
}))
|
||||
).filter(({ title, url }) => title && url);
|
||||
}
|
||||
|
||||
private getDownloadLink(): string | undefined {
|
||||
if (this.options.downloadDefinitionUrl) {
|
||||
return this.options.downloadDefinitionUrl;
|
||||
private getDownloadLink(url?: string): string | undefined {
|
||||
if (url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (this.parser.specUrl) {
|
||||
|
|
|
@ -14,7 +14,7 @@ export class MediaTypeModel {
|
|||
name: string;
|
||||
isRequestType: boolean;
|
||||
onlyRequiredInSamples: boolean;
|
||||
generatedPayloadSamplesMaxDepth: number;
|
||||
generatedSamplesMaxDepth: number;
|
||||
|
||||
/**
|
||||
* @param isRequestType needed to know if skipe RO/RW fields in objects
|
||||
|
@ -30,7 +30,7 @@ export class MediaTypeModel {
|
|||
this.isRequestType = isRequestType;
|
||||
this.schema = info.schema && new SchemaModel(parser, info.schema, '', options);
|
||||
this.onlyRequiredInSamples = options.onlyRequiredInSamples;
|
||||
this.generatedPayloadSamplesMaxDepth = options.generatedPayloadSamplesMaxDepth;
|
||||
this.generatedSamplesMaxDepth = options.generatedSamplesMaxDepth;
|
||||
if (info.examples !== undefined) {
|
||||
this.examples = mapValues(
|
||||
info.examples,
|
||||
|
@ -55,7 +55,7 @@ export class MediaTypeModel {
|
|||
skipReadOnly: this.isRequestType,
|
||||
skipWriteOnly: !this.isRequestType,
|
||||
skipNonRequired: this.isRequestType && this.onlyRequiredInSamples,
|
||||
maxSampleDepth: this.generatedPayloadSamplesMaxDepth,
|
||||
maxSampleDepth: this.generatedSamplesMaxDepth,
|
||||
};
|
||||
if (this.schema && this.schema.oneOf) {
|
||||
this.examples = {};
|
||||
|
|
|
@ -20,7 +20,12 @@ import { RequestBodyModel } from './RequestBody';
|
|||
import { ResponseModel } from './Response';
|
||||
import { SideNavStyleEnum } from '../types';
|
||||
|
||||
import type { OpenAPIExternalDocumentation, OpenAPIServer, OpenAPIXCodeSample } from '../../types';
|
||||
import type {
|
||||
OpenAPIExternalDocumentation,
|
||||
OpenAPIServer,
|
||||
OpenAPIXBadges,
|
||||
OpenAPIXCodeSample,
|
||||
} from '../../types';
|
||||
import type { OpenAPIParser } from '../OpenAPIParser';
|
||||
import type { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import type { MediaContentModel } from './MediaContent';
|
||||
|
@ -71,6 +76,7 @@ export class OperationModel implements IMenuItem {
|
|||
operationId?: string;
|
||||
operationHash?: string;
|
||||
httpVerb: string;
|
||||
badges: OpenAPIXBadges[];
|
||||
deprecated: boolean;
|
||||
path: string;
|
||||
servers: OpenAPIServer[];
|
||||
|
@ -112,6 +118,12 @@ export class OperationModel implements IMenuItem {
|
|||
: options.sideNavStyle === SideNavStyleEnum.PathOnly
|
||||
? this.path
|
||||
: this.name;
|
||||
this.badges =
|
||||
operationSpec['x-badges']?.map(({ name, color, position }) => ({
|
||||
name,
|
||||
color: color,
|
||||
position: position || 'after',
|
||||
})) || [];
|
||||
|
||||
if (this.isCallback) {
|
||||
// NOTE: Callbacks by default should not inherit the specification's global `security` definition.
|
||||
|
@ -195,6 +207,7 @@ export class OperationModel implements IMenuItem {
|
|||
|
||||
@memoize
|
||||
get codeSamples() {
|
||||
const { payloadSampleIdx, hideRequestPayloadSample } = this.options;
|
||||
let samples: Array<OpenAPIXCodeSample | XPayloadSample> =
|
||||
this.operationSpec['x-codeSamples'] || this.operationSpec['x-code-samples'] || [];
|
||||
|
||||
|
@ -204,8 +217,8 @@ export class OperationModel implements IMenuItem {
|
|||
}
|
||||
|
||||
const requestBodyContent = this.requestBody && this.requestBody.content;
|
||||
if (requestBodyContent && requestBodyContent.hasSample) {
|
||||
const insertInx = Math.min(samples.length, this.options.payloadSampleIdx);
|
||||
if (requestBodyContent && requestBodyContent.hasSample && !hideRequestPayloadSample) {
|
||||
const insertInx = Math.min(samples.length, payloadSampleIdx);
|
||||
|
||||
samples = [
|
||||
...samples.slice(0, insertInx),
|
||||
|
@ -234,7 +247,7 @@ export class OperationModel implements IMenuItem {
|
|||
if (this.options.sortPropsAlphabetically) {
|
||||
return sortByField(_parameters, 'name');
|
||||
}
|
||||
if (this.options.requiredPropsFirst) {
|
||||
if (this.options.sortRequiredPropsFirst) {
|
||||
return sortByRequired(_parameters);
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,7 @@ export class SchemaModel {
|
|||
rawSchema: OpenAPISchema;
|
||||
schema: MergedOpenAPISchema;
|
||||
extensions?: Record<string, any>;
|
||||
'x-enumDescriptions': { [name: string]: string };
|
||||
const: any;
|
||||
contentEncoding?: string;
|
||||
contentMediaType?: string;
|
||||
|
@ -122,6 +123,7 @@ export class SchemaModel {
|
|||
this.type = schema.type || detectType(schema);
|
||||
this.format = schema.format;
|
||||
this.enum = schema.enum || [];
|
||||
this['x-enumDescriptions'] = schema['x-enumDescriptions'];
|
||||
this.example = schema.example;
|
||||
this.examples = schema.examples;
|
||||
this.deprecated = !!schema.deprecated;
|
||||
|
@ -221,6 +223,7 @@ export class SchemaModel {
|
|||
}
|
||||
if (this.items?.isPrimitive) {
|
||||
this.enum = this.items.enum;
|
||||
this['x-enumDescriptions'] = this.items['x-enumDescriptions'];
|
||||
}
|
||||
if (isArray(this.type)) {
|
||||
const filteredType = this.type.filter(item => item !== 'array');
|
||||
|
@ -463,7 +466,7 @@ function buildFields(
|
|||
if (options.sortPropsAlphabetically) {
|
||||
fields = sortByField(fields, 'name');
|
||||
}
|
||||
if (options.requiredPropsFirst) {
|
||||
if (options.sortRequiredPropsFirst) {
|
||||
// if not sort alphabetically sort in the order from required keyword
|
||||
fields = sortByRequired(fields, !options.sortPropsAlphabetically ? schema.required : undefined);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import * as Enzyme from 'enzyme';
|
||||
import * as Adapter from '@wojtekmaj/enzyme-adapter-react-17';
|
||||
import Adapter from '@cfaester/enzyme-adapter-react-18';
|
||||
import { TextEncoder, TextDecoder } from 'util';
|
||||
|
||||
Object.assign(global, { TextDecoder, TextEncoder });
|
||||
|
||||
import 'raf/polyfill';
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from 'react';
|
||||
import { hydrate as hydrateComponent, render, unmountComponentAtNode } from 'react-dom';
|
||||
import { createRoot, hydrateRoot } from 'react-dom/client';
|
||||
import { configure } from 'mobx';
|
||||
|
||||
import { Redoc, RedocStandalone } from './components/';
|
||||
|
@ -59,7 +59,8 @@ export function init(
|
|||
spec = specOrSpecUrl;
|
||||
}
|
||||
|
||||
render(
|
||||
const root = createRoot(element!);
|
||||
root.render(
|
||||
React.createElement(
|
||||
RedocStandalone,
|
||||
{
|
||||
|
@ -70,13 +71,12 @@ export function init(
|
|||
},
|
||||
['Loading...'],
|
||||
),
|
||||
element,
|
||||
);
|
||||
}
|
||||
|
||||
export function destroy(element: Element | null = querySelector('redoc')): void {
|
||||
if (element) {
|
||||
unmountComponentAtNode(element);
|
||||
createRoot(element).unmount();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ export function hydrate(
|
|||
|
||||
setTimeout(() => {
|
||||
debugTime('Redoc hydrate');
|
||||
hydrateComponent(<Redoc store={store} />, element, callback);
|
||||
hydrateRoot(element!, <Redoc store={store} />, { onRecoverableError: callback });
|
||||
debugTimeEnd('Redoc hydrate');
|
||||
}, 0);
|
||||
}
|
||||
|
|
|
@ -70,6 +70,12 @@ export interface OpenAPIXCodeSample {
|
|||
source: string;
|
||||
}
|
||||
|
||||
export interface OpenAPIXBadges {
|
||||
name: string;
|
||||
color?: string;
|
||||
position?: 'before' | 'after';
|
||||
}
|
||||
|
||||
export interface OpenAPIOperation {
|
||||
tags?: string[];
|
||||
summary?: string;
|
||||
|
@ -85,6 +91,7 @@ export interface OpenAPIOperation {
|
|||
servers?: OpenAPIServer[];
|
||||
'x-codeSamples'?: OpenAPIXCodeSample[];
|
||||
'x-code-samples'?: OpenAPIXCodeSample[]; // deprecated
|
||||
'x-badges'?: OpenAPIXBadges[];
|
||||
}
|
||||
|
||||
export interface OpenAPIParameter {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -71,6 +71,30 @@ describe('Utils', () => {
|
|||
const obj2 = { a: ['C'], b: ['D'] };
|
||||
expect(mergeObjects({}, obj1, obj2)).toEqual({ a: ['C'], b: ['D'] });
|
||||
});
|
||||
test('should prevent prototype pollution', () => {
|
||||
const target = {};
|
||||
const source = JSON.parse('{"__proto__": {"polluted": "yes"}}');
|
||||
|
||||
mergeObjects(target, source);
|
||||
|
||||
expect(({} as any).polluted).toBeUndefined();
|
||||
});
|
||||
test('should merge objects correctly', () => {
|
||||
const target = { a: 1 };
|
||||
const source = { b: 2 };
|
||||
|
||||
const result = mergeObjects(target, source);
|
||||
|
||||
expect(result).toEqual({ a: 1, b: 2 });
|
||||
});
|
||||
test('should handle nested objects', () => {
|
||||
const target = { a: { b: 1 } };
|
||||
const source = { a: { c: 2 } };
|
||||
|
||||
const result = mergeObjects(target, source);
|
||||
|
||||
expect(result).toEqual({ a: { b: 1, c: 2 } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('titleize', () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ function throttle(func, wait) {
|
|||
return function () {
|
||||
const now = new Date().getTime();
|
||||
const remaining = wait - (now - previous);
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
context = this;
|
||||
// eslint-disable-next-line prefer-rest-params
|
||||
args = arguments;
|
||||
|
|
|
@ -81,7 +81,6 @@ export function appendToMdHeading(md: string, heading: string, content: string)
|
|||
}
|
||||
}
|
||||
|
||||
// credits https://stackoverflow.com/a/46973278/1749888
|
||||
export const mergeObjects = (target: any, ...sources: any[]): any => {
|
||||
if (!sources.length) {
|
||||
return target;
|
||||
|
@ -93,13 +92,15 @@ export const mergeObjects = (target: any, ...sources: any[]): any => {
|
|||
|
||||
if (isMergebleObject(target) && isMergebleObject(source)) {
|
||||
Object.keys(source).forEach((key: string) => {
|
||||
if (isMergebleObject(source[key])) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
if (Object.prototype.hasOwnProperty.call(source, key) && key !== '__proto__') {
|
||||
if (isMergebleObject(source[key])) {
|
||||
if (!target[key]) {
|
||||
target[key] = {};
|
||||
}
|
||||
mergeObjects(target[key], source[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
mergeObjects(target[key], source[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -393,7 +393,7 @@ export function getSerializedValue(field: FieldModel, example: any) {
|
|||
// decode for better readability in examples: see https://github.com/Redocly/redoc/issues/1138
|
||||
return decodeURIComponent(serializeParameterValue(field, example));
|
||||
} else {
|
||||
return String(example);
|
||||
return typeof example === 'object' ? example : String(example);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -654,12 +654,13 @@ export function isRedocExtension(key: string): boolean {
|
|||
'x-codeSamples': true,
|
||||
'x-displayName': true,
|
||||
'x-examples': true,
|
||||
'x-ignoredHeaderParameters': true,
|
||||
'x-enumDescriptions': true,
|
||||
'x-logo': true,
|
||||
'x-nullable': true,
|
||||
'x-servers': true,
|
||||
'x-tagGroups': true,
|
||||
'x-traitTag': true,
|
||||
'x-badges': true,
|
||||
'x-additionalPropertiesName': true,
|
||||
'x-explicitMappingOnly': true,
|
||||
};
|
||||
|
|
|
@ -1,138 +0,0 @@
|
|||
'use strict';
|
||||
const verifyNoBrowserErrors = require('./helpers').verifyNoBrowserErrors;
|
||||
const scrollToEl = require('./helpers').scrollToEl;
|
||||
const fixFFTest = require('./helpers').fixFFTest;
|
||||
const eachNth = require('./helpers').eachNth;
|
||||
const getInnerHtml = require('./helpers').getInnerHtml;
|
||||
|
||||
const URL = 'index.html';
|
||||
|
||||
function waitForInit() {
|
||||
const EC = protractor.ExpectedConditions;
|
||||
const $apiInfo = $('api-info');
|
||||
const $errorMessage = $('.redoc-error');
|
||||
browser.wait(EC.or(EC.visibilityOf($apiInfo), EC.visibilityOf($errorMessage)), 60000);
|
||||
}
|
||||
|
||||
function basicTests(swaggerUrl, title) {
|
||||
describe(`Basic suite for ${title}`, () => {
|
||||
let specUrl = URL;
|
||||
if (swaggerUrl) {
|
||||
specUrl += `?url=${encodeURIComponent(swaggerUrl)}`;
|
||||
}
|
||||
|
||||
beforeEach(done => {
|
||||
browser.get(specUrl);
|
||||
waitForInit();
|
||||
fixFFTest(done);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
verifyNoBrowserErrors();
|
||||
});
|
||||
|
||||
it('should init redoc without errors', done => {
|
||||
const $redoc = $('redoc');
|
||||
expect($redoc.isPresent()).toBe(true);
|
||||
setTimeout(() => {
|
||||
const $operations = $$('operation');
|
||||
expect($operations.count()).toBeGreaterThan(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
basicTests(null, 'Extended Petstore');
|
||||
|
||||
describe('Scroll sync', () => {
|
||||
const specUrl = URL;
|
||||
|
||||
beforeEach(done => {
|
||||
browser.get(specUrl);
|
||||
waitForInit();
|
||||
fixFFTest(done);
|
||||
});
|
||||
|
||||
it('should update active menu entries on page scroll forwards', () => {
|
||||
scrollToEl('[section="tag/store"]').then(() => {
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-1.active > .menu-item-header')).toContain(
|
||||
'store',
|
||||
);
|
||||
expect(getInnerHtml('.selected-tag')).toContain('store');
|
||||
});
|
||||
});
|
||||
|
||||
it('should update active menu entries on page scroll backwards', () => {
|
||||
scrollToEl('[operation-id="getPetById"]').then(() => {
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-1.active .menu-item-header')).toContain(
|
||||
'pet',
|
||||
);
|
||||
expect(getInnerHtml('.selected-tag')).toContain('pet');
|
||||
expect(getInnerHtml('.menu-item.menu-item-depth-2.active .menu-item-header')).toContain(
|
||||
'Find pet by ID',
|
||||
);
|
||||
expect(getInnerHtml('.selected-endpoint')).toContain('Find pet by ID');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Language tabs sync', () => {
|
||||
const specUrl = URL;
|
||||
|
||||
beforeEach(done => {
|
||||
browser.get(specUrl);
|
||||
waitForInit();
|
||||
fixFFTest(done);
|
||||
});
|
||||
|
||||
// skip as it fails for no reason on IE on sauce-labs
|
||||
// TODO: fixme
|
||||
xit('should sync language tabs', () => {
|
||||
const $item = $$('[operation-id="addPet"] tabs > ul > li').last();
|
||||
// check if correct item
|
||||
expect($item.getText()).toContain('PHP');
|
||||
const EC = protractor.ExpectedConditions;
|
||||
browser.wait(EC.elementToBeClickable($item), 5000);
|
||||
$item.click().then(() => {
|
||||
expect($('[operation-id="updatePet"] li.active').getText()).toContain('PHP');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (process.env.JOB === 'e2e-guru') {
|
||||
describe('APIs.guru specs test', () => {
|
||||
// global.apisGuruList was loaded in onPrepare method of protractor config
|
||||
let apisGuruList = global.apisGuruList;
|
||||
|
||||
// Remove certain APIs that are known to cause problems
|
||||
delete apisGuruList['motaword.com']; // invalid (see https://github.com/BigstickCarpet/swagger-parser/issues/26)
|
||||
delete apisGuruList['learnifier.com']; // allof object and no type
|
||||
delete apisGuruList['googleapis.com:mirror']; // bad urls in images
|
||||
delete apisGuruList['googleapis.com:discovery']; // non-string references
|
||||
delete apisGuruList['clarify.io']; // non-string references
|
||||
//delete apisGuruList['pushpay.com']; // https://github.com/Redocly/redoc/issues/30
|
||||
delete apisGuruList['bbci.co.uk']; // too big
|
||||
delete apisGuruList['bbc.com']; // too big
|
||||
delete apisGuruList['osisoft.com']; // too big
|
||||
delete apisGuruList['magento.com']; // too big
|
||||
|
||||
// run quick version of e2e test on all builds except releases
|
||||
if (process.env.TRAVIS && !process.env.TRAVIS_TAG) {
|
||||
console.log('Running on a short APIs guru list');
|
||||
apisGuruList = eachNth(apisGuruList, 20);
|
||||
} else {
|
||||
console.log('Running on full APIs guru list');
|
||||
}
|
||||
|
||||
for (const apiName of Object.keys(apisGuruList)) {
|
||||
const apiInfo = apisGuruList[apiName].versions[apisGuruList[apiName].preferred];
|
||||
let url = apiInfo.swaggerUrl;
|
||||
|
||||
// temporary hack due to this issue: https://github.com/substack/https-browserify/issues/6
|
||||
url = url.replace('https://', 'http://');
|
||||
url = url.replace('apis-guru.github.io/', 'apis-guru.github.io:80/');
|
||||
basicTests(url, `${apiName}:${apiInfo.info.version}\n${url}`);
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user