mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 14:14:56 +03:00
Added callbacks support
This commit is contained in:
parent
cfb6f0fde0
commit
f1eb03fd66
|
@ -44,22 +44,20 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const website =
|
const website =
|
||||||
(info.contact &&
|
(info.contact && info.contact.url && (
|
||||||
info.contact.url && (
|
<InfoSpan>
|
||||||
<InfoSpan>
|
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
||||||
URL: <a href={info.contact.url}>{info.contact.url}</a>
|
</InfoSpan>
|
||||||
</InfoSpan>
|
)) ||
|
||||||
)) ||
|
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const email =
|
const email =
|
||||||
(info.contact &&
|
(info.contact && info.contact.email && (
|
||||||
info.contact.email && (
|
<InfoSpan>
|
||||||
<InfoSpan>
|
{info.contact.name || 'E-mail'}:{' '}
|
||||||
{info.contact.name || 'E-mail'}:{' '}
|
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
||||||
<a href={'mailto:' + info.contact.email}>{info.contact.email}</a>
|
</InfoSpan>
|
||||||
</InfoSpan>
|
)) ||
|
||||||
)) ||
|
|
||||||
null;
|
null;
|
||||||
|
|
||||||
const terms =
|
const terms =
|
||||||
|
|
26
src/components/Callbacks/Callback.tsx
Normal file
26
src/components/Callbacks/Callback.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { observer } from 'mobx-react';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { CallbackModel } from '../../services/models';
|
||||||
|
import { CallbackDetailsWrap, StyledCallbackTitle } from '../Callbacks/styled.elements';
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class CallbackView extends React.Component<{ callback: CallbackModel }> {
|
||||||
|
toggle = () => {
|
||||||
|
this.props.callback.toggle();
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { name, expanded } = this.props.callback;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<StyledCallbackTitle onClick={this.toggle} name={name} opened={expanded} />
|
||||||
|
{expanded && (
|
||||||
|
<CallbackDetailsWrap>
|
||||||
|
<span>{name}</span>
|
||||||
|
</CallbackDetailsWrap>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
22
src/components/Callbacks/CallbackTitle.tsx
Normal file
22
src/components/Callbacks/CallbackTitle.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { ShelfIcon } from '../../common-elements';
|
||||||
|
|
||||||
|
export interface CallbackTitleProps {
|
||||||
|
name: string;
|
||||||
|
opened?: boolean;
|
||||||
|
className?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CallbackTitle extends React.PureComponent<CallbackTitleProps> {
|
||||||
|
render() {
|
||||||
|
const { name, opened, className, onClick } = this.props;
|
||||||
|
return (
|
||||||
|
<div className={className} onClick={onClick || undefined}>
|
||||||
|
<ShelfIcon size={'1.5em'} direction={opened ? 'up' : 'down'} float={'left'} />
|
||||||
|
<strong>{name} </strong>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
35
src/components/Callbacks/CallbacksList.tsx
Normal file
35
src/components/Callbacks/CallbacksList.tsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { CallbackModel } from '../../services/models';
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
import { CallbackView } from './Callback';
|
||||||
|
|
||||||
|
const CallbacksHeader = styled.h3`
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 0.2em 0;
|
||||||
|
margin: 3em 0 1.1em;
|
||||||
|
color: #253137;
|
||||||
|
font-weight: normal;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export interface CallbacksListProps {
|
||||||
|
callbacks: CallbackModel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CallbacksList extends React.PureComponent<CallbacksListProps> {
|
||||||
|
render() {
|
||||||
|
const { callbacks } = this.props;
|
||||||
|
|
||||||
|
if (!callbacks || callbacks.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<CallbacksHeader> Callbacks </CallbacksHeader>
|
||||||
|
{callbacks.map(callback => {
|
||||||
|
return <CallbackView key={callback.name} callback={callback} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
17
src/components/Callbacks/styled.elements.ts
Normal file
17
src/components/Callbacks/styled.elements.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
// import { transparentize } from 'polished';
|
||||||
|
|
||||||
|
import styled from '../../styled-components';
|
||||||
|
import { CallbackTitle } from './CallbackTitle';
|
||||||
|
|
||||||
|
export const StyledCallbackTitle = styled(CallbackTitle)`
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
cursor: pointer;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CallbackDetailsWrap = styled.div`
|
||||||
|
padding: 10px;
|
||||||
|
`;
|
|
@ -4,10 +4,19 @@ import * as React from 'react';
|
||||||
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
|
||||||
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
import { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
||||||
|
|
||||||
|
import { Operation } from '..';
|
||||||
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
import { H1, H2, MiddlePanel, Row, Section, ShareLink } from '../../common-elements';
|
||||||
import { ContentItemModel } from '../../services/MenuBuilder';
|
import { ContentItemModel } from '../../services';
|
||||||
import { GroupModel, OperationModel } from '../../services/models';
|
import { GroupModel, OperationModel } from '../../services/models';
|
||||||
import { Operation } from '../Operation/Operation';
|
import styled from '../../styled-components';
|
||||||
|
|
||||||
|
const CallbacksHeader = styled.h3`
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 0 40px;
|
||||||
|
margin: 3em 0 1.1em;
|
||||||
|
color: #253137;
|
||||||
|
font-weight: normal;
|
||||||
|
`;
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class ContentItems extends React.Component<{
|
export class ContentItems extends React.Component<{
|
||||||
|
@ -18,7 +27,22 @@ export class ContentItems extends React.Component<{
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return items.map(item => <ContentItem item={item} key={item.id} />);
|
|
||||||
|
return items.map(item => {
|
||||||
|
if (item.type === 'operation' && item.callbacks.length > 0) {
|
||||||
|
return (
|
||||||
|
<React.Fragment key={item.id}>
|
||||||
|
<ContentItem item={item} />
|
||||||
|
<CallbacksHeader>Callbacks:</CallbacksHeader>
|
||||||
|
{item.callbacks.map((callbackIndex, idx) => {
|
||||||
|
return <ContentItems key={idx} items={callbackIndex.operations} />;
|
||||||
|
})}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ContentItem key={item.id} item={item} />;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,21 +65,20 @@ export class Field extends React.Component<FieldProps> {
|
||||||
<FieldDetails {...this.props} />
|
<FieldDetails {...this.props} />
|
||||||
</PropertyDetailsCell>
|
</PropertyDetailsCell>
|
||||||
</tr>
|
</tr>
|
||||||
{field.expanded &&
|
{field.expanded && withSubSchema && (
|
||||||
withSubSchema && (
|
<tr key={field.name + 'inner'}>
|
||||||
<tr key={field.name + 'inner'}>
|
<PropertyCellWithInner colSpan={2}>
|
||||||
<PropertyCellWithInner colSpan={2}>
|
<InnerPropertiesWrap>
|
||||||
<InnerPropertiesWrap>
|
<Schema
|
||||||
<Schema
|
schema={field.schema}
|
||||||
schema={field.schema}
|
skipReadOnly={this.props.skipReadOnly}
|
||||||
skipReadOnly={this.props.skipReadOnly}
|
skipWriteOnly={this.props.skipWriteOnly}
|
||||||
skipWriteOnly={this.props.skipWriteOnly}
|
showTitle={this.props.showTitle}
|
||||||
showTitle={this.props.showTitle}
|
/>
|
||||||
/>
|
</InnerPropertiesWrap>
|
||||||
</InnerPropertiesWrap>
|
</PropertyCellWithInner>
|
||||||
</PropertyCellWithInner>
|
</tr>
|
||||||
</tr>
|
)}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,12 +28,11 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
|
||||||
code={code}
|
code={code}
|
||||||
opened={expanded}
|
opened={expanded}
|
||||||
/>
|
/>
|
||||||
{expanded &&
|
{expanded && !empty && (
|
||||||
!empty && (
|
<ResponseDetailsWrap>
|
||||||
<ResponseDetailsWrap>
|
<ResponseDetails response={this.props.response} />
|
||||||
<ResponseDetails response={this.props.response} />
|
</ResponseDetailsWrap>
|
||||||
</ResponseDetailsWrap>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,13 +93,12 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
|
||||||
<th> HTTP Authorization Scheme </th>
|
<th> HTTP Authorization Scheme </th>
|
||||||
<td> {scheme.http.scheme} </td>
|
<td> {scheme.http.scheme} </td>
|
||||||
</tr>,
|
</tr>,
|
||||||
scheme.http.scheme === 'bearer' &&
|
scheme.http.scheme === 'bearer' && scheme.http.bearerFormat && (
|
||||||
scheme.http.bearerFormat && (
|
<tr key="bearer">
|
||||||
<tr key="bearer">
|
<th> Bearer format </th>
|
||||||
<th> Bearer format </th>
|
<td> "{scheme.http.bearerFormat}" </td>
|
||||||
<td> "{scheme.http.bearerFormat}" </td>
|
</tr>
|
||||||
</tr>
|
),
|
||||||
),
|
|
||||||
]
|
]
|
||||||
) : scheme.openId ? (
|
) : scheme.openId ? (
|
||||||
<tr>
|
<tr>
|
||||||
|
|
|
@ -57,22 +57,19 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
||||||
{item.name}
|
{item.name}
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</MenuItemTitle>
|
</MenuItemTitle>
|
||||||
{(item.depth > 0 &&
|
{(item.depth > 0 && item.items.length > 0 && (
|
||||||
item.items.length > 0 && (
|
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
|
||||||
<ShelfIcon float={'right'} direction={item.expanded ? 'down' : 'right'} />
|
)) ||
|
||||||
)) ||
|
|
||||||
null}
|
null}
|
||||||
</MenuItemLabel>
|
</MenuItemLabel>
|
||||||
)}
|
)}
|
||||||
{!withoutChildren &&
|
{!withoutChildren && item.items && item.items.length > 0 && (
|
||||||
item.items &&
|
<MenuItems
|
||||||
item.items.length > 0 && (
|
expanded={item.expanded}
|
||||||
<MenuItems
|
items={item.items}
|
||||||
expanded={item.expanded}
|
onActivate={this.props.onActivate}
|
||||||
items={item.items}
|
/>
|
||||||
onActivate={this.props.onActivate}
|
)}
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</MenuItemLi>
|
</MenuItemLi>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
53
src/services/models/Callback.ts
Normal file
53
src/services/models/Callback.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import { action, observable } from 'mobx';
|
||||||
|
import { OpenAPICallback, Referenced } from '../../types';
|
||||||
|
import { isOperationName } from '../../utils';
|
||||||
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
|
import { OperationModel } from './Operation';
|
||||||
|
|
||||||
|
export class CallbackModel {
|
||||||
|
@observable
|
||||||
|
expanded: boolean;
|
||||||
|
name: string;
|
||||||
|
paths: Referenced<OpenAPICallback>;
|
||||||
|
operations: OperationModel[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
parser: OpenAPIParser,
|
||||||
|
name: string,
|
||||||
|
infoOrRef: Referenced<OpenAPICallback>,
|
||||||
|
options,
|
||||||
|
) {
|
||||||
|
this.name = name;
|
||||||
|
this.paths = parser.deref(infoOrRef);
|
||||||
|
parser.exitRef(infoOrRef);
|
||||||
|
|
||||||
|
for (const pathName of Object.keys(this.paths)) {
|
||||||
|
const path = this.paths[pathName];
|
||||||
|
const operations = Object.keys(path).filter(isOperationName);
|
||||||
|
for (const operationName of operations) {
|
||||||
|
const operationInfo = path[operationName];
|
||||||
|
|
||||||
|
const operation = new OperationModel(
|
||||||
|
parser,
|
||||||
|
{
|
||||||
|
...operationInfo,
|
||||||
|
pathName,
|
||||||
|
httpVerb: operationName,
|
||||||
|
pathParameters: path.parameters || [],
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.operations.push(operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(this.operations);
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
toggle() {
|
||||||
|
this.expanded = !this.expanded;
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,11 +49,7 @@ export class MediaTypeModel {
|
||||||
if (this.schema && this.schema.oneOf) {
|
if (this.schema && this.schema.oneOf) {
|
||||||
this.examples = {};
|
this.examples = {};
|
||||||
for (const subSchema of this.schema.oneOf) {
|
for (const subSchema of this.schema.oneOf) {
|
||||||
const sample = Sampler.sample(
|
const sample = Sampler.sample(subSchema.rawSchema, samplerOptions, parser.spec);
|
||||||
subSchema.rawSchema,
|
|
||||||
samplerOptions,
|
|
||||||
parser.spec,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this.schema.discriminatorProp && typeof sample === 'object' && sample) {
|
if (this.schema.discriminatorProp && typeof sample === 'object' && sample) {
|
||||||
sample[this.schema.discriminatorProp] = subSchema.title;
|
sample[this.schema.discriminatorProp] = subSchema.title;
|
||||||
|
@ -66,11 +62,7 @@ export class MediaTypeModel {
|
||||||
} else if (this.schema) {
|
} else if (this.schema) {
|
||||||
this.examples = {
|
this.examples = {
|
||||||
default: new ExampleModel(parser, {
|
default: new ExampleModel(parser, {
|
||||||
value: Sampler.sample(
|
value: Sampler.sample(info.schema, samplerOptions, parser.spec),
|
||||||
info.schema,
|
|
||||||
samplerOptions,
|
|
||||||
parser.spec,
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
||||||
import { OpenAPIParser } from '../OpenAPIParser';
|
import { OpenAPIParser } from '../OpenAPIParser';
|
||||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||||
|
import { CallbackModel } from './Callback';
|
||||||
import { FieldModel } from './Field';
|
import { FieldModel } from './Field';
|
||||||
import { RequestBodyModel } from './RequestBody';
|
import { RequestBodyModel } from './RequestBody';
|
||||||
import { ResponseModel } from './Response';
|
import { ResponseModel } from './Response';
|
||||||
|
@ -77,8 +78,8 @@ export class OperationModel implements IMenuItem {
|
||||||
operationSpec.operationId !== undefined
|
operationSpec.operationId !== undefined
|
||||||
? 'operation/' + operationSpec.operationId
|
? 'operation/' + operationSpec.operationId
|
||||||
: parent !== undefined
|
: parent !== undefined
|
||||||
? parent.id + this.pointer
|
? parent.id + this.pointer
|
||||||
: this.pointer;
|
: this.pointer;
|
||||||
|
|
||||||
this.name = getOperationSummary(operationSpec);
|
this.name = getOperationSummary(operationSpec);
|
||||||
this.description = operationSpec.description;
|
this.description = operationSpec.description;
|
||||||
|
@ -187,4 +188,16 @@ export class OperationModel implements IMenuItem {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@memoize
|
||||||
|
get callbacks() {
|
||||||
|
return Object.keys(this.operationSpec.callbacks || []).map(callbackName => {
|
||||||
|
return new CallbackModel(
|
||||||
|
this.parser,
|
||||||
|
callbackName,
|
||||||
|
this.operationSpec.callbacks![callbackName],
|
||||||
|
this.options,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './Callback';
|
||||||
export * from '../SpecStore';
|
export * from '../SpecStore';
|
||||||
export * from './Group.model';
|
export * from './Group.model';
|
||||||
export * from './Operation';
|
export * from './Operation';
|
||||||
|
|
Loading…
Reference in New Issue
Block a user