mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-03 03:40:23 +03:00
Merge tag 'v2.0.0-rc.59' into sections-at-the-end
This commit is contained in:
commit
d05cf42ec6
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -1,3 +1,16 @@
|
|||
# [2.0.0-rc.59](https://github.com/Redocly/redoc/compare/v2.0.0-rc.58...v2.0.0-rc.59) (2021-12-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix scroll in example dropdown ([#1803](https://github.com/Redocly/redoc/issues/1803)) ([bc2d9a7](https://github.com/Redocly/redoc/commit/bc2d9a7d9cd530274483fecd136db290a5b46ff7))
|
||||
* x-examples for request body param does not display [#1743](https://github.com/Redocly/redoc/issues/1743) ([#1826](https://github.com/Redocly/redoc/issues/1826)) ([aaa3b32](https://github.com/Redocly/redoc/commit/aaa3b3280c8422d450e8849ae02135dde199d6d5))
|
||||
|
||||
### Features
|
||||
|
||||
* add option sideNavStyle ([#1805](https://github.com/Redocly/redoc/pull/1805)) ([2e4663b](https://github.com/Redocly/redoc/commit/2e4663b3b7022f25d3dc808afbcb3b3ad9483c41))
|
||||
|
||||
|
||||
# [2.0.0-rc.58](https://github.com/Redocly/redoc/compare/v2.0.0-rc.57...v2.0.0-rc.58) (2021-11-29)
|
||||
|
||||
|
||||
|
@ -43,7 +56,7 @@
|
|||
* improve openapi 3.1 ([#1700](https://github.com/Redocly/redoc/issues/1700)) ([cd2d6f7](https://github.com/Redocly/redoc/commit/cd2d6f76e87c8385786a9c8e51c0d11c79d9707c))
|
||||
- show contentEncoding on fields
|
||||
- crash with OpenAPI 3.1 type as array of strings in requestBody
|
||||
- nullable label not shown
|
||||
- nullable label not shown
|
||||
* nullable object's fields were missing ([#1721](https://github.com/Redocly/redoc/issues/1721)) ([ddf297b](https://github.com/Redocly/redoc/commit/ddf297b11269ef515bd62771912a5609721d5e39))
|
||||
|
||||
|
||||
|
|
15
README.md
15
README.md
|
@ -8,14 +8,14 @@
|
|||
[](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [](https://www.npmjs.com/package/redoc) [](https://www.jsdelivr.com/package/npm/redoc) [](https://hub.docker.com/r/redocly/redoc/)
|
||||
</div>
|
||||
|
||||
**This is the README for the `2.x` version of Redoc (React-based).**
|
||||
**This is the README for the `2.x` version of Redoc (React-based).**
|
||||
**The README for the `1.x` version is on the [v1.x](https://github.com/Redocly/redoc/tree/v1.x) branch**
|
||||
|
||||
## About Redoc
|
||||
|
||||
Redoc is an open-source tool for generating documentation from OpenAPI (fka Swagger) definitions.
|
||||
Redoc is an open-source tool for generating documentation from OpenAPI (fka Swagger) definitions.
|
||||
|
||||
By default Redoc offers a three-panel, responsive layout:
|
||||
By default Redoc offers a three-panel, responsive layout:
|
||||
|
||||
- The left panel contains a search bar and navigation menu.
|
||||
- The central panel contains the documentation.
|
||||
|
@ -133,7 +133,7 @@ Additionally, all the 1.x releases are hosted on our GitHub Pages-based CDN **(d
|
|||
|
||||
Redocly's OpenAPI CLI is an open source command-line tool that you can use to lint
|
||||
your OpenAPI definition. Linting helps you to catch errors and inconsistencies in your
|
||||
OpenAPI definition before publishing.
|
||||
OpenAPI definition before publishing.
|
||||
|
||||
Refer to [Lint configuration](https://redoc.ly/docs/cli/guides/lint/) in the OpenAPI documentation for more information.
|
||||
|
||||
|
@ -173,10 +173,10 @@ replace the `spec-url` attribute with the url or local file address to your defi
|
|||
```
|
||||
|
||||
For step-by-step instructions for how to get started using Redoc
|
||||
to render your OpenAPI definition, refer to the
|
||||
to render your OpenAPI definition, refer to the
|
||||
[**Redoc quickstart guide**](https://redoc.ly/docs/redoc/quickstart/intro/).
|
||||
|
||||
See [**IE11 Support Notes**](docs/usage-with-ie11.md) for information on
|
||||
See [**IE11 Support Notes**](docs/usage-with-ie11.md) for information on
|
||||
IE support for Redoc.
|
||||
|
||||
## Redoc CLI
|
||||
|
@ -240,6 +240,9 @@ You can use all of the following options with the standalone version of the <red
|
|||
* `payloadSampleIdx` - if set, payload sample will be inserted at this index or last. Indexes start from 0.
|
||||
* `theme` - ReDoc theme. For details check [theme docs](#redoc-theme-object).
|
||||
* `untrustedSpec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!**
|
||||
* `sideNavStyle` - can be specified in various ways:
|
||||
* **summary-only**: displays a summary in the sidebar navigation item. (**default**)
|
||||
* **path-only**: displays a path in the sidebar navigation item.
|
||||
|
||||
### `<redoc>` theme object
|
||||
* `spacing`
|
||||
|
|
10
cli/npm-shrinkwrap.json
generated
10
cli/npm-shrinkwrap.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "redoc-cli",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "redoc-cli",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^3.5.1",
|
||||
|
@ -17,7 +17,7 @@
|
|||
"node-libs-browser": "^2.2.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"redoc": "2.0.0-rc.57",
|
||||
"redoc": "2.0.0-rc.58",
|
||||
"styled-components": "^5.3.0",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
|
@ -921,9 +921,7 @@
|
|||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"os": ["darwin"],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc-cli",
|
||||
"version": "0.13.0",
|
||||
"version": "0.13.1",
|
||||
"description": "ReDoc's Command Line Interface",
|
||||
"main": "index.js",
|
||||
"bin": "index.js",
|
||||
|
@ -19,7 +19,7 @@
|
|||
"node-libs-browser": "^2.2.1",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"redoc": "2.0.0-rc.57",
|
||||
"redoc": "2.0.0-rc.58",
|
||||
"styled-components": "^5.3.0",
|
||||
"yargs": "^17.0.1"
|
||||
},
|
||||
|
|
|
@ -33,14 +33,14 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
},
|
||||
|
||||
devServer: {
|
||||
contentBase: __dirname,
|
||||
watchContentBase: true,
|
||||
static: __dirname,
|
||||
port: 9090,
|
||||
disableHostCheck: true,
|
||||
stats: 'minimal',
|
||||
hot: true,
|
||||
historyApiFallback: true,
|
||||
},
|
||||
stats: {
|
||||
children: true,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.json'],
|
||||
fallback: {
|
||||
|
@ -72,7 +72,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
|
|||
{ test: [/\.eot$/, /\.gif$/, /\.woff$/, /\.svg$/, /\.ttf$/], use: 'null-loader' },
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [getBabelLoader({useBuiltIns: true, hot: true} )],
|
||||
use: [getBabelLoader({ useBuiltIns: true, hot: true })],
|
||||
exclude: {
|
||||
and: [/node_modules/],
|
||||
not: {
|
||||
|
|
7105
package-lock.json
generated
7105
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"version": "2.0.0-rc.58",
|
||||
"version": "2.0.0-rc.59",
|
||||
"description": "ReDoc",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -37,7 +37,7 @@
|
|||
"unit": "jest --coverage",
|
||||
"e2e": "cypress run",
|
||||
"e2e-ci": "cypress run --record",
|
||||
"bundlesize": "bundlesize",
|
||||
"bundlesize": "size-limit",
|
||||
"ts-check": "tsc --noEmit --skipLibCheck",
|
||||
"cy:open": "cypress open",
|
||||
"bundle:clean": "rimraf bundles",
|
||||
|
@ -75,6 +75,7 @@
|
|||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@cypress/webpack-preprocessor": "^5.9.0",
|
||||
"@hot-loader/react-dom": "^17.0.1",
|
||||
"@size-limit/preset-app": "^7.0.4",
|
||||
"@types/chai": "^4.2.18",
|
||||
"@types/dompurify": "^2.2.2",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
|
@ -83,9 +84,9 @@
|
|||
"@types/json-pointer": "^1.0.30",
|
||||
"@types/lodash": "^4.14.170",
|
||||
"@types/lunr": "^2.3.3",
|
||||
"@types/node": "^15.6.1",
|
||||
"@types/mark.js": "^8.11.5",
|
||||
"@types/marked": "^1.1.0",
|
||||
"@types/node": "^15.6.1",
|
||||
"@types/prismjs": "^1.16.5",
|
||||
"@types/prop-types": "^15.7.3",
|
||||
"@types/react": "^17.0.8",
|
||||
|
@ -102,7 +103,6 @@
|
|||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"beautify-benchmark": "^0.2.4",
|
||||
"bundlesize": "^0.18.1",
|
||||
"conventional-changelog-cli": "^2.0.34",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"core-js": "^3.13.1",
|
||||
|
@ -130,6 +130,7 @@
|
|||
"react-hot-loader": "^4.13.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"shelljs": "^0.8.4",
|
||||
"size-limit": "^7.0.4",
|
||||
"style-loader": "^2.0.0",
|
||||
"styled-components": "^5.3.0",
|
||||
"ts-jest": "^27.0.2",
|
||||
|
@ -140,7 +141,7 @@
|
|||
"url-polyfill": "^1.1.12",
|
||||
"webpack": "^5.38.1",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-dev-server": "^4.6.0",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"workerize-loader": "github:redocly/workerize-loader#webpack-5-dist"
|
||||
},
|
||||
|
@ -163,7 +164,6 @@
|
|||
"lunr": "^2.3.9",
|
||||
"mark.js": "^8.11.1",
|
||||
"marked": "^0.7.0",
|
||||
"memoize-one": "^5.2.1",
|
||||
"mobx-react": "^7.2.0",
|
||||
"openapi-sampler": "^1.0.1",
|
||||
"path-browserify": "^1.0.1",
|
||||
|
@ -177,10 +177,10 @@
|
|||
"swagger2openapi": "^7.0.6",
|
||||
"url-template": "^2.0.8"
|
||||
},
|
||||
"bundlesize": [
|
||||
"size-limit": [
|
||||
{
|
||||
"path": "./bundles/redoc.standalone.js",
|
||||
"maxSize": "350 kB"
|
||||
"limit": "350 kB"
|
||||
}
|
||||
],
|
||||
"jest": {
|
||||
|
|
|
@ -18,12 +18,6 @@ import { ResponsesList } from '../Responses/ResponsesList';
|
|||
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
|
||||
import { SecurityRequirements } from '../SecurityRequirement/SecurityRequirement';
|
||||
|
||||
const OperationRow = styled(Row)`
|
||||
backface-visibility: hidden;
|
||||
contain: content;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const Description = styled.div`
|
||||
margin-bottom: ${({ theme }) => theme.spacing.unit * 6}px;
|
||||
`;
|
||||
|
@ -43,7 +37,7 @@ export class Operation extends React.Component<OperationProps> {
|
|||
return (
|
||||
<OptionsContext.Consumer>
|
||||
{options => (
|
||||
<OperationRow>
|
||||
<Row>
|
||||
<MiddlePanel>
|
||||
<H2>
|
||||
<ShareLink to={operation.id} />
|
||||
|
@ -71,7 +65,7 @@ export class Operation extends React.Component<OperationProps> {
|
|||
<ResponseSamples operation={operation} />
|
||||
<CallbackSamples callbacks={operation.callbacks} />
|
||||
</DarkRightPanel>
|
||||
</OperationRow>
|
||||
</Row>
|
||||
)}
|
||||
</OptionsContext.Consumer>
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ 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';
|
||||
|
||||
export interface MenuItemProps {
|
||||
item: IMenuItem;
|
||||
|
@ -33,7 +34,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
|||
|
||||
scrollIntoViewIfActive() {
|
||||
if (this.props.item.active && this.ref.current) {
|
||||
this.ref.current.scrollIntoViewIfNeeded();
|
||||
scrollIntoViewIfNeeded(this.ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,8 +51,8 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
|||
<OperationMenuItemContent {...this.props} item={item as OperationModel} />
|
||||
) : (
|
||||
<MenuItemLabel depth={item.depth} active={item.active} type={item.type} ref={this.ref}>
|
||||
<MenuItemTitle title={item.name}>
|
||||
{item.name}
|
||||
<MenuItemTitle title={item.sidebarLabel}>
|
||||
{item.sidebarLabel}
|
||||
{this.props.children}
|
||||
</MenuItemTitle>
|
||||
{(item.depth > 0 && item.items.length > 0 && (
|
||||
|
@ -82,7 +83,7 @@ export class OperationMenuItemContent extends React.Component<OperationMenuItemC
|
|||
|
||||
componentDidUpdate() {
|
||||
if (this.props.item.active && this.ref.current) {
|
||||
this.ref.current.scrollIntoViewIfNeeded();
|
||||
scrollIntoViewIfNeeded(this.ref.current);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,7 +102,7 @@ export class OperationMenuItemContent extends React.Component<OperationMenuItemC
|
|||
<OperationBadge type={item.httpVerb}>{shortenHTTPVerb(item.httpVerb)}</OperationBadge>
|
||||
)}
|
||||
<MenuItemTitle width="calc(100% - 38px)">
|
||||
{item.name}
|
||||
{item.sidebarLabel}
|
||||
{this.props.children}
|
||||
</MenuItemTitle>
|
||||
</MenuItemLabel>
|
||||
|
|
|
@ -37,7 +37,6 @@ export class SideMenu extends React.Component<{ menu: MenuStore; className?: str
|
|||
if (item && item.active && this.context.menuToggle) {
|
||||
return item.expanded ? item.collapse() : item.expand();
|
||||
}
|
||||
|
||||
this.props.menu.activateAndScroll(item, true);
|
||||
setTimeout(() => {
|
||||
if (this._updateScroll) {
|
||||
|
|
|
@ -16,6 +16,7 @@ export interface IMenuItem {
|
|||
id: string;
|
||||
absoluteIdx?: number;
|
||||
name: string;
|
||||
sidebarLabel: string;
|
||||
description?: string;
|
||||
depth: number;
|
||||
active: boolean;
|
||||
|
|
|
@ -5,6 +5,11 @@ import { isNumeric, mergeObjects } from '../utils/helpers';
|
|||
import { LabelsConfigRaw, setRedocLabels } from './Labels';
|
||||
import { MDXComponentMeta } from './MarkdownRenderer';
|
||||
|
||||
export enum SideNavStyleEnum {
|
||||
SummaryOnly = 'summary-only',
|
||||
PathOnly = 'path-only',
|
||||
}
|
||||
|
||||
export interface RedocRawOptions {
|
||||
theme?: ThemeInterface;
|
||||
scrollYOffset?: number | string | (() => number);
|
||||
|
@ -22,6 +27,7 @@ export interface RedocRawOptions {
|
|||
disableSearch?: boolean | string;
|
||||
onlyRequiredInSamples?: boolean | string;
|
||||
showExtensions?: boolean | string | string[];
|
||||
sideNavStyle?: SideNavStyleEnum;
|
||||
hideSingleRequestSampleTab?: boolean | string;
|
||||
menuToggle?: boolean | string;
|
||||
jsonSampleExpandLevel?: number | string | 'all';
|
||||
|
@ -143,6 +149,22 @@ export class RedocNormalizedOptions {
|
|||
}
|
||||
}
|
||||
|
||||
static normalizeSideNavStyle(value: RedocRawOptions['sideNavStyle']): SideNavStyleEnum {
|
||||
const defaultValue = SideNavStyleEnum.SummaryOnly;
|
||||
if (typeof value !== 'string') {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case defaultValue:
|
||||
return value;
|
||||
case SideNavStyleEnum.PathOnly:
|
||||
return SideNavStyleEnum.PathOnly;
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
static normalizeSectionsAtTheEnd(value: RedocRawOptions['sectionsAtTheEnd']): string[] {
|
||||
if (typeof value === 'undefined' || typeof value !== 'string') {
|
||||
return new Array(0);
|
||||
|
@ -198,6 +220,7 @@ export class RedocNormalizedOptions {
|
|||
disableSearch: boolean;
|
||||
onlyRequiredInSamples: boolean;
|
||||
showExtensions: boolean | string[];
|
||||
sideNavStyle: SideNavStyleEnum;
|
||||
hideSingleRequestSampleTab: boolean;
|
||||
menuToggle: boolean;
|
||||
jsonSampleExpandLevel: number;
|
||||
|
@ -257,6 +280,7 @@ export class RedocNormalizedOptions {
|
|||
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.menuToggle = argValueToBoolean(raw.menuToggle, true);
|
||||
this.jsonSampleExpandLevel = RedocNormalizedOptions.normalizeJsonSampleExpandLevel(
|
||||
|
|
|
@ -14,6 +14,7 @@ export class GroupModel implements IMenuItem {
|
|||
id: string;
|
||||
absoluteIdx?: number;
|
||||
name: string;
|
||||
sidebarLabel: string;
|
||||
description?: string;
|
||||
type: MenuItemGroupType;
|
||||
|
||||
|
@ -44,6 +45,8 @@ export class GroupModel implements IMenuItem {
|
|||
this.name = tagOrGroup['x-displayName'] || tagOrGroup.name;
|
||||
this.level = (tagOrGroup as MarkdownHeading).level || 1;
|
||||
|
||||
this.sidebarLabel = this.name;
|
||||
|
||||
// remove sections from markdown, same as in ApiInfo
|
||||
this.description = tagOrGroup.description || '';
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import { FieldModel } from './Field';
|
|||
import { MediaContentModel } from './MediaContent';
|
||||
import { RequestBodyModel } from './RequestBody';
|
||||
import { ResponseModel } from './Response';
|
||||
import { SideNavStyleEnum } from '../RedocNormalizedOptions';
|
||||
|
||||
export interface XPayloadSample {
|
||||
lang: 'payload';
|
||||
|
@ -49,6 +50,7 @@ export class OperationModel implements IMenuItem {
|
|||
id: string;
|
||||
absoluteIdx?: number;
|
||||
name: string;
|
||||
sidebarLabel: string;
|
||||
description?: string;
|
||||
type = 'operation' as const;
|
||||
|
||||
|
@ -105,6 +107,8 @@ export class OperationModel implements IMenuItem {
|
|||
|
||||
this.name = getOperationSummary(operationSpec);
|
||||
|
||||
this.sidebarLabel = options.sideNavStyle === SideNavStyleEnum.PathOnly ? this.path : this.name;
|
||||
|
||||
if (this.isCallback) {
|
||||
// NOTE: Callbacks by default should not inherit the specification's global `security` definition.
|
||||
// Can be defined individually per-callback in the specification. Defaults to none.
|
||||
|
|
|
@ -3,6 +3,7 @@ import { OpenAPIRequestBody, Referenced } from '../../types';
|
|||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { MediaContentModel } from './MediaContent';
|
||||
import { getContentWithLegacyExamples } from '../../utils';
|
||||
|
||||
type RequestBodyProps = {
|
||||
parser: OpenAPIParser;
|
||||
|
@ -18,13 +19,15 @@ export class RequestBodyModel {
|
|||
|
||||
constructor(props: RequestBodyProps) {
|
||||
const { parser, infoOrRef, options, isEvent } = props;
|
||||
const isRequest = isEvent ? false : true;
|
||||
const isRequest = !isEvent;
|
||||
const info = parser.deref(infoOrRef);
|
||||
this.description = info.description || '';
|
||||
this.required = !!info.required;
|
||||
parser.exitRef(infoOrRef);
|
||||
if (info.content !== undefined) {
|
||||
this.content = new MediaContentModel(parser, info.content, isRequest, options);
|
||||
|
||||
const mediaContent = getContentWithLegacyExamples(info);
|
||||
if (mediaContent !== undefined) {
|
||||
this.content = new MediaContentModel(parser, mediaContent, isRequest, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -186,17 +186,20 @@ export interface OpenAPIRequestBody {
|
|||
description?: string;
|
||||
required?: boolean;
|
||||
content: { [mime: string]: OpenAPIMediaType };
|
||||
|
||||
'x-examples'?: { [mime: string]: { [name: string]: Referenced<OpenAPIExample> } };
|
||||
'x-example'?: { [mime: string]: any };
|
||||
}
|
||||
|
||||
export interface OpenAPIResponses {
|
||||
[code: string]: OpenAPIResponse;
|
||||
}
|
||||
|
||||
export interface OpenAPIResponse {
|
||||
description?: string;
|
||||
export interface OpenAPIResponse
|
||||
extends Pick<OpenAPIRequestBody, 'description' | 'x-examples' | 'x-example'> {
|
||||
headers?: { [name: string]: Referenced<OpenAPIHeader> };
|
||||
content?: { [mime: string]: OpenAPIMediaType };
|
||||
links?: { [name: string]: Referenced<OpenAPILink> };
|
||||
content?: { [mime: string]: OpenAPIMediaType };
|
||||
}
|
||||
|
||||
export interface OpenAPILink {
|
||||
|
|
|
@ -11,10 +11,16 @@ import {
|
|||
serializeParameterValue,
|
||||
sortByRequired,
|
||||
humanizeNumberRange,
|
||||
getContentWithLegacyExamples,
|
||||
} from '../';
|
||||
|
||||
import { FieldModel, OpenAPIParser, RedocNormalizedOptions } from '../../services';
|
||||
import { OpenAPIParameter, OpenAPIParameterLocation, OpenAPIParameterStyle } from '../../types';
|
||||
import {
|
||||
OpenAPIMediaType,
|
||||
OpenAPIParameter,
|
||||
OpenAPIParameterLocation,
|
||||
OpenAPIParameterStyle,
|
||||
} from '../../types';
|
||||
import { expandDefaultServerVariables } from '../openapi';
|
||||
|
||||
describe('Utils', () => {
|
||||
|
@ -1161,4 +1167,106 @@ describe('Utils', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenAPI getContentWithLegacyExamples', () => {
|
||||
it('should return undefined if no x-examples/x-example and no content', () => {
|
||||
expect(getContentWithLegacyExamples({})).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return unmodified object if no x-examples or x-example', () => {
|
||||
const info = {
|
||||
content: {
|
||||
'application/json': {},
|
||||
},
|
||||
};
|
||||
|
||||
const content = getContentWithLegacyExamples(info);
|
||||
expect(content).toStrictEqual(info.content);
|
||||
});
|
||||
|
||||
it('should create a new content object if no content and x-examples', () => {
|
||||
const info = {
|
||||
'x-examples': {
|
||||
'application/json': {
|
||||
name: {
|
||||
value: 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const content = getContentWithLegacyExamples(info);
|
||||
expect(content).toEqual({
|
||||
'application/json': {
|
||||
examples: {
|
||||
name: {
|
||||
value: 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a new content object if no content and x-example', () => {
|
||||
const info = {
|
||||
'x-example': {
|
||||
'application/json': 'test',
|
||||
},
|
||||
};
|
||||
|
||||
const content = getContentWithLegacyExamples(info);
|
||||
expect(content).toEqual({
|
||||
'application/json': { example: 'test' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should return copy of content with injected x-example', () => {
|
||||
const info = {
|
||||
'x-example': {
|
||||
'application/json': 'test',
|
||||
},
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
'text/plain': { schema: { type: 'string' } },
|
||||
},
|
||||
};
|
||||
|
||||
const content = getContentWithLegacyExamples(info) as { [mime: string]: OpenAPIMediaType };
|
||||
expect(content).toEqual({
|
||||
'application/json': { schema: { type: 'string' }, example: 'test' },
|
||||
'text/plain': { schema: { type: 'string' } },
|
||||
});
|
||||
expect(content).not.toStrictEqual(info.content);
|
||||
expect(content['application/json']).not.toStrictEqual(info.content['application/json']);
|
||||
expect(content['text/plain']).toStrictEqual(info.content['text/plain']);
|
||||
});
|
||||
|
||||
it('should prefer x-examples over x-example', () => {
|
||||
const info = {
|
||||
'x-example': {
|
||||
'application/json': 'test',
|
||||
},
|
||||
'x-examples': {
|
||||
'application/json': { name: { value: 'test' } },
|
||||
},
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { type: 'string' },
|
||||
},
|
||||
'text/plain': { schema: { type: 'string' } },
|
||||
},
|
||||
};
|
||||
|
||||
const content = getContentWithLegacyExamples(info) as { [mime: string]: OpenAPIMediaType };
|
||||
expect(content).toEqual({
|
||||
'application/json': { schema: { type: 'string' }, examples: { name: { value: 'test' } } },
|
||||
'text/plain': { schema: { type: 'string' } },
|
||||
});
|
||||
expect(content).not.toStrictEqual(info.content);
|
||||
expect(content['application/json']).not.toStrictEqual(info.content['application/json']);
|
||||
expect(content['text/plain']).toStrictEqual(info.content['text/plain']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,52 +24,54 @@ export function html2Str(html: string): string {
|
|||
.join(' ');
|
||||
}
|
||||
|
||||
// scrollIntoViewIfNeeded polyfill
|
||||
// Alternate scrollIntoViewIfNeeded implementation.
|
||||
// Used in all cases, since it seems Chrome's implementation is buggy
|
||||
// when "Experimental Web Platform Features" is enabled (at least of version 96).
|
||||
// See #1714, #1742
|
||||
|
||||
if (typeof Element !== 'undefined' && !(Element as any).prototype.scrollIntoViewIfNeeded) {
|
||||
(Element as any).prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
|
||||
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
|
||||
export function scrollIntoViewIfNeeded(el: HTMLElement, centerIfNeeded = true) {
|
||||
const parent = el.parentNode as HTMLElement | null;
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
const parentComputedStyle = window.getComputedStyle(parent, undefined);
|
||||
const parentBorderTopWidth = parseInt(
|
||||
parentComputedStyle.getPropertyValue('border-top-width'),
|
||||
10,
|
||||
);
|
||||
const parentBorderLeftWidth = parseInt(
|
||||
parentComputedStyle.getPropertyValue('border-left-width'),
|
||||
10,
|
||||
);
|
||||
const overTop = el.offsetTop - parent.offsetTop < parent.scrollTop;
|
||||
const overBottom =
|
||||
el.offsetTop - parent.offsetTop + el.clientHeight - parentBorderTopWidth >
|
||||
parent.scrollTop + parent.clientHeight;
|
||||
const overLeft = el.offsetLeft - parent.offsetLeft < parent.scrollLeft;
|
||||
const overRight =
|
||||
el.offsetLeft - parent.offsetLeft + el.clientWidth - parentBorderLeftWidth >
|
||||
parent.scrollLeft + parent.clientWidth;
|
||||
const alignWithTop = overTop && !overBottom;
|
||||
|
||||
const parent = this.parentNode;
|
||||
const parentComputedStyle = window.getComputedStyle(parent, undefined);
|
||||
const parentBorderTopWidth = parseInt(
|
||||
parentComputedStyle.getPropertyValue('border-top-width'),
|
||||
10,
|
||||
);
|
||||
const parentBorderLeftWidth = parseInt(
|
||||
parentComputedStyle.getPropertyValue('border-left-width'),
|
||||
10,
|
||||
);
|
||||
const overTop = this.offsetTop - parent.offsetTop < parent.scrollTop;
|
||||
const overBottom =
|
||||
this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth >
|
||||
parent.scrollTop + parent.clientHeight;
|
||||
const overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft;
|
||||
const overRight =
|
||||
this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth >
|
||||
parent.scrollLeft + parent.clientWidth;
|
||||
const alignWithTop = overTop && !overBottom;
|
||||
if ((overTop || overBottom) && centerIfNeeded) {
|
||||
parent.scrollTop =
|
||||
el.offsetTop -
|
||||
parent.offsetTop -
|
||||
parent.clientHeight / 2 -
|
||||
parentBorderTopWidth +
|
||||
el.clientHeight / 2;
|
||||
}
|
||||
|
||||
if ((overTop || overBottom) && centerIfNeeded) {
|
||||
parent.scrollTop =
|
||||
this.offsetTop -
|
||||
parent.offsetTop -
|
||||
parent.clientHeight / 2 -
|
||||
parentBorderTopWidth +
|
||||
this.clientHeight / 2;
|
||||
}
|
||||
if ((overLeft || overRight) && centerIfNeeded) {
|
||||
parent.scrollLeft =
|
||||
el.offsetLeft -
|
||||
parent.offsetLeft -
|
||||
parent.clientWidth / 2 -
|
||||
parentBorderLeftWidth +
|
||||
el.clientWidth / 2;
|
||||
}
|
||||
|
||||
if ((overLeft || overRight) && centerIfNeeded) {
|
||||
parent.scrollLeft =
|
||||
this.offsetLeft -
|
||||
parent.offsetLeft -
|
||||
parent.clientWidth / 2 -
|
||||
parentBorderLeftWidth +
|
||||
this.clientWidth / 2;
|
||||
}
|
||||
|
||||
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
|
||||
this.scrollIntoView(alignWithTop);
|
||||
}
|
||||
};
|
||||
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
|
||||
el.scrollIntoView(alignWithTop);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import {
|
|||
OpenAPIMediaType,
|
||||
OpenAPIParameter,
|
||||
OpenAPIParameterStyle,
|
||||
OpenAPIRequestBody,
|
||||
OpenAPIResponse,
|
||||
OpenAPISchema,
|
||||
OpenAPIServer,
|
||||
Referenced,
|
||||
|
@ -638,3 +640,33 @@ export function pluralizeType(displayType: string): string {
|
|||
.map(type => type.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/, '$1s$2'))
|
||||
.join(' or ');
|
||||
}
|
||||
|
||||
export function getContentWithLegacyExamples(
|
||||
info: OpenAPIRequestBody | OpenAPIResponse,
|
||||
): { [mime: string]: OpenAPIMediaType } | undefined {
|
||||
let mediaContent = info.content;
|
||||
const xExamples = info['x-examples']; // converted from OAS2 body param
|
||||
const xExample = info['x-example']; // converted from OAS2 body param
|
||||
|
||||
if (xExamples) {
|
||||
mediaContent = { ...mediaContent };
|
||||
for (const mime of Object.keys(xExamples)) {
|
||||
const examples = xExamples[mime];
|
||||
mediaContent[mime] = {
|
||||
...mediaContent[mime],
|
||||
examples,
|
||||
};
|
||||
}
|
||||
} else if (xExample) {
|
||||
mediaContent = { ...mediaContent };
|
||||
for (const mime of Object.keys(xExample)) {
|
||||
const example = xExample[mime];
|
||||
mediaContent[mime] = {
|
||||
...mediaContent[mime],
|
||||
example,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return mediaContent;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user