chore: minor refactor

This commit is contained in:
Roman Hotsiy 2018-06-28 20:13:18 +03:00
parent 06ef51c08c
commit c3da0a01d4
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
13 changed files with 299 additions and 173 deletions

View File

@ -0,0 +1,39 @@
import * as React from 'react';
import { MiddlePanel, Row } from '../../common-elements/';
import { AppStore } from '../../services/AppStore';
import { Markdown } from '../Markdown/Markdown';
import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes';
export interface ApiDescriptionProps {
store: AppStore;
}
const ALLOWED_COMPONENTS = {
'security-definitions': {
component: SecurityDefs,
propsSelector: _store => ({
securitySchemes: _store!.spec.securitySchemes,
}),
},
};
export class ApiDescription extends React.PureComponent<ApiDescriptionProps> {
render() {
const { store } = this.props;
const description = store.spec.info.description;
return (
<Row>
<MiddlePanel>
<Markdown
source={description || ''}
raw={false}
allowedComponents={ALLOWED_COMPONENTS}
store={store}
/>
</MiddlePanel>
</Row>
);
}
}

View File

@ -93,22 +93,6 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
</p>
)) ||
null}
<div>
<Markdown
source={info.description || ''}
raw={false}
components={{
'security-definitions': {
component: SecurityDefs,
propsSelector: _store => ({
securitySchemes: _store!.spec.securitySchemes,
}),
},
}}
store={store}
/>
</div>
</MiddlePanel>
</Row>
);

View File

@ -0,0 +1,2 @@
export { ApiDescription } from './ApiDescription';
export { ApiInfo } from './ApiInfo';

View File

@ -2,97 +2,107 @@ import * as React from 'react';
import styled, { ResolvedThemeInterface, StyledComponentClass } from '../../styled-components';
import * as DOMPurify from 'dompurify';
import { AppStore, MarkdownRenderer } from '../../services';
import { AppStore, MarkdownRenderer, MDXComponentMeta } from '../../services';
import { OptionsContext } from '../OptionsProvider';
import { markdownCss } from './styles';
import { StyledMarkdownBlock } from './styled.elements';
export interface MDComponent {
component: React.ComponentClass;
propsSelector: (store?: AppStore) => any;
attrs?: object;
}
const StyledMarkdownSpan = StyledMarkdownBlock.withComponent('span');
export interface MarkdownProps {
source: string;
export interface StylingMarkdownProps {
dense?: boolean;
inline?: boolean;
className?: string;
}
export interface BaseMarkdownProps extends StylingMarkdownProps {
raw?: boolean;
components?: Dict<MDComponent>;
sanitize?: boolean;
store?: AppStore;
}
class InternalMarkdown extends React.Component<MarkdownProps> {
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
function SanitizedMarkdownHTML(props: StylingMarkdownProps & { html: string }) {
const Wrap = props.inline ? StyledMarkdownSpan : StyledMarkdownBlock;
return (
<OptionsContext.Consumer>
{options => (
<Wrap
className={'redoc-markdown'}
dangerouslySetInnerHTML={{
__html: sanitize(options.untrustedSpec, props.html),
}}
/>
)}
</OptionsContext.Consumer>
);
}
export interface MarkdownProps extends BaseMarkdownProps {
allowedComponents?: Dict<MDXComponentMeta>;
source: string;
}
export class Markdown extends React.Component<MarkdownProps> {
constructor(props: MarkdownProps) {
super(props);
if (props.components && props.inline) {
if (props.allowedComponents && props.inline) {
throw new Error('Markdown Component: "inline" mode doesn\'t support "components"');
}
}
render() {
const { source, raw, className, components, inline, dense, store } = this.props;
const { source, raw, allowedComponents, store, inline, dense } = this.props;
if (components && !store) {
throw new Error('When using components with Markdwon in ReDoc, store prop must be provided');
if (allowedComponents && !store) {
throw new Error('When using componentes in markdown, store prop must be provided');
}
const sanitize = (untrustedSpec, html) => (untrustedSpec ? DOMPurify.sanitize(html) : html);
const renderer = new MarkdownRenderer();
const parts = components
? renderer.renderMdWithComponents(source, components, raw)
: [renderer.renderMd(source, raw)];
if (allowedComponents) {
return (
<AdvancedMarkdown
parts={renderer.renderMdWithComponents(source, allowedComponents, raw)}
{...this.props}
/>
);
} else {
return (
<SanitizedMarkdownHTML
html={renderer.renderMd(source, raw)}
inline={inline}
dense={dense}
/>
);
}
}
}
export interface AdvancedMarkdownProps extends BaseMarkdownProps {
parts: Array<string | MDXComponentMeta>;
}
export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
render() {
const { raw, inline, dense, store, parts } = this.props;
if (!parts.length) {
return null;
}
let appendClass = ' redoc-markdown';
if (dense) {
appendClass += ' -dense';
}
if (inline) {
appendClass += ' -inline';
}
return (
<OptionsContext.Consumer>
{options =>
inline ? (
<span
className={className + appendClass}
dangerouslySetInnerHTML={{
__html: sanitize(options.untrustedSpec, parts[0] as string),
}}
/>
) : (
<div className={className + appendClass}>
{parts.map(
(part, idx) =>
typeof part === 'string' ? (
<div
key={idx}
dangerouslySetInnerHTML={{ __html: sanitize(options.untrustedSpec, part) }}
/>
) : (
<part.component
key={idx}
{...{ ...part.attrs, ...part.propsSelector(store) }}
/>
),
)}
</div>
)
}
</OptionsContext.Consumer>
<>
{parts.map(
(part, idx) =>
typeof part === 'string' ? (
<SanitizedMarkdownHTML html={part} inline={inline} dense={dense} key={idx} />
) : (
<part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />
),
)}
</>
);
}
}
export const Markdown = styled(InternalMarkdown)`
${markdownCss};
`;

View File

@ -1,9 +1,15 @@
import * as React from 'react';
import { InterpolationFunction, Styles, ThemeProps } from 'styled-components';
import { headerCommonMixin, linkifyMixin } from '../../common-elements';
import { css, ResolvedThemeInterface, StyledComponentClass } from '../../styled-components';
import styled, {
css,
ResolvedThemeInterface,
StyledComponentClass,
withProps,
} from '../../styled-components';
export const markdownCss = css`
export const StyledMarkdownBlock = withProps<{ dense?: boolean; inline?: boolean }>(styled.div)`
font-family: ${props => props.theme.baseFont.family};
font-weight: ${props => props.theme.baseFont.weight};
@ -15,13 +21,17 @@ export const markdownCss = css`
}
}
&.-dense p {
${({ dense }) =>
dense &&
` p {
margin: 0;
}
}`}
&.-inline p {
${({ inline }) =>
inline &&
` p {
display: inline-block;
}
}`}
h1 {
${headerCommonMixin(1)};

View File

@ -4,7 +4,7 @@ import * as React from 'react';
import { ThemeProvider } from '../../styled-components';
import { AppStore } from '../../services';
import { ApiInfo } from '../ApiInfo/ApiInfo';
import { ApiDescription, ApiInfo } from '../ApiInfo/';
import { ApiLogo } from '../ApiLogo/ApiLogo';
import { ContentItems } from '../ContentItems/ContentItems';
import { OptionsProvider } from '../OptionsProvider';
@ -52,6 +52,7 @@ export class Redoc extends React.Component<RedocProps> {
</StickyResponsiveSidebar>
<ApiContentWrap className="api-content">
<ApiInfo store={store} />
<ApiDescription store={store} />
<ContentItems items={menu.items as any} />
</ApiContentWrap>
<BackgroundStub />

View File

@ -6,16 +6,6 @@ import { MediaContentModel, OperationModel } from '../../services/models';
import { Tab, TabList, TabPanel, Tabs } from '../../common-elements';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
export interface ResponseSampleProps {
content: MediaContentModel;
}
class ResponseSample extends React.Component<ResponseSampleProps, any> {
render() {
return <PayloadSamples content={this.props.content} />;
}
}
export interface ResponseSamplesProps {
operation: OperationModel;
}
@ -45,7 +35,7 @@ export class ResponseSamples extends React.Component<ResponseSamplesProps> {
</TabList>
{responses.map(response => (
<TabPanel key={response.code}>
<ResponseSample content={response.content!} />
<PayloadSamples content={response.content!} />;
</TabPanel>
))}
</Tabs>

View File

@ -2,14 +2,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { ResponseModel } from '../../services/models';
import { UnderlinedHeader } from '../../common-elements';
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema';
import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders';
import { ResponseDetails } from './ResponseDetails';
import { ResponseDetailsWrap, StyledResponseTitle } from './styled.elements';
@observer
@ -38,24 +31,10 @@ export class ResponseView extends React.Component<{ response: ResponseModel }> {
{expanded &&
!empty && (
<ResponseDetailsWrap>
{description && <Markdown source={description} />}
<ResponseHeaders headers={headers} />
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
{({ schema }) => {
return <Schema skipWriteOnly={true} key="schema" schema={schema} />;
}}
</MediaTypesSwitch>
<ResponseDetails response={this.props.response} />
</ResponseDetailsWrap>
)}
</div>
);
}
private renderDropdown = props => {
return (
<UnderlinedHeader key="header">
Response Schema: <DropdownOrLabel {...props} />
</UnderlinedHeader>
);
};
}

View File

@ -0,0 +1,36 @@
import * as React from 'react';
import { ResponseModel } from '../../services/models';
import { UnderlinedHeader } from '../../common-elements';
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema';
import { Markdown } from '../Markdown/Markdown';
import { ResponseHeaders } from './ResponseHeaders';
export class ResponseDetails extends React.PureComponent<{ response: ResponseModel }> {
render() {
const { description, headers, content } = this.props.response;
return (
<>
{description && <Markdown source={description} />}
<ResponseHeaders headers={headers} />
<MediaTypesSwitch content={content} renderDropdown={this.renderDropdown}>
{({ schema }) => {
return <Schema skipWriteOnly={true} key="schema" schema={schema} />;
}}
</MediaTypesSwitch>
</>
);
}
private renderDropdown = props => {
return (
<UnderlinedHeader key="header">
Response Schema: <DropdownOrLabel {...props} />
</UnderlinedHeader>
);
};
}

View File

@ -6,6 +6,7 @@ import { H2, ShareLink } from '../../common-elements';
import styled from '../../styled-components';
import { OpenAPISecurityScheme } from '../../types';
import { Markdown } from '../Markdown/Markdown';
import { StyledMarkdownBlock } from '../Markdown/styled.elements';
const AUTH_TYPES = {
oauth2: 'OAuth2',
@ -89,47 +90,49 @@ export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
{scheme.id}
</H2>
<Markdown source={scheme.description || ''} />
<AuthTable className="security-details">
<tbody>
<tr>
<th> Security scheme type: </th>
<td> {AUTH_TYPES[scheme.type] || scheme.type} </td>
</tr>
{scheme.apiKey ? (
<StyledMarkdownBlock>
<table className="security-details">
<tbody>
<tr>
<th> {scheme.apiKey.in} parameter name:</th>
<td> {scheme.apiKey.name} </td>
<th> Security scheme type: </th>
<td> {AUTH_TYPES[scheme.type] || scheme.type} </td>
</tr>
) : scheme.http ? (
[
<tr key="scheme">
<th> HTTP Authorization Scheme </th>
<td> {scheme.http.scheme} </td>
</tr>,
scheme.http.scheme === 'bearer' &&
scheme.http.bearerFormat && (
<tr key="bearer">
<th> Bearer format </th>
<td> "{scheme.http.bearerFormat}" </td>
</tr>
),
]
) : scheme.openId ? (
<tr>
<th> Connect URL </th>
<td>
<a target="_blank" href={scheme.openId.connectUrl}>
{scheme.openId.connectUrl}
</a>
</td>
</tr>
) : scheme.flows ? (
Object.keys(scheme.flows).map(type => (
<OAuthFlow key={type} type={type} flow={scheme.flows[type]} />
))
) : null}
</tbody>
</AuthTable>
{scheme.apiKey ? (
<tr>
<th> {scheme.apiKey.in} parameter name:</th>
<td> {scheme.apiKey.name} </td>
</tr>
) : scheme.http ? (
[
<tr key="scheme">
<th> HTTP Authorization Scheme </th>
<td> {scheme.http.scheme} </td>
</tr>,
scheme.http.scheme === 'bearer' &&
scheme.http.bearerFormat && (
<tr key="bearer">
<th> Bearer format </th>
<td> "{scheme.http.bearerFormat}" </td>
</tr>
),
]
) : scheme.openId ? (
<tr>
<th> Connect URL </th>
<td>
<a target="_blank" href={scheme.openId.connectUrl}>
{scheme.openId.connectUrl}
</a>
</td>
</tr>
) : scheme.flows ? (
Object.keys(scheme.flows).map(type => (
<OAuthFlow key={type} type={type} flow={scheme.flows[type]} />
))
) : null}
</tbody>
</table>
</StyledMarkdownBlock>
</div>
))}
</div>

View File

@ -1,6 +1,7 @@
export * from './RedocStandalone';
export * from './Redoc/Redoc';
export * from './ApiInfo/ApiInfo';
export * from './ApiInfo/ApiDescription';
export * from './ApiLogo/ApiLogo';
export * from './ContentItems/ContentItems';
export { ApiContentWrap, BackgroundStub, RedocWrap } from './Redoc/styled.elements';
@ -10,6 +11,18 @@ export * from './Operation/Operation';
export * from './Loading/Loading';
export * from './RedocStandalone';
export * from './JsonViewer';
export * from './Markdown/Markdown';
export { StyledMarkdownBlock } from './Markdown/styled.elements';
export * from './SecuritySchemes/SecuritySchemes';
export * from './Responses/Response';
export * from './Responses/ResponseDetails';
export * from './Responses/ResponseHeaders';
export * from './Responses/ResponsesList';
export * from './Responses/ResponseTitle';
export * from './ResponseSamples/ResponseSamples';
export * from './PayloadSamples/PayloadSamples';
export * from './MediaTypeSwitch/MediaTypesSwitch';
export * from './ErrorBoundary';
export * from './StoreProvider';

View File

@ -1,4 +1,5 @@
export * from './components';
export { MiddlePanel, Row, RightPanel } from './common-elements/';
export * from './services';
export * from './utils';

View File

@ -8,7 +8,7 @@
dependencies:
"@babel/highlight" "7.0.0-beta.47"
"@babel/code-frame@^7.0.0-beta.35":
"@babel/code-frame@^7.0.0-beta.35", "@babel/code-frame@^7.0.0-beta.39":
version "7.0.0-beta.51"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz#bd71d9b192af978df915829d39d4094456439a0c"
dependencies:
@ -568,7 +568,7 @@ ajv@^4.9.1:
co "^4.6.0"
json-stable-stringify "^1.0.1"
ajv@^5.0.1, ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0:
ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0, ajv@^5.5.2:
version "5.5.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
dependencies:
@ -1512,6 +1512,16 @@ beautify-benchmark@^0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/beautify-benchmark/-/beautify-benchmark-0.2.4.tgz#3151def14c1a2e0d07ff2e476861c7ed0e1ae39b"
better-ajv-errors@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/better-ajv-errors/-/better-ajv-errors-0.5.1.tgz#59dcd5e84b582ba2b5ddbb960bcfcbf439b369af"
dependencies:
"@babel/code-frame" "^7.0.0-beta.39"
chalk "^2.3.1"
json-to-ast "^2.0.2"
jsonpointer "^4.0.1"
leven "^2.1.0"
big.js@^3.1.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
@ -1913,7 +1923,7 @@ chalk@2.1.0:
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
chalk@2.4.1, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1:
chalk@2.4.1, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
dependencies:
@ -5473,6 +5483,10 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
json-to-ast@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/json-to-ast/-/json-to-ast-2.0.3.tgz#ef76a29207e7ab93cc058d862e6511a0e8e4792d"
json3@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1"
@ -5501,6 +5515,10 @@ jsonparse@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
jsonpointer@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9"
jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
@ -6253,7 +6271,7 @@ node-fetch@^1.0.1:
encoding "^0.1.11"
is-stream "^1.0.1"
node-fetch@^2.0.0:
node-fetch@^2.0.0, node-fetch@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5"
@ -6436,6 +6454,45 @@ nwsapi@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.0.4.tgz#dc79040a5f77b97716dc79565fc7fc3ef7d50570"
oas-kit-common@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/oas-kit-common/-/oas-kit-common-1.0.3.tgz#f9957227bd001d346b42f4907a3df917c535f77c"
oas-linter@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/oas-linter/-/oas-linter-1.0.3.tgz#a8bce67fe6b29c87a87b5cb39919cd660f4dbe77"
dependencies:
js-yaml "^3.11.0"
should "^13.2.1"
oas-resolver@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/oas-resolver/-/oas-resolver-1.0.6.tgz#359e86ee8312effc1211f805891a9d8cc8c92f03"
dependencies:
js-yaml "^3.11.0"
node-fetch "^2.1.1"
oas-kit-common "^1.0.3"
reftools "^1.0.0"
yargs "^11.0.0"
oas-schema-walker@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/oas-schema-walker/-/oas-schema-walker-1.0.3.tgz#f1ae73a671bef5fdcc71335d65e623fa4abb46ff"
oas-validator@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/oas-validator/-/oas-validator-1.1.3.tgz#49820f4aafd2bbea5d02f28bcea785de369af3b8"
dependencies:
ajv "^5.5.2"
better-ajv-errors "^0.5.1"
js-yaml "^3.11.0"
oas-kit-common "^1.0.3"
oas-linter "^1.0.3"
oas-resolver "^1.0.6"
oas-schema-walker "^1.0.3"
reftools "^1.0.0"
should "^13.2.1"
oauth-sign@~0.8.1, oauth-sign@~0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
@ -7610,9 +7667,9 @@ reduce-function-call@^1.0.1:
dependencies:
balanced-match "^0.4.2"
reftools@0.0.20:
version "0.0.20"
resolved "https://registry.yarnpkg.com/reftools/-/reftools-0.0.20.tgz#011e00736e51c631149a3a22b4c05b7383bdee8c"
reftools@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/reftools/-/reftools-1.0.0.tgz#3a5f979205c21ed702eae9f6ddcb4d88b4379a4a"
regenerate@^1.2.1:
version "1.4.0"
@ -8127,7 +8184,7 @@ should-util@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.0.tgz#c98cda374aa6b190df8ba87c9889c2b4db620063"
should@^13.0.1:
should@^13.2.1:
version "13.2.1"
resolved "https://registry.yarnpkg.com/should/-/should-13.2.1.tgz#84e6ebfbb145c79e0ae42307b25b3f62dcaf574e"
dependencies:
@ -8623,18 +8680,19 @@ svgo@^0.7.0:
sax "~1.2.1"
whet.extend "~0.9.9"
swagger2openapi@^2.11.0:
version "2.11.16"
resolved "https://registry.yarnpkg.com/swagger2openapi/-/swagger2openapi-2.11.16.tgz#7614ed2ebe0617656f8777bf67a33e8b0fba4e9b"
swagger2openapi@^3.1.2:
version "3.2.3"
resolved "https://registry.yarnpkg.com/swagger2openapi/-/swagger2openapi-3.2.3.tgz#89976ad3d3fda6a00b45cc9f7e3917b597eaa736"
dependencies:
ajv "^5.0.1"
call-me-maybe "^1.0.1"
co "^4.6.0"
js-yaml "^3.6.1"
node-fetch "^2.0.0"
node-readfiles "^0.2.0"
reftools "0.0.20"
should "^13.0.1"
oas-kit-common "^1.0.3"
oas-resolver "^1.0.6"
oas-schema-walker "^1.0.3"
oas-validator "^1.1.3"
reftools "^1.0.0"
yargs "^11.0.0"
symbol-observable@1.0.1: