Merge tag 'v2.0.0-rc.59' into sections-at-the-end

This commit is contained in:
Roberto Fernández 2021-12-14 14:23:37 +01:00
commit d05cf42ec6
19 changed files with 1543 additions and 5938 deletions

View File

@ -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))

View File

@ -8,14 +8,14 @@
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc) [![Docker Build Status](https://img.shields.io/docker/build/redocly/redoc.svg)](https://hub.docker.com/r/redocly/redoc/)
</div>
**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`

View File

@ -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"
}

View File

@ -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"
},

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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": {

View File

@ -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>
);

View File

@ -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>

View File

@ -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) {

View File

@ -16,6 +16,7 @@ export interface IMenuItem {
id: string;
absoluteIdx?: number;
name: string;
sidebarLabel: string;
description?: string;
depth: number;
active: boolean;

View File

@ -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(

View File

@ -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 || '';

View File

@ -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.

View File

@ -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);
}
}
}

View File

@ -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 {

View File

@ -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']);
});
});
});

View File

@ -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);
}
}

View File

@ -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;
}