mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 06:04:56 +03:00
Add callback support
This commit is contained in:
parent
c05db38576
commit
b40455158a
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>
|
||||
);
|
||||
}
|
||||
}
|
15
src/components/Callbacks/styled.elements.ts
Normal file
15
src/components/Callbacks/styled.elements.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
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 { AdvancedMarkdown } from '../Markdown/AdvancedMarkdown';
|
||||
|
||||
import { Operation } from '..';
|
||||
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 { 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
|
||||
export class ContentItems extends React.Component<{
|
||||
|
@ -18,7 +27,21 @@ export class ContentItems extends React.Component<{
|
|||
if (items.length === 0) {
|
||||
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} />;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
55
src/services/models/Callback.ts
Normal file
55
src/services/models/Callback.ts
Normal file
|
@ -0,0 +1,55 @@
|
|||
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,
|
||||
true,
|
||||
this.name,
|
||||
);
|
||||
|
||||
this.operations.push(operation);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(this.operations);
|
||||
}
|
||||
|
||||
@action
|
||||
toggle() {
|
||||
this.expanded = !this.expanded;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import {
|
|||
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
|
||||
import { OpenAPIParser } from '../OpenAPIParser';
|
||||
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
|
||||
import { CallbackModel } from './Callback';
|
||||
import { FieldModel } from './Field';
|
||||
import { MediaContentModel } from './MediaContent';
|
||||
import { RequestBodyModel } from './RequestBody';
|
||||
|
@ -77,12 +78,15 @@ export class OperationModel implements IMenuItem {
|
|||
servers: OpenAPIServer[];
|
||||
security: SecurityRequirementModel[];
|
||||
extensions: Dict<any>;
|
||||
isCallback: boolean;
|
||||
|
||||
constructor(
|
||||
private parser: OpenAPIParser,
|
||||
private operationSpec: ExtendedOpenAPIOperation,
|
||||
parent: GroupModel | undefined,
|
||||
private options: RedocNormalizedOptions,
|
||||
isCallback: boolean = false,
|
||||
callbackEventName: string | undefined = undefined,
|
||||
) {
|
||||
this.pointer = JsonPointer.compile(['paths', operationSpec.pathName, operationSpec.httpVerb]);
|
||||
|
||||
|
@ -93,7 +97,6 @@ export class OperationModel implements IMenuItem {
|
|||
? parent.id + this.pointer
|
||||
: this.pointer;
|
||||
|
||||
this.name = getOperationSummary(operationSpec);
|
||||
this.description = operationSpec.description;
|
||||
this.parent = parent;
|
||||
this.externalDocs = operationSpec.externalDocs;
|
||||
|
@ -103,20 +106,50 @@ export class OperationModel implements IMenuItem {
|
|||
this.deprecated = !!operationSpec.deprecated;
|
||||
this.operationId = operationSpec.operationId;
|
||||
this.path = operationSpec.pathName;
|
||||
this.isCallback = isCallback;
|
||||
this.codeSamples = operationSpec['x-code-samples'] || [];
|
||||
|
||||
if (this.isCallback) {
|
||||
// NOTE: Use callback's event name as the view label, not the operationID.
|
||||
this.name = callbackEventName || getOperationSummary(operationSpec);
|
||||
// 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.
|
||||
this.security = (operationSpec.security || []).map(
|
||||
security => new SecurityRequirementModel(security, parser),
|
||||
);
|
||||
} else {
|
||||
this.name = getOperationSummary(operationSpec);
|
||||
this.security = (operationSpec.security || parser.spec.security || []).map(
|
||||
security => new SecurityRequirementModel(security, parser),
|
||||
);
|
||||
|
||||
const requestBodyContent = this.requestBody && this.requestBody.content;
|
||||
if (requestBodyContent && requestBodyContent.hasSample) {
|
||||
const insertInx = Math.min(this.codeSamples.length, options.payloadSampleIdx);
|
||||
|
||||
this.codeSamples = [
|
||||
...this.codeSamples.slice(0, insertInx),
|
||||
{
|
||||
lang: 'payload',
|
||||
label: 'Payload',
|
||||
source: '',
|
||||
requestBodyContent,
|
||||
},
|
||||
...this.codeSamples.slice(insertInx),
|
||||
];
|
||||
}
|
||||
|
||||
const pathInfo = parser.byRef<OpenAPIPath>(
|
||||
JsonPointer.compile(['paths', operationSpec.pathName]),
|
||||
);
|
||||
|
||||
// NOTE: Callbacks by default will inherit the specification's global `servers` definition.
|
||||
// In many cases, this may be undesirable. Override individually in the specification to remedy this.
|
||||
this.servers = normalizeServers(
|
||||
parser.specUrl,
|
||||
operationSpec.servers || (pathInfo && pathInfo.servers) || parser.spec.servers || [],
|
||||
);
|
||||
|
||||
this.security = (operationSpec.security || parser.spec.security || []).map(
|
||||
security => new SecurityRequirementModel(security, parser),
|
||||
);
|
||||
|
||||
if (options.showExtensions) {
|
||||
this.extensions = extractExtensions(operationSpec, options.showExtensions);
|
||||
}
|
||||
|
@ -224,4 +257,16 @@ export class OperationModel implements IMenuItem {
|
|||
);
|
||||
});
|
||||
}
|
||||
|
||||
@memoize
|
||||
get callbacks() {
|
||||
return Object.keys(this.operationSpec.callbacks || []).map(callbackEventName => {
|
||||
return new CallbackModel(
|
||||
this.parser,
|
||||
callbackEventName,
|
||||
this.operationSpec.callbacks![callbackEventName],
|
||||
this.options,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './Callback';
|
||||
export * from '../SpecStore';
|
||||
export * from './Group.model';
|
||||
export * from './Operation';
|
||||
|
|
Loading…
Reference in New Issue
Block a user