chore: add tslint + fix lint issues

This commit is contained in:
Roman Hotsiy 2018-01-22 20:30:53 +02:00
parent 3819ad7e6a
commit dc18743df4
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
95 changed files with 721 additions and 611 deletions

View File

@ -5,8 +5,8 @@ import { AppContainer } from 'react-hot-loader';
import { Redoc, RedocProps } from '../../src/components/Redoc/Redoc'; import { Redoc, RedocProps } from '../../src/components/Redoc/Redoc';
import { AppStore } from '../../src/services/AppStore'; import { AppStore } from '../../src/services/AppStore';
import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec';
import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions'; import { RedocRawOptions } from '../../src/services/RedocNormalizedOptions';
import { loadAndBundleSpec } from '../../src/utils/loadAndBundleSpec';
const renderRoot = (Component: typeof Redoc, props: RedocProps) => const renderRoot = (Component: typeof Redoc, props: RedocProps) =>
render( render(
@ -29,7 +29,7 @@ const options: RedocRawOptions = {};
async function init() { async function init() {
const spec = await loadAndBundleSpec(specUrl); const spec = await loadAndBundleSpec(specUrl);
store = new AppStore(spec, specUrl, options); store = new AppStore(spec, specUrl, options);
renderRoot(Redoc, { store: store }); renderRoot(Redoc, { store });
} }
init(); init();
@ -44,7 +44,7 @@ if (module.hot) {
store = AppStore.fromJS(state); store = AppStore.fromJS(state);
} }
renderRoot(Redoc, { store: store }); renderRoot(Redoc, { store });
}; };
module.hot.accept(['../../src/components/Redoc/Redoc'], reload()); module.hot.accept(['../../src/components/Redoc/Redoc'], reload());

View File

@ -7,8 +7,7 @@
"start": "webpack-dev-server --hot", "start": "webpack-dev-server --hot",
"start:perf": "webpack-dev-server --env.prod --env.perf", "start:perf": "webpack-dev-server --env.prod --env.perf",
"start:prod": "webpack-dev-server --env.prod", "start:prod": "webpack-dev-server --env.prod",
"dev": "webpack-dashboard -- webpack-dev-server --hot", "test": "npm run lint && npm run unit && npm run e2e",
"test": "npm run unit && npm run e2e",
"unit": "jest", "unit": "jest",
"e2e": "npm run e2e:clean && npm run e2e:tsc && cypress run", "e2e": "npm run e2e:clean && npm run e2e:tsc && cypress run",
"e2e:tsc": "tsc -p tsconfig.e2e.json", "e2e:tsc": "tsc -p tsconfig.e2e.json",
@ -20,8 +19,8 @@
"bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone", "bundle": "npm run bundle:clean && npm run bundle:lib && npm run bundle:standalone",
"stats": "webpack -p --env.lib --env.standalone --env.prod --json --profile > stats.json", "stats": "webpack -p --env.lib --env.standalone --env.prod --json --profile > stats.json",
"prettier": "prettier --write \"src/**/*.{ts,tsx}\"", "prettier": "prettier --write \"src/**/*.{ts,tsx}\"",
"http-server": "http-server .", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1" "lint": "tslint --project tsconfig.json"
}, },
"author": "", "author": "",
"license": "MIT", "license": "MIT",
@ -51,7 +50,6 @@
"enzyme-adapter-react-16": "^1.0.4", "enzyme-adapter-react-16": "^1.0.4",
"enzyme-to-json": "^3.2.2", "enzyme-to-json": "^3.2.2",
"html-webpack-plugin": "^2.30.1", "html-webpack-plugin": "^2.30.1",
"http-server": "^0.10.0",
"jest": "^21.1.0", "jest": "^21.1.0",
"mobx-react-devtools": "^4.2.15", "mobx-react-devtools": "^4.2.15",
"prettier": "^1.5.3", "prettier": "^1.5.3",
@ -59,14 +57,14 @@
"puppeteer": "^0.10.2", "puppeteer": "^0.10.2",
"raf": "^3.4.0", "raf": "^3.4.0",
"react-dev-utils": "^4.1.0", "react-dev-utils": "^4.1.0",
"react-hot-loader": "3.0.0-beta.6",
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"source-map-loader": "^0.2.1", "source-map-loader": "^0.2.1",
"style-loader": "^0.18.2", "style-loader": "^0.18.2",
"ts-jest": "^21.0.1", "ts-jest": "^21.0.1",
"ts-node": "^3.3.0", "ts-node": "^3.3.0",
"tslint": "^5.7.0", "tslint": "^5.7.0",
"typescript": "^2.4.2", "tslint-react": "^3.4.0",
"typescript": "^2.6.2",
"webpack": "^3.10.0", "webpack": "^3.10.0",
"webpack-dev-server": "^2.9.5", "webpack-dev-server": "^2.9.5",
"webpack-node-externals": "^1.6.0" "webpack-node-externals": "^1.6.0"
@ -84,9 +82,11 @@
"mobx": "^3.3.0", "mobx": "^3.3.0",
"mobx-react": "^4.3.3", "mobx-react": "^4.3.3",
"openapi-sampler": "1.0.0-beta.8", "openapi-sampler": "1.0.0-beta.8",
"perfect-scrollbar": "^1.3.0",
"prismjs": "^1.8.1", "prismjs": "^1.8.1",
"prop-types": "^15.6.0", "prop-types": "^15.6.0",
"react-dropdown": "^1.3.0", "react-dropdown": "^1.3.0",
"react-hot-loader": "3.0.0-beta.6",
"react-perfect-scrollbar": "^0.2.2", "react-perfect-scrollbar": "^0.2.2",
"react-tabs": "^2.0.0", "react-tabs": "^2.0.0",
"remarkable": "^1.7.1", "remarkable": "^1.7.1",

View File

@ -1,15 +1,17 @@
import Dropdown from 'react-dropdown'; import Dropdown from 'react-dropdown';
import styled from '../styled-components'; import styled, { withProps } from '../styled-components';
import { withProps } from '../styled-components';
export type DropdownOption = { label: string; value: string }; export interface DropdownOption {
label: string;
value: string;
}
export type DropdownProps = { export interface DropdownProps {
options: DropdownOption[]; options: DropdownOption[];
value: DropdownOption; value: DropdownOption;
onChange: (val: DropdownOption) => void; onChange: (val: DropdownOption) => void;
}; }
export const StyledDropdown = withProps<DropdownProps>(styled(Dropdown))` export const StyledDropdown = withProps<DropdownProps>(styled(Dropdown))`
min-width: 100px; min-width: 100px;

View File

@ -1,6 +1,6 @@
import styled from '../styled-components'; import styled from '../styled-components';
import { deprecatedCss } from './mixins';
import { transparentizeHex } from '../utils/styled'; import { transparentizeHex } from '../utils/styled';
import { deprecatedCss } from './mixins';
export const PropertiesTableCaption = styled.caption` export const PropertiesTableCaption = styled.caption`
text-align: right; text-align: right;

View File

@ -1,6 +1,6 @@
import styled from 'styled-components'; import styled from 'styled-components';
import { PropertyNameCell } from './fields-layout';
import { transparentizeHex } from '../utils/styled'; import { transparentizeHex } from '../utils/styled';
import { PropertyNameCell } from './fields-layout';
export const ClickablePropertyNameCell = PropertyNameCell.extend` export const ClickablePropertyNameCell = PropertyNameCell.extend`
cursor: pointer; cursor: pointer;

View File

@ -1,9 +1,9 @@
import styled, { css } from '../styled-components'; import styled, { css } from '../styled-components';
const headerFontSize = { const headerFontSize = {
'1': '1.85714em', 1: '1.85714em',
'2': '1.57143em', 2: '1.57143em',
'3': '1.27em', 3: '1.27em',
}; };
export const headerCommonMixin = level => css` export const headerCommonMixin = level => css`

View File

@ -1,5 +1,6 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
// tslint:disable-next-line
export const linkifyMixin = className => css` export const linkifyMixin = className => css`
${className} { ${className} {
cursor: pointer; cursor: pointer;

View File

@ -6,7 +6,9 @@ export const deprecatedCss = css`
`; `;
export const hoverColor = color => { export const hoverColor = color => {
if (!color) return ''; if (!color) {
return '';
}
return css` return css`
&:hover { &:hover {
color: ${color}; color: ${color};

View File

@ -1,5 +1,5 @@
import psStyles from 'perfect-scrollbar/css/perfect-scrollbar.css';
import styled, { injectGlobal } from '../styled-components'; import styled, { injectGlobal } from '../styled-components';
import psStyles from 'perfect-scrollbar/dist/css/perfect-scrollbar.css';
import PerfectScrollbarOriginal from 'react-perfect-scrollbar'; import PerfectScrollbarOriginal from 'react-perfect-scrollbar';

View File

@ -1,5 +1,4 @@
import styled from '../styled-components'; import styled, { withProps } from '../styled-components';
import { withProps } from '../styled-components';
export const OneOfList = styled.ul` export const OneOfList = styled.ul`
margin: 0; margin: 0;

View File

@ -8,7 +8,7 @@ const directionMap = {
down: '0', down: '0',
}; };
class _ShelfIcon extends React.PureComponent<{ class IntShelfIcon extends React.PureComponent<{
className?: string; className?: string;
float?: 'left' | 'right'; float?: 'left' | 'right';
size?: string; size?: string;
@ -33,7 +33,7 @@ class _ShelfIcon extends React.PureComponent<{
} }
} }
export const ShelfIcon = styled(_ShelfIcon)` export const ShelfIcon = styled(IntShelfIcon)`
height: ${props => props.size || '18px'}; height: ${props => props.size || '18px'};
width: ${props => props.size || '18px'}; width: ${props => props.size || '18px'};
vertical-align: middle; vertical-align: middle;

View File

@ -1,5 +1,5 @@
import styled from '../styled-components';
import { Tabs as ReactTabs } from 'react-tabs'; import { Tabs as ReactTabs } from 'react-tabs';
import styled from '../styled-components';
export { Tab, TabList, TabPanel } from 'react-tabs'; export { Tab, TabList, TabPanel } from 'react-tabs';

View File

@ -1,18 +1,18 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { AppStore } from '../../services/AppStore'; import { AppStore } from '../../services/AppStore';
import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes'; import { DarkRightPanel, MiddlePanel, Row } from '../../common-elements/';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { MiddlePanel, DarkRightPanel, Row } from '../../common-elements/'; import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes';
import { import {
ApiHeader, ApiHeader,
DownloadButton, DownloadButton,
InfoSpan, InfoSpan,
InfoSpanBoxWrap,
InfoSpanBox, InfoSpanBox,
InfoSpanBoxWrap,
} from './styled.elements'; } from './styled.elements';
interface ApiInfoProps { interface ApiInfoProps {
@ -101,8 +101,8 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
components={{ components={{
'security-definitions': { 'security-definitions': {
component: SecurityDefs, component: SecurityDefs,
propsSelector: store => ({ propsSelector: _store => ({
securitySchemes: store!.spec.securitySchemes, securitySchemes: _store!.spec.securitySchemes,
}), }),
}, },
}} }}

View File

@ -1,6 +1,6 @@
import styled from '../../styled-components'; import styled from '../../styled-components';
import { MiddlePanel, H1 } from '../../common-elements'; import { H1, MiddlePanel } from '../../common-elements';
const delimiterWidth = 15; const delimiterWidth = 15;

View File

@ -1,6 +1,6 @@
import { OpenAPIInfo } from '../../types';
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { OpenAPIInfo } from '../../types';
import { LogoImgEl, LogoWrap } from './styled.elements'; import { LogoImgEl, LogoWrap } from './styled.elements';
const LinkWrap = url => Component => <a href={url}>{Component}</a>; const LinkWrap = url => Component => <a href={url}>{Component}</a>;
@ -10,7 +10,9 @@ export class ApiLogo extends React.Component<{ info: OpenAPIInfo }> {
render() { render() {
const { info } = this.props; const { info } = this.props;
const logoInfo = info['x-logo']; const logoInfo = info['x-logo'];
if (!logoInfo || !logoInfo.url) return null; if (!logoInfo || !logoInfo.url) {
return null;
}
const logo = ( const logo = (
<LogoImgEl src={logoInfo.url} style={{ backgroundColor: logoInfo.backgroundColor }} /> <LogoImgEl src={logoInfo.url} style={{ backgroundColor: logoInfo.backgroundColor }} />

View File

@ -1,13 +1,13 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { SECTION_ATTR } from '../../services/MenuStore'; import { SECTION_ATTR } from '../../services/MenuStore';
import { Markdown } from '../Markdown/Markdown'; import { Markdown } from '../Markdown/Markdown';
import { DarkRightPanel, H1, MiddlePanel, ShareLink, Row } from '../../common-elements'; import { DarkRightPanel, H1, MiddlePanel, Row, ShareLink } from '../../common-elements';
import { Operation } from '../Operation/Operation';
import { ContentItemModel } from '../../services/MenuBuilder'; import { ContentItemModel } from '../../services/MenuBuilder';
import { OperationModel } from '../../services/models'; import { OperationModel } from '../../services/models';
import { Operation } from '../Operation/Operation';
@observer @observer
export class ContentItems extends React.Component<{ export class ContentItems extends React.Component<{
@ -15,14 +15,16 @@ export class ContentItems extends React.Component<{
}> { }> {
render() { render() {
const items = this.props.items; const items = this.props.items;
if (items.length === 0) return null; if (items.length === 0) {
return null;
}
return items.map(item => <ContentItem item={item} key={item.id} />); return items.map(item => <ContentItem item={item} key={item.id} />);
} }
} }
type ContentItemProps = { interface ContentItemProps {
item: ContentItemModel; item: ContentItemModel;
}; }
@observer @observer
export class ContentItem extends React.Component<ContentItemProps> { export class ContentItem extends React.Component<ContentItemProps> {

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { MimeLabel, SimpleDropdown, DropdownProps } from '../../common-elements/dropdown'; import { DropdownProps, MimeLabel, SimpleDropdown } from '../../common-elements/dropdown';
export interface DropdownOrLabelProps extends DropdownProps { export interface DropdownOrLabelProps extends DropdownProps {
Label?: React.ComponentClass; Label?: React.ComponentClass;

View File

@ -1,16 +1,16 @@
import { ComponentWithOptions } from '../OptionsProvider';
import * as React from 'react'; import * as React from 'react';
import { OperationModel } from '../../services';
import { ShelfIcon } from '../../common-elements'; import { ShelfIcon } from '../../common-elements';
import { OperationModel } from '../../services';
import { ComponentWithOptions } from '../OptionsProvider';
import { SelectOnClick } from '../SelectOnClick/SelectOnClick'; import { SelectOnClick } from '../SelectOnClick/SelectOnClick';
import { import {
OperationEndpointWrap,
EndpointInfo, EndpointInfo,
HttpVerb, HttpVerb,
OperationEndpointWrap,
ServerItem,
ServerRelativeURL, ServerRelativeURL,
ServersOverlay, ServersOverlay,
ServerItem,
ServerUrl, ServerUrl,
} from './styled.elements'; } from './styled.elements';

View File

@ -1,5 +1,4 @@
import * as React from 'react'; import * as React from 'react';
import { Children } from 'react';
import styled from '../styled-components'; import styled from '../styled-components';
const ErrorWrapper = styled.div` const ErrorWrapper = styled.div`
@ -14,7 +13,7 @@ export class ErrorBoundary extends React.Component<{}, { error?: Error }> {
} }
componentDidCatch(error) { componentDidCatch(error) {
this.setState({ error: error }); this.setState({ error });
return false; return false;
} }
@ -35,6 +34,6 @@ export class ErrorBoundary extends React.Component<{}, { error?: Error }> {
</ErrorWrapper> </ErrorWrapper>
); );
} }
return Children.only(this.props.children); return React.Children.only(this.props.children);
} }
} }

View File

@ -9,7 +9,9 @@ export interface EnumValuesProps {
export class EnumValues extends React.PureComponent<EnumValuesProps> { export class EnumValues extends React.PureComponent<EnumValuesProps> {
render() { render() {
const { values, type } = this.props; const { values, type } = this.props;
if (!values.length) return null; if (!values.length) {
return null;
}
return ( return (
<div> <div>

View File

@ -1,5 +1,5 @@
import { FieldDetails } from './FieldDetails';
import * as React from 'react'; import * as React from 'react';
import { FieldDetails } from './FieldDetails';
import { ClickablePropertyNameCell, RequiredLabel } from '../../common-elements/fields'; import { ClickablePropertyNameCell, RequiredLabel } from '../../common-elements/fields';

View File

@ -7,7 +7,9 @@ export interface ConstraintsViewProps {
export class ConstraintsView extends React.PureComponent<ConstraintsViewProps> { export class ConstraintsView extends React.PureComponent<ConstraintsViewProps> {
render() { render() {
if (this.props.constraints.length === 0) return null; if (this.props.constraints.length === 0) {
return null;
}
return ( return (
<span> <span>
{' '} {' '}

View File

@ -1,5 +1,5 @@
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
import * as React from 'react'; import * as React from 'react';
import { ExampleValue, FieldLabel } from '../../common-elements/fields';
export interface FieldDetailProps { export interface FieldDetailProps {
value?: any; value?: any;
@ -8,7 +8,9 @@ export interface FieldDetailProps {
export class FieldDetail extends React.PureComponent<FieldDetailProps> { export class FieldDetail extends React.PureComponent<FieldDetailProps> {
render() { render() {
if (this.props.value === undefined) return null; if (this.props.value === undefined) {
return null;
}
return ( return (
<div> <div>
<FieldLabel> {this.props.label} </FieldLabel>{' '} <FieldLabel> {this.props.label} </FieldLabel>{' '}

View File

@ -1,19 +1,19 @@
import * as React from 'react'; import * as React from 'react';
import { FieldProps } from './Field';
import { Markdown } from '../Markdown/Markdown';
import { EnumValues } from './EnumValues';
import { FieldDetail } from './FieldDetail';
import { ConstraintsView } from './FieldContstraints';
import { import {
RecursiveLabel,
NullableLabel, NullableLabel,
PatternLabel, PatternLabel,
RecursiveLabel,
TypeFormat, TypeFormat,
TypeName, TypeName,
TypeTitle,
TypePrefix, TypePrefix,
TypeTitle,
} from '../../common-elements/fields'; } from '../../common-elements/fields';
import { Markdown } from '../Markdown/Markdown';
import { EnumValues } from './EnumValues';
import { FieldProps } from './Field';
import { ConstraintsView } from './FieldContstraints';
import { FieldDetail } from './FieldDetail';
import { Badge } from '../../common-elements/'; import { Badge } from '../../common-elements/';

View File

@ -23,8 +23,8 @@ class Json extends React.PureComponent<JsonProps> {
} }
clickListener = (event: MouseEvent) => { clickListener = (event: MouseEvent) => {
var collapsed, let collapsed;
target = event.target as HTMLElement; const target = event.target as HTMLElement;
if (target.className === 'collapser') { if (target.className === 'collapser') {
collapsed = target.parentElement!.getElementsByClassName('collapsible')[0]; collapsed = target.parentElement!.getElementsByClassName('collapsible')[0];
if (collapsed.parentElement.classList.contains('collapsed')) { if (collapsed.parentElement.classList.contains('collapsed')) {

View File

@ -1,17 +1,17 @@
import * as React from 'react'; import * as React from 'react';
import styled from '../../styled-components'; import styled from '../../styled-components';
import * as DOMPurify from 'dompurify';
import { AppStore, MarkdownRenderer } from '../../services'; import { AppStore, MarkdownRenderer } from '../../services';
import { ComponentWithOptions } from '../OptionsProvider'; import { ComponentWithOptions } from '../OptionsProvider';
import * as DOMPurify from 'dompurify';
import { markdownCss } from './styles'; import { markdownCss } from './styles';
export type MDComponent = { export interface MDComponent {
component: React.ComponentClass; component: React.ComponentClass;
propsSelector: (store?: AppStore) => any; propsSelector: (store?: AppStore) => any;
attrs?: object; attrs?: object;
}; }
export interface MarkdownProps { export interface MarkdownProps {
source: string; source: string;
@ -29,7 +29,7 @@ class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
super(props); super(props);
if (props.components && props.inline) { if (props.components && props.inline) {
throw new Error(`Markdown Component: "inline" mode doesn't support "components"`); throw new Error('Markdown Component: "inline" mode doesn\'t support "components"');
} }
} }
@ -40,24 +40,27 @@ class InternalMarkdown extends ComponentWithOptions<MarkdownProps> {
throw new Error('When using components with Markdwon in ReDoc, store prop must be provided'); throw new Error('When using components with Markdwon in ReDoc, store prop must be provided');
} }
let sanitize: (string) => string; const sanitize =
this.props.sanitize || this.options.untrustedSpec
if (this.props.sanitize || this.options.untrustedSpec) { ? (html: string) => DOMPurify.sanitize(html)
sanitize = html => DOMPurify.sanitize(html); : (html: string) => html;
} else {
sanitize = html => html;
}
const renderer = new MarkdownRenderer(); const renderer = new MarkdownRenderer();
const parts = components const parts = components
? renderer.renderMdWithComponents(source, components, raw) ? renderer.renderMdWithComponents(source, components, raw)
: [renderer.renderMd(source, raw)]; : [renderer.renderMd(source, raw)];
if (!parts.length) return null; if (!parts.length) {
return null;
}
let appendClass = ' redoc-markdown'; let appendClass = ' redoc-markdown';
if (dense) appendClass += ' -dense'; if (dense) {
if (inline) appendClass += ' -inline'; appendClass += ' -dense';
}
if (inline) {
appendClass += ' -inline';
}
if (inline) { if (inline) {
return ( return (

View File

@ -1,5 +1,5 @@
import { css } from '../../styled-components';
import { headerCommonMixin, linkifyMixin } from '../../common-elements'; import { headerCommonMixin, linkifyMixin } from '../../common-elements';
import { css } from '../../styled-components';
export const markdownCss = css` export const markdownCss = css`
p { p {

View File

@ -1,8 +1,8 @@
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react'; import * as React from 'react';
import { MediaContentModel, SchemaModel, MediaTypeModel } from '../../services/models';
import { DropdownProps } from '../../common-elements/dropdown'; import { DropdownProps } from '../../common-elements/dropdown';
import { MediaContentModel, MediaTypeModel, SchemaModel } from '../../services/models';
export interface MediaTypeChildProps { export interface MediaTypeChildProps {
schema: SchemaModel; schema: SchemaModel;
@ -18,15 +18,19 @@ export interface MediaTypesSwitchProps {
@observer @observer
export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> { export class MediaTypesSwitch extends React.Component<MediaTypesSwitchProps> {
switchMedia = ({ value }) => { switchMedia = ({ value }) => {
this.props.content && this.props.content.activate(parseInt(value)); if (this.props.content) {
this.props.content.activate(parseInt(value, 10));
}
}; };
render() { render() {
const { content } = this.props; const { content } = this.props;
if (!content || !content.mediaTypes || !content.mediaTypes.length) return null; if (!content || !content.mediaTypes || !content.mediaTypes.length) {
return null;
}
const activeMimeIdx = content.activeMimeIdx; const activeMimeIdx = content.activeMimeIdx;
let options = content.mediaTypes.map((mime, idx) => { const options = content.mediaTypes.map((mime, idx) => {
return { return {
label: mime.name, label: mime.name,
value: idx.toString(), value: idx.toString(),

View File

@ -8,13 +8,13 @@ import { Badge, DarkRightPanel, H2, MiddlePanel, Row } from '../../common-elemen
import { ComponentWithOptions } from '../OptionsProvider'; import { ComponentWithOptions } from '../OptionsProvider';
import { Markdown } from '../Markdown/Markdown';
import { Parameters } from '../Parameters/Parameters';
import { ResponsesList } from '../Responses/ResponsesList';
import { RequestSamples } from '../RequestSamples/RequestSamples';
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
import { ShareLink } from '../../common-elements/linkify'; import { ShareLink } from '../../common-elements/linkify';
import { Endpoint } from '../Endpoint/Endpoint'; import { Endpoint } from '../Endpoint/Endpoint';
import { Markdown } from '../Markdown/Markdown';
import { Parameters } from '../Parameters/Parameters';
import { RequestSamples } from '../RequestSamples/RequestSamples';
import { ResponsesList } from '../Responses/ResponsesList';
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
import { OperationModel as OperationType } from '../../services/models'; import { OperationModel as OperationType } from '../../services/models';

View File

@ -1,5 +1,5 @@
import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import * as React from 'react';
import { RedocNormalizedOptions } from '../services/RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../services/RedocNormalizedOptions';

View File

@ -1,17 +1,19 @@
import * as React from 'react';
import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel'; import { DropdownOrLabel } from '../DropdownOrLabel/DropdownOrLabel';
import { ParametersGroup } from './ParametersGroup'; import { ParametersGroup } from './ParametersGroup';
import * as React from 'react';
import { UnderlinedHeader } from '../../common-elements'; import { UnderlinedHeader } from '../../common-elements';
import { Schema } from '../Schema';
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { FieldModel, RequestBodyModel } from '../../services/models'; import { FieldModel, RequestBodyModel } from '../../services/models';
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
import { Schema } from '../Schema';
import { MediaContentModel } from '../../services'; import { MediaContentModel } from '../../services';
function safePush(obj, prop, item) { function safePush(obj, prop, item) {
if (!obj[prop]) obj[prop] = []; if (!obj[prop]) {
obj[prop] = [];
}
obj[prop].push(item); obj[prop].push(item);
} }
@ -24,7 +26,7 @@ const PARAM_PLACES = ['path', 'query', 'cookie', 'header'];
export class Parameters extends React.PureComponent<ParametersProps> { export class Parameters extends React.PureComponent<ParametersProps> {
orderParams(params: FieldModel[]): Dict<FieldModel[]> { orderParams(params: FieldModel[]): Dict<FieldModel[]> {
let res = {}; const res = {};
params.forEach(param => { params.forEach(param => {
safePush(res, param.in, param); safePush(res, param.in, param);
}); });
@ -37,7 +39,7 @@ export class Parameters extends React.PureComponent<ParametersProps> {
return null; return null;
} }
let paramsMap = this.orderParams(parameters); const paramsMap = this.orderParams(parameters);
const paramsPlaces = parameters.length > 0 ? PARAM_PLACES : []; const paramsPlaces = parameters.length > 0 ? PARAM_PLACES : [];
@ -54,17 +56,18 @@ export class Parameters extends React.PureComponent<ParametersProps> {
} }
} }
function BodyContent(props: { content: MediaContentModel }): JSX.Element { function DropdownWithinHeader(props) {
const { content } = props;
return ( return (
<MediaTypesSwitch
content={content}
renderDropdown={props => (
<UnderlinedHeader key="header"> <UnderlinedHeader key="header">
Request Body schema: <DropdownOrLabel {...props} /> Request Body schema: <DropdownOrLabel {...props} />
</UnderlinedHeader> </UnderlinedHeader>
)} );
> }
function BodyContent(props: { content: MediaContentModel }): JSX.Element {
const { content } = props;
return (
<MediaTypesSwitch content={content} renderDropdown={DropdownWithinHeader}>
{({ schema }) => { {({ schema }) => {
return <Schema skipReadOnly={true} key="schema" schema={schema} />; return <Schema skipReadOnly={true} key="schema" schema={schema} />;
}} }}

View File

@ -16,7 +16,9 @@ export interface ParametersGroupProps {
export class ParametersGroup extends React.PureComponent<ParametersGroupProps, any> { export class ParametersGroup extends React.PureComponent<ParametersGroupProps, any> {
render() { render() {
const { place, parameters } = this.props; const { place, parameters } = this.props;
if (!parameters || !parameters.length) return null; if (!parameters || !parameters.length) {
return null;
}
return ( return (
<div key={place}> <div key={place}>

View File

@ -1,9 +1,9 @@
import * as React from 'react'; import * as React from 'react';
import { SmallTabs, Tab, TabList, TabPanel } from '../../common-elements';
import { MediaTypeModel } from '../../services/models'; import { MediaTypeModel } from '../../services/models';
import { StyledJson } from '../JsonViewer/JsonViewer'; import { StyledJson } from '../JsonViewer/JsonViewer';
import { SourceCode } from '../SourceCode/SourceCode'; import { SourceCode } from '../SourceCode/SourceCode';
import { SmallTabs, TabList, TabPanel, Tab } from '../../common-elements';
import { NoSampleLabel } from './styled.elements'; import { NoSampleLabel } from './styled.elements';
import { isJsonLike, langFromMime } from '../../utils'; import { isJsonLike, langFromMime } from '../../utils';
@ -24,7 +24,9 @@ export class MediaTypeSamples extends React.Component<PayloadSamplesProps> {
(sample && <SourceCode lang={langFromMime(mimeType)} source={sample} />) || { noSample }; (sample && <SourceCode lang={langFromMime(mimeType)} source={sample} />) || { noSample };
const examplesNames = Object.keys(examples); const examplesNames = Object.keys(examples);
if (examplesNames.length === 0) return noSample; if (examplesNames.length === 0) {
return noSample;
}
if (examplesNames.length > 1) { if (examplesNames.length > 1) {
return ( return (
<SmallTabs> <SmallTabs>

View File

@ -1,6 +1,6 @@
import { MediaTypeSamples } from './MediaTypeSamples';
import * as React from 'react'
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { MediaTypeSamples } from './MediaTypeSamples';
import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch'; import { MediaTypesSwitch } from '../MediaTypeSwitch/MediaTypesSwitch';
@ -16,15 +16,18 @@ export interface PayloadSamplesProps {
export class PayloadSamples extends React.Component<PayloadSamplesProps> { export class PayloadSamples extends React.Component<PayloadSamplesProps> {
render() { render() {
const mimeContent = this.props.content; const mimeContent = this.props.content;
if (mimeContent === undefined) return null; if (mimeContent === undefined) {
return null;
}
return ( return (
<MediaTypesSwitch <MediaTypesSwitch content={mimeContent} renderDropdown={this.renderDropdown}>
content={mimeContent}
renderDropdown={props => <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />}
>
{mediaType => <MediaTypeSamples key="samples" mediaType={mediaType} />} {mediaType => <MediaTypeSamples key="samples" mediaType={mediaType} />}
</MediaTypesSwitch> </MediaTypesSwitch>
); );
} }
private renderDropdown = props => {
return <DropdownOrLabel Label={MimeLabel} Dropdown={InvertedSimpleDropdown} {...props} />;
};
} }

View File

@ -27,7 +27,6 @@ export const InvertedSimpleDropdown = styled(SimpleDropdown)`
} }
`; `;
export const NoSampleLabel = styled.div` export const NoSampleLabel = styled.div`
font-family: ${props => props.theme.code.fontFamily}; font-family: ${props => props.theme.code.fontFamily};
font-size: 12px; font-size: 12px;

View File

@ -1,16 +1,16 @@
import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import * as React from 'react';
import { ThemeProvider } from '../../styled-components'; import { ThemeProvider } from '../../styled-components';
import { ApiInfo } from '../ApiInfo/ApiInfo';
import { RedocWrap, ApiContent } from './elements';
import { ApiLogo } from '../ApiLogo/ApiLogo';
import { SideMenu } from '../SideMenu/SideMenu';
import { ContentItems } from '../ContentItems/ContentItems';
import { AppStore } from '../../services'; import { AppStore } from '../../services';
import { ApiInfo } from '../ApiInfo/ApiInfo';
import { ApiLogo } from '../ApiLogo/ApiLogo';
import { ContentItems } from '../ContentItems/ContentItems';
import { OptionsProvider } from '../OptionsProvider'; import { OptionsProvider } from '../OptionsProvider';
import { SideMenu } from '../SideMenu/SideMenu';
import { StickySidebar } from '../StickySidebar/StickySidebar'; import { StickySidebar } from '../StickySidebar/StickySidebar';
import { ApiContent, RedocWrap } from './elements';
export interface RedocProps { export interface RedocProps {
store: AppStore; store: AppStore;

View File

@ -1,11 +1,11 @@
import * as React from 'react';
import * as PropTypes from 'prop-types'; import * as PropTypes from 'prop-types';
import * as React from 'react';
import { Loading } from './Loading/Loading';
import { StoreProvider } from './StoreProvider';
import { ErrorBoundary } from './ErrorBoundary';
import { Redoc } from './Redoc/Redoc';
import { RedocNormalizedOptions, RedocRawOptions } from '../services/RedocNormalizedOptions'; import { RedocNormalizedOptions, RedocRawOptions } from '../services/RedocNormalizedOptions';
import { ErrorBoundary } from './ErrorBoundary';
import { Loading } from './Loading/Loading';
import { Redoc } from './Redoc/Redoc';
import { StoreProvider } from './StoreProvider';
export interface RedocStandaloneProps { export interface RedocStandaloneProps {
spec?: object; spec?: object;

View File

@ -1,10 +1,10 @@
import { SourceCode } from '../SourceCode/SourceCode';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { OperationModel } from '../../services/models'; import { OperationModel } from '../../services/models';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
import { SourceCode } from '../SourceCode/SourceCode';
import { Tab, Tabs, TabList, TabPanel } from '../../common-elements'; import { Tab, TabList, TabPanel, Tabs } from '../../common-elements';
export interface RequestSamplesProps { export interface RequestSamplesProps {
operation: OperationModel; operation: OperationModel;

View File

@ -1,9 +1,9 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { MediaContentModel, OperationModel } from '../../services/models'; import { MediaContentModel, OperationModel } from '../../services/models';
import { Tab, Tabs, TabList, TabPanel } from '../../common-elements'; import { Tab, TabList, TabPanel, Tabs } from '../../common-elements';
import { PayloadSamples } from '../PayloadSamples/PayloadSamples'; import { PayloadSamples } from '../PayloadSamples/PayloadSamples';
export interface ResponseSampleProps { export interface ResponseSampleProps {

View File

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

View File

@ -1,10 +1,10 @@
import { PropertiesTable } from '../../common-elements/fields-layout';
import * as React from 'react'; import * as React from 'react';
import { PropertiesTable } from '../../common-elements/fields-layout';
import { HeadersCaption } from './styled.elements';
import { mapWithLast } from '../../utils';
import { FieldModel } from '../../services/models'; import { FieldModel } from '../../services/models';
import { mapWithLast } from '../../utils';
import { Field } from '../Fields/Field'; import { Field } from '../Fields/Field';
import { HeadersCaption } from './styled.elements';
export interface ResponseHeadersProps { export interface ResponseHeadersProps {
headers?: FieldModel[]; headers?: FieldModel[];

View File

@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { Markdown } from '../Markdown/Markdown';
import { ShelfIcon } from '../../common-elements'; import { ShelfIcon } from '../../common-elements';
import { Markdown } from '../Markdown/Markdown';
export interface ResponseTitleProps { export interface ResponseTitleProps {
code: string; code: string;

View File

@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { ResponseView } from './Response';
import { ResponseModel } from '../../services/models'; import { ResponseModel } from '../../services/models';
import { ResponseView } from './Response';
const ResponsesHeader = styled.h3` const ResponsesHeader = styled.h3`
font-size: 18px; font-size: 18px;
@ -19,7 +19,9 @@ export class ResponsesList extends React.PureComponent<ResponseListProps> {
render() { render() {
const { responses } = this.props; const { responses } = this.props;
if (!responses || responses.length === 0) return null; if (!responses || responses.length === 0) {
return null;
}
return ( return (
<div> <div>

View File

@ -1,8 +1,8 @@
import styled from '../../styled-components'; import styled from '../../styled-components';
import { ResponseTitle } from './ResponseTitle';
import { UnderlinedHeader } from '../../common-elements'; import { UnderlinedHeader } from '../../common-elements';
import { transparentizeHex } from '../../utils'; import { transparentizeHex } from '../../utils';
import { ResponseTitle } from './ResponseTitle';
export const StyledResponseTitle = styled(ResponseTitle)` export const StyledResponseTitle = styled(ResponseTitle)`
padding: 10px; padding: 10px;

View File

@ -1,8 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { SchemaProps, Schema } from './Schema'; import { Schema, SchemaProps } from './Schema';
import { ArrayOpenningLabel, ArrayClosingLabel } from '../../common-elements'; import { ArrayClosingLabel, ArrayOpenningLabel } from '../../common-elements';
export class ArraySchema extends React.PureComponent<SchemaProps> { export class ArraySchema extends React.PureComponent<SchemaProps> {
render() { render() {

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { StyledDropdown, DropdownOption } from '../../common-elements/dropdown'; import { DropdownOption, StyledDropdown } from '../../common-elements/dropdown';
import { SchemaModel } from '../../services/models'; import { SchemaModel } from '../../services/models';
@observer @observer
@ -10,7 +10,9 @@ export class DiscriminatorDropdown extends React.Component<{
enumValues: string[]; enumValues: string[];
}> { }> {
sortOptions(options: DropdownOption[], enumValues: string[]): void { sortOptions(options: DropdownOption[], enumValues: string[]): void {
if (enumValues.length === 0) return; if (enumValues.length === 0) {
return;
}
const enumOrder = {}; const enumOrder = {};
@ -48,7 +50,7 @@ export class DiscriminatorDropdown extends React.Component<{
} }
changeActiveChild = ({ value }) => { changeActiveChild = ({ value }) => {
const idx = parseInt(value); const idx = parseInt(value, 10);
this.props.parent.activateOneOf(idx); this.props.parent.activateOneOf(idx);
}; };
} }

View File

@ -1,17 +1,17 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { SchemaModel, FieldModel } from '../../services/models'; import { FieldModel, SchemaModel } from '../../services/models';
import { Field } from '../Fields/Field';
import { DiscriminatorDropdown } from './DiscriminatorDropdown';
import { Schema, SchemaProps } from './Schema';
import { import {
InnerPropertiesWrap, InnerPropertiesWrap,
PropertiesTable, PropertiesTable,
PropertiesTableCaption, PropertiesTableCaption,
PropertyCellWithInner, PropertyCellWithInner,
} from '../../common-elements/fields-layout'; } from '../../common-elements/fields-layout';
import { Field } from '../Fields/Field';
import { DiscriminatorDropdown } from './DiscriminatorDropdown';
import { Schema, SchemaProps } from './Schema';
import { mapWithLast } from '../../utils'; import { mapWithLast } from '../../utils';

View File

@ -1,8 +1,33 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { OneOfButton, OneOfLabel, OneOfList } from '../../common-elements/schema'; import {
import { SchemaProps, Schema } from './Schema'; OneOfButton as StyledOneOfButton,
OneOfLabel,
OneOfList,
} from '../../common-elements/schema';
import { SchemaModel } from '../../services/models';
import { Schema, SchemaProps } from './Schema';
export interface OneOfButtonProps {
subSchema: SchemaModel;
idx: number;
schema: SchemaModel;
}
export class OneOfButton extends React.PureComponent<OneOfButtonProps> {
render() {
const { idx, schema, subSchema } = this.props;
return (
<StyledOneOfButton active={idx === schema.activeOneOf} onClick={this.activateOneOf}>
{subSchema.title || subSchema.displayType}
</StyledOneOfButton>
);
}
activateOneOf() {
this.props.schema.activateOneOf(this.props.idx);
}
}
@observer @observer
export class OneOfSchema extends React.Component<SchemaProps> { export class OneOfSchema extends React.Component<SchemaProps> {
@ -17,13 +42,7 @@ export class OneOfSchema extends React.Component<SchemaProps> {
<OneOfLabel> {schema.oneOfType} </OneOfLabel> <OneOfLabel> {schema.oneOfType} </OneOfLabel>
<OneOfList> <OneOfList>
{oneOf.map((subSchema, idx) => ( {oneOf.map((subSchema, idx) => (
<OneOfButton <OneOfButton key={subSchema._$ref} schema={schema} subSchema={subSchema} idx={idx} />
key={subSchema._$ref}
active={idx === schema.activeOneOf}
onClick={() => schema.activateOneOf(idx)}
>
{subSchema.title || subSchema.displayType}
</OneOfButton>
))} ))}
</OneOfList> </OneOfList>
<Schema schema={oneOf[schema.activeOneOf]} /> <Schema schema={oneOf[schema.activeOneOf]} />

View File

@ -1,14 +1,14 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { FieldDetails } from '../Fields/FieldDetails';
import { RecursiveLabel, TypeName, TypeTitle } from '../../common-elements/fields'; import { RecursiveLabel, TypeName, TypeTitle } from '../../common-elements/fields';
import { FieldDetails } from '../Fields/FieldDetails';
import { SchemaModel } from '../../services/models'; import { SchemaModel } from '../../services/models';
import { ArraySchema } from './ArraySchema';
import { ObjectSchema } from './ObjectSchema'; import { ObjectSchema } from './ObjectSchema';
import { OneOfSchema } from './OneOfSchema'; import { OneOfSchema } from './OneOfSchema';
import { ArraySchema } from './ArraySchema';
export interface SchemaProps { export interface SchemaProps {
schema: SchemaModel; schema: SchemaModel;
@ -21,7 +21,9 @@ export interface SchemaProps {
export class Schema extends React.Component<Partial<SchemaProps>> { export class Schema extends React.Component<Partial<SchemaProps>> {
render() { render() {
const { schema } = this.props; const { schema } = this.props;
if (!schema) return <em> Schema not provided </em>; if (!schema) {
return <em> Schema not provided </em>;
}
const { type, oneOf, discriminatorProp, isCircular } = schema; const { type, oneOf, discriminatorProp, isCircular } = schema;
if (isCircular) { if (isCircular) {

View File

@ -2,8 +2,8 @@ import * as React from 'react';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { transparentizeHex } from '../../utils/styled'; import { transparentizeHex } from '../../utils/styled';
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
import { UnderlinedHeader } from '../../common-elements/headers'; import { UnderlinedHeader } from '../../common-elements/headers';
import { SecurityRequirementModel } from '../../services/models/SecurityRequirement';
const ScopeName = styled.code` const ScopeName = styled.code`
font-size: ${props => props.theme.code.fontSize}; font-size: ${props => props.theme.code.fontSize};
@ -57,7 +57,9 @@ export interface SecurityRequirementsProps {
export class SecurityRequirements extends React.PureComponent<SecurityRequirementsProps> { export class SecurityRequirements extends React.PureComponent<SecurityRequirementsProps> {
render() { render() {
const securities = this.props.securities; const securities = this.props.securities;
if (!securities.length) return null; if (!securities.length) {
return null;
}
return ( return (
<div> <div>
<AuthHeaderColumn> <AuthHeaderColumn>

View File

@ -2,10 +2,10 @@ import * as React from 'react';
import { SecuritySchemesModel } from '../../services/models'; import { SecuritySchemesModel } from '../../services/models';
import styled from '../../styled-components';
import { H2, ShareLink } from '../../common-elements'; import { H2, ShareLink } from '../../common-elements';
import { Markdown } from '../Markdown/Markdown'; import styled from '../../styled-components';
import { OpenAPISecurityScheme } from '../../types'; import { OpenAPISecurityScheme } from '../../types';
import { Markdown } from '../Markdown/Markdown';
const AUTH_TYPES = { const AUTH_TYPES = {
oauth2: 'OAuth2', oauth2: 'OAuth2',
@ -76,7 +76,9 @@ export interface SecurityDefsProps {
export class SecurityDefs extends React.PureComponent<SecurityDefsProps> { export class SecurityDefs extends React.PureComponent<SecurityDefsProps> {
render() { render() {
if (!this.props.securitySchemes) return null; if (!this.props.securitySchemes) {
return null;
}
return ( return (
<div> <div>

View File

@ -3,6 +3,7 @@ import * as React from 'react';
import { ClipboardService } from '../../services'; import { ClipboardService } from '../../services';
export class SelectOnClick extends React.PureComponent { export class SelectOnClick extends React.PureComponent {
private child: HTMLDivElement | null;
handleClick = () => { handleClick = () => {
ClipboardService.selectElement(this.refs.child); ClipboardService.selectElement(this.refs.child);
}; };
@ -10,7 +11,7 @@ export class SelectOnClick extends React.PureComponent {
render() { render() {
const { children } = this.props; const { children } = this.props;
return ( return (
<div ref="child" onClick={this.handleClick}> <div ref={el => (this.child = el)} onClick={this.handleClick}>
{children} {children}
</div> </div>
); );

View File

@ -1,10 +1,10 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { IMenuItem, OperationModel } from '../../services';
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
import { ShelfIcon } from '../../common-elements/shelfs'; import { ShelfIcon } from '../../common-elements/shelfs';
import { IMenuItem, OperationModel } from '../../services';
import { MenuItems } from './MenuItems'; import { MenuItems } from './MenuItems';
import { MenuItemLabel, MenuItemLi, MenuItemTitle, OperationBadge } from './styled.elements';
interface MenuItemProps { interface MenuItemProps {
item: IMenuItem; item: IMenuItem;
@ -42,9 +42,9 @@ export class MenuItem extends React.Component<MenuItemProps> {
} }
} }
export type OperationMenuItemContentProps = { export interface OperationMenuItemContentProps {
item: OperationModel; item: OperationModel;
}; }
@observer @observer
class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> { class OperationMenuItemContent extends React.Component<OperationMenuItemContentProps> {

View File

@ -1,10 +1,10 @@
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { IMenuItem } from '../../services'; import { IMenuItem } from '../../services';
import { MenuItemUl } from './styled.elements';
import { MenuItem } from './MenuItem'; import { MenuItem } from './MenuItem';
import { MenuItemUl } from './styled.elements';
interface MenuItemsProps { interface MenuItemsProps {
items: IMenuItem[]; items: IMenuItem[];

View File

@ -1,8 +1,8 @@
import { ComponentWithOptions } from '../OptionsProvider';
import * as React from 'react';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import * as React from 'react';
import { ComponentWithOptions } from '../OptionsProvider';
import { MenuStore, IMenuItem } from '../../services/MenuStore'; import { IMenuItem, MenuStore } from '../../services/MenuStore';
import { MenuItems } from './MenuItems'; import { MenuItems } from './MenuItems';
import { PerfectScrollbar } from '../../common-elements/perfect-scrollbar'; import { PerfectScrollbar } from '../../common-elements/perfect-scrollbar';

View File

@ -1,5 +1,5 @@
import { deprecatedCss } from '../../common-elements'; import { deprecatedCss } from '../../common-elements';
import styled, { withProps, css } from '../../styled-components'; import styled, { css, withProps } from '../../styled-components';
export const OperationBadge = withProps<{ type: string }>(styled.span).attrs({ export const OperationBadge = withProps<{ type: string }>(styled.span).attrs({
className: props => `operation-type ${props.type}`, className: props => `operation-type ${props.type}`,
@ -88,7 +88,7 @@ export const MenuItemLi = withProps<{ depth: number }>(styled.li)`
`; `;
export const menuItemDepth = { export const menuItemDepth = {
'0': css` 0: css`
opacity: 0.7; opacity: 0.7;
text-transform: uppercase; text-transform: uppercase;
font-size: 0.8em; font-size: 0.8em;
@ -96,14 +96,14 @@ export const menuItemDepth = {
cursor: default; cursor: default;
color: ${props => props.theme.colors.text}; color: ${props => props.theme.colors.text};
`, `,
'1': css` 1: css`
font-size: 0.929em; font-size: 0.929em;
text-transform: uppercase; text-transform: uppercase;
&:hover { &:hover {
color: ${props => props.theme.colors.main}; color: ${props => props.theme.colors.main};
} }
`, `,
'2': css` 2: css`
color: ${props => props.theme.colors.text}; color: ${props => props.theme.colors.text};
`, `,
}; };

View File

@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { highlight } from '../../utils';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { highlight } from '../../utils';
const StyledPre = styled.pre` const StyledPre = styled.pre`
font-family: ${props => props.theme.code.fontFamily}; font-family: ${props => props.theme.code.fontFamily};

View File

@ -1,7 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import { ComponentWithOptions } from '../OptionsProvider';
import { RedocNormalizedOptions, RedocRawOptions } from '../../services/RedocNormalizedOptions'; import { RedocNormalizedOptions, RedocRawOptions } from '../../services/RedocNormalizedOptions';
import styled from '../../styled-components'; import styled from '../../styled-components';
import { ComponentWithOptions } from '../OptionsProvider';
let Stickyfill; let Stickyfill;
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
@ -34,11 +34,15 @@ export class StickySidebar extends ComponentWithOptions<StickySidebarProps> {
stickyElement: Element; stickyElement: Element;
componentDidMount() { componentDidMount() {
stickyfill && stickyfill.add(this.stickyElement); if (stickyfill) {
stickyfill.add(this.stickyElement);
}
} }
componentWillUnmount() { componentWillUnmount() {
stickyfill && stickyfill.remove(this.stickyElement); if (stickyfill) {
stickyfill.remove(this.stickyElement);
}
} }
get scrollYOffset() { get scrollYOffset() {
@ -52,7 +56,7 @@ export class StickySidebar extends ComponentWithOptions<StickySidebarProps> {
} }
render() { render() {
let top = this.scrollYOffset; const top = this.scrollYOffset;
const height = `calc(100vh - ${top})`; const height = `calc(100vh - ${top})`;
@ -60,6 +64,7 @@ export class StickySidebar extends ComponentWithOptions<StickySidebarProps> {
<StyledStickySidebar <StyledStickySidebar
className={this.props.className} className={this.props.className}
style={{ top, height }} style={{ top, height }}
// tslint:disable-next-line
innerRef={el => { innerRef={el => {
this.stickyElement = el; this.stickyElement = el;
}} }}

View File

@ -1,8 +1,8 @@
import { Component } from 'react'; import { Component } from 'react';
import { AppStore } from '../services/'; import { AppStore } from '../services/';
import { loadAndBundleSpec } from '../utils';
import { RedocRawOptions } from '../services/RedocNormalizedOptions'; import { RedocRawOptions } from '../services/RedocNormalizedOptions';
import { loadAndBundleSpec } from '../utils';
interface StoreProviderProps { interface StoreProviderProps {
specUrl?: string; specUrl?: string;
@ -36,7 +36,7 @@ export class StoreProvider extends Component<StoreProviderProps, StoreProviderSt
} }
async load() { async load() {
let { specUrl, spec, options } = this.props; const { specUrl, spec, options } = this.props;
this.setState({ this.setState({
loading: true, loading: true,
@ -62,7 +62,9 @@ export class StoreProvider extends Component<StoreProviderProps, StoreProviderSt
} }
render() { render() {
if (this.state.error) throw this.state.error; if (this.state.error) {
throw this.state.error;
}
return this.props.children(this.state); return this.props.children(this.state);
} }
} }

View File

@ -1,11 +1,11 @@
import { OpenAPISpec } from '../types'; import { OpenAPISpec } from '../types';
import { SpecStore } from './models';
import { MenuStore } from './MenuStore';
import { ScrollService } from './ScrollService';
import { loadAndBundleSpec } from '../utils/loadAndBundleSpec'; import { loadAndBundleSpec } from '../utils/loadAndBundleSpec';
import { MenuStore } from './MenuStore';
import { SpecStore } from './models';
import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOptions'; import { RedocNormalizedOptions, RedocRawOptions } from './RedocNormalizedOptions';
import { ScrollService } from './ScrollService';
type StoreData = { interface StoreData {
menu: { menu: {
activeItemIdx: number; activeItemIdx: number;
}; };
@ -14,7 +14,7 @@ type StoreData = {
data: any; data: any;
}; };
options: RedocRawOptions; options: RedocRawOptions;
}; }
export async function createStore( export async function createStore(
spec: object, spec: object,
@ -26,6 +26,18 @@ export async function createStore(
} }
export class AppStore { export class AppStore {
/**
* deserialize store
* **SUPER HACKY AND NOT OPTIMAL IMPLEMENTATION**
*/
// TODO:
static fromJS(state: StoreData): AppStore {
const inst = new AppStore(state.spec.data, state.spec.url, state.options);
inst.menu.activeItemIdx = state.menu.activeItemIdx || 0;
inst.menu.activate(inst.menu.flatItems[inst.menu.activeItemIdx]);
return inst;
}
menu: MenuStore; menu: MenuStore;
spec: SpecStore; spec: SpecStore;
rawOptions: RedocRawOptions; rawOptions: RedocRawOptions;
@ -63,15 +75,4 @@ export class AppStore {
options: this.rawOptions, options: this.rawOptions,
}; };
} }
/**
* deserialize store
* **SUPER HACKY AND NOT OPTIMAL IMPLEMENTATION**
*/
// TODO:
static fromJS(state: StoreData): AppStore {
const inst = new AppStore(state.spec.data, state.spec.url, state.options);
inst.menu.activeItemIdx = state.menu.activeItemIdx || 0;
inst.menu.activate(inst.menu.flatItems[inst.menu.activeItemIdx]);
return inst;
}
} }

View File

@ -11,8 +11,8 @@ export class ClipboardService {
static selectElement(element: any): void { static selectElement(element: any): void {
let range; let range;
let selection; let selection;
if ((<any>document.body).createTextRange) { if ((document.body as any).createTextRange) {
range = (<any>document.body).createTextRange(); range = (document.body as any).createTextRange();
range.moveToElementText(element); range.moveToElementText(element);
range.select(); range.select();
} else if (document.createRange && window.getSelection) { } else if (document.createRange && window.getSelection) {
@ -25,8 +25,8 @@ export class ClipboardService {
} }
static deselect(): void { static deselect(): void {
if ((<any>document).selection) { if ((document as any).selection) {
(<any>document).selection.empty(); (document as any).selection.empty();
} else if (window.getSelection) { } else if (window.getSelection) {
window.getSelection().removeAllRanges(); window.getSelection().removeAllRanges();
} }
@ -44,13 +44,15 @@ export class ClipboardService {
static copyElement(element: any): boolean { static copyElement(element: any): boolean {
ClipboardService.selectElement(element); ClipboardService.selectElement(element);
let res = ClipboardService.copySelected(); const res = ClipboardService.copySelected();
if (res) ClipboardService.deselect(); if (res) {
ClipboardService.deselect();
}
return res; return res;
} }
static copyCustom(text: string): boolean { static copyCustom(text: string): boolean {
let textArea = document.createElement('textarea'); const textArea = document.createElement('textarea');
textArea.style.position = 'fixed'; textArea.style.position = 'fixed';
textArea.style.top = '0'; textArea.style.top = '0';
textArea.style.left = '0'; textArea.style.left = '0';
@ -77,7 +79,7 @@ export class ClipboardService {
textArea.select(); textArea.select();
let res = ClipboardService.copySelected(); const res = ClipboardService.copySelected();
document.body.removeChild(textArea); document.body.removeChild(textArea);
return res; return res;

View File

@ -8,7 +8,7 @@ function isSameHash(a: string, b: string): boolean {
return a === b || '#' + a === b || a === '#' + b; return a === b || '#' + a === b || a === '#' + b;
} }
class _HistoryService { class IntHistoryService {
private causedHashChange: boolean = false; private causedHashChange: boolean = false;
private _emiter; private _emiter;
@ -50,7 +50,9 @@ class _HistoryService {
@bind @bind
@debounce @debounce
update(hash: string | null, rewriteHistory: boolean = false) { update(hash: string | null, rewriteHistory: boolean = false) {
if (hash == null || isSameHash(hash, this.hash)) return; if (hash == null || isSameHash(hash, this.hash)) {
return;
}
if (rewriteHistory) { if (rewriteHistory) {
if (isBrowser) { if (isBrowser) {
window.history.replaceState(null, '', window.location.href.split('#')[0] + '#' + hash); window.history.replaceState(null, '', window.location.href.split('#')[0] + '#' + hash);
@ -64,7 +66,7 @@ class _HistoryService {
} }
} }
export const HistoryService = new _HistoryService(); export const HistoryService = new IntHistoryService();
if (module.hot) { if (module.hot) {
module.hot.dispose(() => { module.hot.dispose(() => {

View File

@ -1,9 +1,9 @@
import * as Remarkable from 'remarkable'; import * as Remarkable from 'remarkable';
import { MDComponent } from '../components/Markdown/Markdown'; import { MDComponent } from '../components/Markdown/Markdown';
import { highlight, html2Str } from '../utils';
import { IMenuItem, SECTION_ATTR } from './MenuStore'; import { IMenuItem, SECTION_ATTR } from './MenuStore';
import { GroupModel } from './models'; import { GroupModel } from './models';
import { highlight, html2Str } from '../utils';
const md = new Remarkable('default', { const md = new Remarkable('default', {
html: true, html: true,
@ -20,14 +20,14 @@ export function buildComponentComment(name: string) {
return `<!-- ReDoc-Inject: <${name}> -->`; return `<!-- ReDoc-Inject: <${name}> -->`;
} }
type MarkdownHeading = { interface MarkdownHeading {
name: string; name: string;
children?: MarkdownHeading[]; children?: MarkdownHeading[];
content?: string; content?: string;
}; }
export class MarkdownRenderer { export class MarkdownRenderer {
public headings: GroupModel[] = []; headings: GroupModel[] = [];
currentTopHeading: GroupModel; currentTopHeading: GroupModel;
private _origRules: any = {}; private _origRules: any = {};
@ -52,9 +52,11 @@ export class MarkdownRenderer {
} }
flattenHeadings(container?: MarkdownHeading[]): MarkdownHeading[] { flattenHeadings(container?: MarkdownHeading[]): MarkdownHeading[] {
if (container === undefined) return []; if (container === undefined) {
let res: MarkdownHeading[] = []; return [];
for (let heading of container) { }
const res: MarkdownHeading[] = [];
for (const heading of container) {
res.push(heading); res.push(heading);
res.push(...this.flattenHeadings(heading.children)); res.push(...this.flattenHeadings(heading.children));
} }
@ -64,14 +66,16 @@ export class MarkdownRenderer {
attachHeadingsContent(rawText: string) { attachHeadingsContent(rawText: string) {
const buildRegexp = heading => new RegExp(`<h\\d ${SECTION_ATTR}="section/${heading.id}">`); const buildRegexp = heading => new RegExp(`<h\\d ${SECTION_ATTR}="section/${heading.id}">`);
let flatHeadings = this.flattenHeadings(this.headings); const flatHeadings = this.flattenHeadings(this.headings);
if (flatHeadings.length < 1) return; if (flatHeadings.length < 1) {
return;
}
let prevHeading = flatHeadings[0]; let prevHeading = flatHeadings[0];
let prevPos = rawText.search(buildRegexp(prevHeading)); let prevPos = rawText.search(buildRegexp(prevHeading));
for (let i = 1; i < flatHeadings.length; i++) { for (let i = 1; i < flatHeadings.length; i++) {
let heading = flatHeadings[i]; const heading = flatHeadings[i];
let currentPos = rawText.substr(prevPos + 1).search(buildRegexp(heading)) + prevPos + 1; const currentPos = rawText.substr(prevPos + 1).search(buildRegexp(heading)) + prevPos + 1;
prevHeading.content = html2Str(rawText.substring(prevPos, currentPos)); prevHeading.content = html2Str(rawText.substring(prevPos, currentPos));
prevHeading = heading; prevHeading = heading;
@ -84,17 +88,17 @@ export class MarkdownRenderer {
if (tokens[idx].hLevel > 2) { if (tokens[idx].hLevel > 2) {
return this._origRules.open(tokens, idx); return this._origRules.open(tokens, idx);
} else { } else {
let content = tokens[idx + 1].content; const content = tokens[idx + 1].content;
if (tokens[idx].hLevel === 1) { if (tokens[idx].hLevel === 1) {
this.currentTopHeading = this.saveHeading(content); this.currentTopHeading = this.saveHeading(content);
let id = this.currentTopHeading.id; const id = this.currentTopHeading.id;
return ( return (
`<a name="${id}"></a>` + `<a name="${id}"></a>` +
`<h${tokens[idx].hLevel} ${SECTION_ATTR}="${id}" id="${id}">` + `<h${tokens[idx].hLevel} ${SECTION_ATTR}="${id}" id="${id}">` +
`<a class="share-link" href="#${id}"></a>` `<a class="share-link" href="#${id}"></a>`
); );
} else if (tokens[idx].hLevel === 2) { } else if (tokens[idx].hLevel === 2) {
let { id } = this.saveHeading(content, this.currentTopHeading.items); const { id } = this.saveHeading(content, this.currentTopHeading.items);
return ( return (
`<a name="${id}"></a>` + `<a name="${id}"></a>` +
`<h${tokens[idx].hLevel} ${SECTION_ATTR}="${id}" id="${id}">` + `<h${tokens[idx].hLevel} ${SECTION_ATTR}="${id}" id="${id}">` +
@ -119,9 +123,9 @@ export class MarkdownRenderer {
md.renderer.rules.heading_close = this.headingCloseRule; md.renderer.rules.heading_close = this.headingCloseRule;
} }
let text = rawText; const text = rawText;
let res = md.render(text); const res = md.render(text);
this.attachHeadingsContent(res); this.attachHeadingsContent(res);
@ -142,17 +146,18 @@ export class MarkdownRenderer {
rawText: string, rawText: string,
components: Dict<MDComponent>, components: Dict<MDComponent>,
raw: boolean = true, raw: boolean = true,
): (string | MDComponent)[] { ): Array<string | MDComponent> {
let componentDefs: string[] = []; const componentDefs: string[] = [];
let match; const anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi');
let anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi'); let match = anyCompRegexp.exec(rawText);
while ((match = anyCompRegexp.exec(rawText))) { while (match) {
componentDefs.push(match[1]); componentDefs.push(match[1]);
match = anyCompRegexp.exec(rawText);
} }
let splitCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '.*?'), 'mi'); const splitCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '.*?'), 'mi');
let htmlParts = rawText.split(splitCompRegexp); const htmlParts = rawText.split(splitCompRegexp);
let res: any[] = []; const res: any[] = [];
for (let i = 0; i < htmlParts.length; i++) { for (let i = 0; i < htmlParts.length; i++) {
const htmlPart = htmlParts[i]; const htmlPart = htmlParts[i];
if (htmlPart) { if (htmlPart) {
@ -160,10 +165,12 @@ export class MarkdownRenderer {
} }
if (componentDefs[i]) { if (componentDefs[i]) {
const { componentName, attrs } = parseComponent(componentDefs[i]); const { componentName, attrs } = parseComponent(componentDefs[i]);
if (!componentName) continue; if (!componentName) {
continue;
}
res.push({ res.push({
...components[componentName], ...components[componentName],
attrs: attrs, attrs,
}); });
} }
} }
@ -178,7 +185,9 @@ function parseComponent(
attrs: any; attrs: any;
} { } {
const match = /<([\w_-]+).*?>/.exec(htmlTag); const match = /<([\w_-]+).*?>/.exec(htmlTag);
if (match === null || match.length <= 1) return { componentName: undefined, attrs: {} }; if (match === null || match.length <= 1) {
return { componentName: undefined, attrs: {} };
}
const componentName = match[1]; const componentName = match[1];
return { return {
componentName, componentName,

View File

@ -1,8 +1,8 @@
import { OpenAPIParser } from './OpenAPIParser';
import { GroupModel, OperationModel } from './models';
import { JsonPointer, isOperationName } from '../utils';
import { OpenAPIOperation, OpenAPIParameter, OpenAPISpec, OpenAPITag, Referenced } from '../types'; import { OpenAPIOperation, OpenAPIParameter, OpenAPISpec, OpenAPITag, Referenced } from '../types';
import { isOperationName, JsonPointer } from '../utils';
import { MarkdownRenderer } from './MarkdownRenderer'; import { MarkdownRenderer } from './MarkdownRenderer';
import { GroupModel, OperationModel } from './models';
import { OpenAPIParser } from './OpenAPIParser';
import { RedocNormalizedOptions } from './RedocNormalizedOptions'; import { RedocNormalizedOptions } from './RedocNormalizedOptions';
export type TagInfo = OpenAPITag & { export type TagInfo = OpenAPITag & {
@ -13,7 +13,7 @@ export type TagInfo = OpenAPITag & {
export type ExtendedOpenAPIOperation = { export type ExtendedOpenAPIOperation = {
_$ref: string; _$ref: string;
httpVerb: string; httpVerb: string;
pathParameters: Referenced<OpenAPIParameter>[]; pathParameters: Array<Referenced<OpenAPIParameter>>;
} & OpenAPIOperation; } & OpenAPIOperation;
export type TagsInfoMap = Dict<TagInfo>; export type TagsInfoMap = Dict<TagInfo>;
@ -70,9 +70,9 @@ export class MenuBuilder {
tags: TagsInfoMap, tags: TagsInfoMap,
options: RedocNormalizedOptions, options: RedocNormalizedOptions,
): GroupModel[] { ): GroupModel[] {
let res: GroupModel[] = []; const res: GroupModel[] = [];
for (let group of groups) { for (const group of groups) {
let item = new GroupModel('group', group, parent); const item = new GroupModel('group', group, parent);
item.depth = GROUP_DEPTH; item.depth = GROUP_DEPTH;
item.items = MenuBuilder.getTagsItems(parser, tags, item, group, options); item.items = MenuBuilder.getTagsItems(parser, tags, item, group, options);
res.push(item); res.push(item);
@ -111,16 +111,18 @@ export class MenuBuilder {
return tagsMap[tagName]; return tagsMap[tagName];
}); });
let res: (GroupModel | OperationModel)[] = []; const res: Array<GroupModel | OperationModel> = [];
for (let tag of tags) { for (const tag of tags) {
if (!tag) continue; if (!tag) {
let item = new GroupModel('tag', tag, parent); continue;
}
const item = new GroupModel('tag', tag, parent);
item.depth = GROUP_DEPTH + 1; item.depth = GROUP_DEPTH + 1;
item.items = this.getOperationsItems(parser, item, tag, item.depth + 1, options); item.items = this.getOperationsItems(parser, item, tag, item.depth + 1, options);
// don't put empty tag into content, instead put its operations // don't put empty tag into content, instead put its operations
if (tag.name === '') { if (tag.name === '') {
let items = this.getOperationsItems(parser, undefined, tag, item.depth + 1, options); const items = this.getOperationsItems(parser, undefined, tag, item.depth + 1, options);
res.push(...items); res.push(...items);
continue; continue;
} }
@ -147,9 +149,9 @@ export class MenuBuilder {
return []; return [];
} }
let res: OperationModel[] = []; const res: OperationModel[] = [];
for (let operationInfo of tag.operations) { for (const operationInfo of tag.operations) {
let operation = new OperationModel(parser, operationInfo, parent, options); const operation = new OperationModel(parser, operationInfo, parent, options);
operation.depth = depth; operation.depth = depth;
res.push(operation); res.push(operation);
} }
@ -161,16 +163,15 @@ export class MenuBuilder {
*/ */
static getTagsWithOperations(spec: OpenAPISpec): TagsInfoMap { static getTagsWithOperations(spec: OpenAPISpec): TagsInfoMap {
const tags: TagsInfoMap = {}; const tags: TagsInfoMap = {};
for (let tag of spec.tags || []) { for (const tag of spec.tags || []) {
tags[tag.name] = Object.assign(tag); tags[tag.name] = { ...tag, operations: [] };
tags[tag.name].operations = [];
} }
const paths = spec.paths; const paths = spec.paths;
for (let pathName of Object.keys(paths)) { for (const pathName of Object.keys(paths)) {
const path = paths[pathName]; const path = paths[pathName];
const operations = Object.keys(path).filter(isOperationName); const operations = Object.keys(path).filter(isOperationName);
for (let operationName of operations) { for (const operationName of operations) {
const operationInfo = path[operationName]; const operationInfo = path[operationName];
let operationTags = operationInfo.tags; let operationTags = operationInfo.tags;
@ -179,7 +180,7 @@ export class MenuBuilder {
operationTags = ['']; operationTags = [''];
} }
const operationPointer = JsonPointer.compile(['paths', pathName, operationName]); const operationPointer = JsonPointer.compile(['paths', pathName, operationName]);
for (let tagName of operationTags) { for (const tagName of operationTags) {
let tag = tags[tagName]; let tag = tags[tagName];
if (tag === undefined) { if (tag === undefined) {
tag = { tag = {
@ -188,7 +189,9 @@ export class MenuBuilder {
}; };
tags[tagName] = tag; tags[tagName] = tag;
} }
if (tag['x-traitTag']) continue; if (tag['x-traitTag']) {
continue;
}
tag.operations.push({ tag.operations.push({
...operationInfo, ...operationInfo,
_$ref: operationPointer, _$ref: operationPointer,

View File

@ -1,12 +1,12 @@
import { action, computed } from 'mobx';
import { querySelector } from '../utils/dom'; import { querySelector } from '../utils/dom';
import { OperationModel, GroupModel, SpecStore } from './models'; import { GroupModel, OperationModel, SpecStore } from './models';
import { computed, action } from 'mobx';
import { ScrollService } from './ScrollService';
import { HistoryService } from './HistoryService'; import { HistoryService } from './HistoryService';
import { ScrollService } from './ScrollService';
import { GROUP_DEPTH } from './MenuBuilder';
import { flattenByProp } from '../utils'; import { flattenByProp } from '../utils';
import { GROUP_DEPTH } from './MenuBuilder';
export type MenuItemGroupType = 'group' | 'tag' | 'section'; export type MenuItemGroupType = 'group' | 'tag' | 'section';
export type MenuItemType = MenuItemGroupType | 'operation'; export type MenuItemType = MenuItemGroupType | 'operation';
@ -18,7 +18,7 @@ export interface IMenuItem {
name: string; name: string;
depth: number; depth: number;
active: boolean; active: boolean;
items: Array<IMenuItem>; items: IMenuItem[];
parent?: IMenuItem; parent?: IMenuItem;
deprecated?: boolean; deprecated?: boolean;
type: MenuItemType; type: MenuItemType;
@ -34,18 +34,18 @@ export const SECTION_ATTR = 'data-section-id';
* Stores all side-menu related information * Stores all side-menu related information
*/ */
export class MenuStore { export class MenuStore {
/**
* cached flattened menu items to support absolute indexing
*/
private _unsubscribe: Function;
private _hashUnsubscribe: Function;
private _items?: (GroupModel | OperationModel)[];
/** /**
* active item absolute index (when flattened). -1 means nothing is selected * active item absolute index (when flattened). -1 means nothing is selected
*/ */
activeItemIdx: number = -1; activeItemIdx: number = -1;
/**
* cached flattened menu items to support absolute indexing
*/
private _unsubscribe: () => void;
private _hashUnsubscribe: () => void;
private _items?: Array<GroupModel | OperationModel>;
/** /**
* *
* @param spec [SpecStore](#SpecStore) which contains page content structure * @param spec [SpecStore](#SpecStore) which contains page content structure
@ -107,13 +107,15 @@ export class MenuStore {
*/ */
@action.bound @action.bound
updateOnHash(hash: string = HistoryService.hash): boolean { updateOnHash(hash: string = HistoryService.hash): boolean {
if (!hash) return false; if (!hash) {
return false;
}
let item: IMenuItem | undefined; let item: IMenuItem | undefined;
hash = hash.substr(1); hash = hash.substr(1);
let namespace = hash.split('/')[0]; const namespace = hash.split('/')[0];
let ptr = decodeURIComponent(hash.substr(namespace.length + 1)); let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
if (namespace === 'section' || namespace === 'tag') { if (namespace === 'section' || namespace === 'tag') {
let sectionId = ptr.split('/')[0]; const sectionId = ptr.split('/')[0];
ptr = ptr.substr(sectionId.length); ptr = ptr.substr(sectionId.length);
let searchId; let searchId;
@ -123,14 +125,14 @@ export class MenuStore {
searchId = ptr || namespace + '/' + sectionId; searchId = ptr || namespace + '/' + sectionId;
} }
item = this.flatItems.find(item => item.id === searchId); item = this.flatItems.find(i => i.id === searchId);
if (item === undefined) { if (item === undefined) {
this._scrollService.scrollIntoViewBySelector(`[${SECTION_ATTR}="${searchId}"]`); this._scrollService.scrollIntoViewBySelector(`[${SECTION_ATTR}="${searchId}"]`);
return false; return false;
} }
} else if (namespace === 'operation') { } else if (namespace === 'operation') {
item = this.flatItems.find(item => { item = this.flatItems.find(i => {
return (item as OperationModel).operationId === ptr; return (i as OperationModel).operationId === ptr;
}); });
} }
if (item) { if (item) {
@ -219,7 +221,11 @@ export class MenuStore {
* @see MenuStore.activate * @see MenuStore.activate
*/ */
@action @action
activateAndScroll(item: IMenuItem | undefined, updateHash: boolean, rewriteHistory?: boolean) { activateAndScroll(
item: IMenuItem | undefined,
updateHash: boolean,
rewriteHistory?: boolean,
) {
this.activate(item, updateHash, rewriteHistory); this.activate(item, updateHash, rewriteHistory);
this.scrollToActive(); this.scrollToActive();
} }

View File

@ -3,11 +3,11 @@ import { resolve as urlResolve } from 'url';
import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types'; import { OpenAPIRef, OpenAPISchema, OpenAPISpec, Referenced } from '../types';
import { appendToMdHeading, isBrowser } from '../utils/';
import { JsonPointer } from '../utils/JsonPointer'; import { JsonPointer } from '../utils/JsonPointer';
import { isNamedDefinition } from '../utils/openapi'; import { isNamedDefinition } from '../utils/openapi';
import { COMPONENT_REGEXP, buildComponentComment } from './MarkdownRenderer'; import { buildComponentComment, COMPONENT_REGEXP } from './MarkdownRenderer';
import { RedocNormalizedOptions } from './RedocNormalizedOptions'; import { RedocNormalizedOptions } from './RedocNormalizedOptions';
import { appendToMdHeading, isBrowser } from '../utils/';
export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] }; export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
@ -16,7 +16,7 @@ export type MergedOpenAPISchema = OpenAPISchema & { parentRefs?: string[] };
* endless recursion because of circular refs * endless recursion because of circular refs
*/ */
class RefCounter { class RefCounter {
public _counter = {}; _counter = {};
reset(): void { reset(): void {
this._counter = {}; this._counter = {};
@ -42,6 +42,8 @@ export class OpenAPIParser {
@observable specUrl: string; @observable specUrl: string;
@observable.ref spec: OpenAPISpec; @observable.ref spec: OpenAPISpec;
private _refCounter: RefCounter = new RefCounter();
constructor( constructor(
spec: OpenAPISpec, spec: OpenAPISpec,
specUrl: string | undefined, specUrl: string | undefined,
@ -60,8 +62,6 @@ export class OpenAPIParser {
} }
} }
private _refCounter: RefCounter = new RefCounter();
validate(spec: any) { validate(spec: any) {
if (spec.openapi === undefined) { if (spec.openapi === undefined) {
throw new Error('Document must be valid OpenAPI 3.0.0 definition'); throw new Error('Document must be valid OpenAPI 3.0.0 definition');
@ -93,8 +93,12 @@ export class OpenAPIParser {
*/ */
byRef = <T extends any = any>(ref: string): T | undefined => { byRef = <T extends any = any>(ref: string): T | undefined => {
let res; let res;
if (this.spec === undefined) return; if (!this.spec) {
if (ref.charAt(0) !== '#') ref = '#' + ref; return;
}
if (ref.charAt(0) !== '#') {
ref = '#' + ref;
}
ref = decodeURIComponent(ref); ref = decodeURIComponent(ref);
try { try {
res = JsonPointer.get(this.spec, ref); res = JsonPointer.get(this.spec, ref);
@ -120,7 +124,7 @@ export class OpenAPIParser {
resetVisited() { resetVisited() {
if (__DEV__) { if (__DEV__) {
// check in dev mode // check in dev mode
for (let k in this._refCounter._counter) { for (const k in this._refCounter._counter) {
if (this._refCounter._counter[k] > 0) { if (this._refCounter._counter[k] > 0) {
console.warn('Not exited reference: ' + k); console.warn('Not exited reference: ' + k);
} }
@ -130,7 +134,9 @@ export class OpenAPIParser {
} }
exitRef<T>(ref: Referenced<T>) { exitRef<T>(ref: Referenced<T>) {
if (!this.isRef(ref)) return; if (!this.isRef(ref)) {
return;
}
this._refCounter.exit(ref.$ref); this._refCounter.exit(ref.$ref);
} }
@ -146,6 +152,7 @@ export class OpenAPIParser {
this._refCounter.visit(obj.$ref); this._refCounter.visit(obj.$ref);
if (visited && !forceCircular) { if (visited && !forceCircular) {
// circular reference detected // circular reference detected
// tslint:disable-next-line
return Object.assign({}, resolved, { 'x-circular-ref': true }); return Object.assign({}, resolved, { 'x-circular-ref': true });
} }
// deref again in case one more $ref is here // deref again in case one more $ref is here
@ -191,7 +198,7 @@ export class OpenAPIParser {
}; };
}); });
for (let { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) { for (const { $ref: subSchemaRef, schema: subSchema } of allOfSchemas) {
if ( if (
receiver.type !== subSchema.type && receiver.type !== subSchema.type &&
receiver.type !== undefined && receiver.type !== undefined &&
@ -244,7 +251,7 @@ export class OpenAPIParser {
findDerived($refs: string[]): Dict<string> { findDerived($refs: string[]): Dict<string> {
const res: Dict<string> = {}; const res: Dict<string> = {};
const schemas = (this.spec.components && this.spec.components.schemas) || {}; const schemas = (this.spec.components && this.spec.components.schemas) || {};
for (let defName in schemas) { for (const defName in schemas) {
const def = this.deref(schemas[defName]); const def = this.deref(schemas[defName]);
if ( if (
def.allOf !== undefined && def.allOf !== undefined &&

View File

@ -1,11 +1,10 @@
import { ThemeInterface } from '../theme'; import defaultTheme, { ThemeInterface } from '../theme';
import { isNumeric, mergeObjects } from '../utils/helpers';
import { querySelector } from '../utils/dom'; import { querySelector } from '../utils/dom';
import defaultTheme from '../theme'; import { isNumeric, mergeObjects } from '../utils/helpers';
export interface RedocRawOptions { export interface RedocRawOptions {
theme?: ThemeInterface; theme?: ThemeInterface;
scrollYOffset?: number | string | Function; scrollYOffset?: number | string | (() => number);
hideHostname?: boolean | string; hideHostname?: boolean | string;
expandResponses?: string | 'all'; expandResponses?: string | 'all';
requiredPropsFirst?: boolean | string; requiredPropsFirst?: boolean | string;
@ -17,34 +16,16 @@ export interface RedocRawOptions {
} }
function argValueToBoolean(val?: string | boolean): boolean { function argValueToBoolean(val?: string | boolean): boolean {
if (val === undefined) return false; if (val === undefined) {
if (typeof val === 'string') return true; return false;
}
if (typeof val === 'string') {
return true;
}
return val; return val;
} }
export class RedocNormalizedOptions { export class RedocNormalizedOptions {
theme: ThemeInterface;
scrollYOffset: () => number;
hideHostname: boolean;
expandResponses: { [code: string]: boolean } | 'all';
requiredPropsFirst: boolean;
noAutoAuth: boolean;
nativeScrollbars: boolean;
pathInMiddlePanel: boolean;
untrustedSpec: boolean;
constructor(raw: RedocRawOptions) {
this.theme = mergeObjects({} as any, defaultTheme, raw.theme || {});
this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset);
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
this.untrustedSpec = argValueToBoolean(raw.untrustedSpec);
}
static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) { static normalizeExpandResponses(value: RedocRawOptions['expandResponses']) {
if (value === 'all') { if (value === 'all') {
return 'all'; return 'all';
@ -98,4 +79,26 @@ export class RedocNormalizedOptions {
return () => 0; return () => 0;
} }
theme: ThemeInterface;
scrollYOffset: () => number;
hideHostname: boolean;
expandResponses: { [code: string]: boolean } | 'all';
requiredPropsFirst: boolean;
noAutoAuth: boolean;
nativeScrollbars: boolean;
pathInMiddlePanel: boolean;
untrustedSpec: boolean;
constructor(raw: RedocRawOptions) {
this.theme = mergeObjects({} as any, defaultTheme, raw.theme || {});
this.scrollYOffset = RedocNormalizedOptions.normalizeScrollYOffset(raw.scrollYOffset);
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
this.untrustedSpec = argValueToBoolean(raw.untrustedSpec);
}
} }

View File

@ -1,7 +1,7 @@
import { debounce, bind } from 'decko'; import { bind, debounce } from 'decko';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import { querySelector, isBrowser } from '../utils'; import { isBrowser, querySelector } from '../utils';
import { RedocNormalizedOptions } from './RedocNormalizedOptions'; import { RedocNormalizedOptions } from './RedocNormalizedOptions';
const EVENT = 'scroll'; const EVENT = 'scroll';
@ -18,11 +18,15 @@ export class ScrollService {
bind() { bind() {
this._prevOffsetY = this.scrollY(); this._prevOffsetY = this.scrollY();
this._scrollParent && this._scrollParent.addEventListener('scroll', this.handleScroll); if (this._scrollParent) {
this._scrollParent.addEventListener('scroll', this.handleScroll);
}
} }
dispose() { dispose() {
this._scrollParent && this._scrollParent.removeEventListener('scroll', this.handleScroll); if (this._scrollParent) {
this._scrollParent.removeEventListener('scroll', this.handleScroll);
}
this._emiter.removeAllListeners(EVENT); this._emiter.removeAllListeners(EVENT);
} }
@ -37,12 +41,16 @@ export class ScrollService {
} }
isElementBellow(el: Element | null) { isElementBellow(el: Element | null) {
if (el === null) return; if (el === null) {
return;
}
return el.getBoundingClientRect().top > this.options.scrollYOffset(); return el.getBoundingClientRect().top > this.options.scrollYOffset();
} }
isElementAbove(el: Element | null) { isElementAbove(el: Element | null) {
if (el === null) return; if (el === null) {
return;
}
return Math.trunc(el.getBoundingClientRect().top) <= this.options.scrollYOffset(); return Math.trunc(el.getBoundingClientRect().top) <= this.options.scrollYOffset();
} }
@ -56,10 +64,10 @@ export class ScrollService {
return; return;
} }
element.scrollIntoView(); element.scrollIntoView();
this._scrollParent && if (this._scrollParent && this._scrollParent.scrollBy) {
this._scrollParent.scrollBy &&
(this._scrollParent.scrollBy as any)(0, -this.options.scrollYOffset()); (this._scrollParent.scrollBy as any)(0, -this.options.scrollYOffset());
} }
}
scrollIntoViewBySelector(selector: string) { scrollIntoViewBySelector(selector: string) {
const element = querySelector(selector); const element = querySelector(selector);

View File

@ -1,13 +1,13 @@
import { computed, observable } from 'mobx';
import { OpenAPISpec } from '../types'; import { OpenAPISpec } from '../types';
import { observable, computed } from 'mobx';
// import { OpenAPIExternalDocumentation, OpenAPIInfo } from '../types'; // import { OpenAPIExternalDocumentation, OpenAPIInfo } from '../types';
import { MenuBuilder } from './MenuBuilder'; import { MenuBuilder } from './MenuBuilder';
import { OpenAPIParser } from './OpenAPIParser';
import { ApiInfoModel } from './models/ApiInfo'; import { ApiInfoModel } from './models/ApiInfo';
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
import { SecuritySchemesModel } from './models/SecuritySchemes'; import { SecuritySchemesModel } from './models/SecuritySchemes';
import { OpenAPIParser } from './OpenAPIParser';
import { RedocNormalizedOptions } from './RedocNormalizedOptions';
/** /**
* Store that containts all the specification related information in the form of tree * Store that containts all the specification related information in the form of tree
*/ */

View File

@ -1,6 +1,6 @@
import { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types'; import { OpenAPIContact, OpenAPIInfo, OpenAPILicense } from '../../types';
import { OpenAPIParser } from '../OpenAPIParser';
import { isBrowser } from '../../utils/'; import { isBrowser } from '../../utils/';
import { OpenAPIParser } from '../OpenAPIParser';
export class ApiInfoModel implements OpenAPIInfo { export class ApiInfoModel implements OpenAPIInfo {
title: string; title: string;

View File

@ -1,4 +1,4 @@
import { Referenced, OpenAPIExample } from '../../types'; import { OpenAPIExample, Referenced } from '../../types';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
export class ExampleModel { export class ExampleModel {

View File

@ -1,24 +1,24 @@
import { observable, action } from 'mobx'; import { action, observable } from 'mobx';
import { OpenAPIParameter, Referenced } from '../../types'; import { OpenAPIParameter, Referenced } from '../../types';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { SchemaModel } from './Schema';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { SchemaModel } from './Schema';
/** /**
* Field or Parameter model ready to be used by components * Field or Parameter model ready to be used by components
*/ */
export class FieldModel { export class FieldModel {
@observable public expanded: boolean = false; @observable expanded: boolean = false;
public schema: SchemaModel; schema: SchemaModel;
public name: string; name: string;
public required: boolean; required: boolean;
public description: string; description: string;
public example?: string; example?: string;
public deprecated: boolean; deprecated: boolean;
public in?: string; in?: string;
constructor( constructor(
parser: OpenAPIParser, parser: OpenAPIParser,

View File

@ -1,11 +1,10 @@
import { observable, action } from 'mobx'; import { action, observable } from 'mobx';
import slugify from 'slugify';
import { OpenAPIExternalDocumentation, OpenAPITag } from '../../types'; import { OpenAPIExternalDocumentation, OpenAPITag } from '../../types';
import { ContentItemModel } from '../MenuBuilder'; import { ContentItemModel } from '../MenuBuilder';
import { IMenuItem, MenuItemGroupType } from '../MenuStore'; import { IMenuItem, MenuItemGroupType } from '../MenuStore';
const slugify = require('slugify');
/** /**
* Operations Group model ready to be used by components * Operations Group model ready to be used by components
*/ */
@ -17,7 +16,7 @@ export class GroupModel implements IMenuItem {
description?: string; description?: string;
type: MenuItemGroupType; type: MenuItemGroupType;
items: Array<ContentItemModel> = []; items: ContentItemModel[] = [];
parent?: GroupModel; parent?: GroupModel;
externalDocs?: OpenAPIExternalDocumentation; externalDocs?: OpenAPIExternalDocumentation;
@ -48,7 +47,9 @@ export class GroupModel implements IMenuItem {
@action @action
deactivate() { deactivate() {
// disallow deactivating groups // disallow deactivating groups
if (this.type === 'group') return; if (this.type === 'group') {
return;
}
this.active = false; this.active = false;
} }

View File

@ -1,10 +1,10 @@
import { observable, action, computed } from 'mobx'; import { action, computed, observable } from 'mobx';
import { OpenAPIMediaType } from '../../types'; import { OpenAPIMediaType } from '../../types';
import { MediaTypeModel } from './MediaType'; import { MediaTypeModel } from './MediaType';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
/** /**
* MediaContent model ready to be sued by React components * MediaContent model ready to be sued by React components

View File

@ -1,10 +1,10 @@
import * as Sampler from 'openapi-sampler'; import * as Sampler from 'openapi-sampler';
import { OpenAPIExample, OpenAPIMediaType } from '../../types'; import { OpenAPIExample, OpenAPIMediaType } from '../../types';
import { SchemaModel } from './Schema';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { SchemaModel } from './Schema';
import { mapValues, isJsonLike } from '../../utils'; import { isJsonLike, mapValues } from '../../utils';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { ExampleModel } from './Example'; import { ExampleModel } from './Example';
@ -39,14 +39,13 @@ export class MediaTypeModel {
} }
generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) { generateExample(parser: OpenAPIParser, info: OpenAPIMediaType) {
const { schema, isRequestType } = this; if (this.schema && this.schema.oneOf) {
if (schema && schema.oneOf) {
this.examples = {}; this.examples = {};
for (let subSchema of schema.oneOf) { for (const subSchema of this.schema.oneOf) {
this.examples[subSchema.title] = { this.examples[subSchema.title] = {
value: Sampler.sample( value: Sampler.sample(
subSchema.rawSchema, subSchema.rawSchema,
{ skipReadOnly: isRequestType, skipReadWrite: !isRequestType }, { skipReadOnly: this.isRequestType, skipReadWrite: !this.isRequestType },
parser.spec, parser.spec,
), ),
}; };
@ -56,7 +55,7 @@ export class MediaTypeModel {
default: new ExampleModel(parser, { default: new ExampleModel(parser, {
value: Sampler.sample( value: Sampler.sample(
info.schema, info.schema,
{ skipReadOnly: isRequestType, skipReadWrite: !isRequestType }, { skipReadOnly: this.isRequestType, skipReadWrite: !this.isRequestType },
parser.spec, parser.spec,
), ),
}), }),

View File

@ -1,21 +1,21 @@
import { observable, action } from 'mobx'; import { action, observable } from 'mobx';
import { join as joinPaths } from 'path'; import { join as joinPaths } from 'path';
import { parse as urlParse } from 'url'; import { parse as urlParse } from 'url';
import { IMenuItem } from '../MenuStore'; import { IMenuItem } from '../MenuStore';
import { SecurityRequirementModel } from './SecurityRequirement';
import { GroupModel } from './Group.model'; import { GroupModel } from './Group.model';
import { SecurityRequirementModel } from './SecurityRequirement';
import { OpenAPIExternalDocumentation, OpenAPIServer } from '../../types'; import { OpenAPIExternalDocumentation, OpenAPIServer } from '../../types';
import { FieldModel } from './Field'; import { getOperationSummary, isAbsolutePath, JsonPointer, stripTrailingSlash } from '../../utils';
import { ResponseModel } from './Response';
import { RequestBodyModel } from './RequestBody';
import { CodeSample } from './types';
import { OpenAPIParser } from '../OpenAPIParser';
import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder'; import { ContentItemModel, ExtendedOpenAPIOperation } from '../MenuBuilder';
import { JsonPointer, getOperationSummary, isAbsolutePath, stripTrailingSlash } from '../../utils'; import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { FieldModel } from './Field';
import { RequestBodyModel } from './RequestBody';
import { ResponseModel } from './Response';
import { CodeSample } from './types';
/** /**
* Operation model ready to be used by components * Operation model ready to be used by components
@ -30,7 +30,7 @@ export class OperationModel implements IMenuItem {
parent?: GroupModel; parent?: GroupModel;
externalDocs?: OpenAPIExternalDocumentation; externalDocs?: OpenAPIExternalDocumentation;
items: Array<ContentItemModel> = []; items: ContentItemModel[] = [];
depth: number; depth: number;
@ -80,7 +80,7 @@ export class OperationModel implements IMenuItem {
let hasSuccessResponses = false; let hasSuccessResponses = false;
this.responses = Object.keys(operationSpec.responses || []) this.responses = Object.keys(operationSpec.responses || [])
.filter(code => { .filter(code => {
if (parseInt(code) >= 100 && parseInt(code) <= 399) { if (parseInt(code, 10) >= 100 && parseInt(code, 10) <= 399) {
hasSuccessResponses = true; hasSuccessResponses = true;
} }
return isNumeric(code) || code === 'default'; return isNumeric(code) || code === 'default';

View File

@ -1,8 +1,8 @@
import { OpenAPIRequestBody, Referenced } from '../../types'; import { OpenAPIRequestBody, Referenced } from '../../types';
import { MediaContentModel } from './MediaContent';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { MediaContentModel } from './MediaContent';
export class RequestBodyModel { export class RequestBodyModel {
description: string; description: string;

View File

@ -1,21 +1,21 @@
import { observable, action } from 'mobx'; import { action, observable } from 'mobx';
import { OpenAPIResponse, Referenced } from '../../types'; import { OpenAPIResponse, Referenced } from '../../types';
import { getStatusCodeType } from '../../utils';
import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { FieldModel } from './Field'; import { FieldModel } from './Field';
import { MediaContentModel } from './MediaContent'; import { MediaContentModel } from './MediaContent';
import { OpenAPIParser } from '../OpenAPIParser';
import { getStatusCodeType } from '../../utils';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
export class ResponseModel { export class ResponseModel {
@observable public expanded: boolean; @observable expanded: boolean;
public content?: MediaContentModel; content?: MediaContentModel;
public code: string; code: string;
public description: string; description: string;
public type: string; type: string;
public headers: FieldModel[] = []; headers: FieldModel[] = [];
constructor( constructor(
parser: OpenAPIParser, parser: OpenAPIParser,

View File

@ -1,11 +1,12 @@
import { observable, action } from 'mobx'; import { action, observable } from 'mobx';
import { OpenAPISchema, Referenced } from '../../types'; import { OpenAPISchema, Referenced } from '../../types';
import { FieldModel } from './Field';
import { OpenAPIParser } from '../OpenAPIParser'; import { OpenAPIParser } from '../OpenAPIParser';
import { RedocNormalizedOptions } from '../RedocNormalizedOptions'; import { RedocNormalizedOptions } from '../RedocNormalizedOptions';
import { FieldModel } from './Field';
import { MergedOpenAPISchema } from '../';
import { import {
detectType, detectType,
humanizeConstraints, humanizeConstraints,
@ -13,7 +14,6 @@ import {
isPrimitiveType, isPrimitiveType,
JsonPointer, JsonPointer,
} from '../../utils/'; } from '../../utils/';
import { MergedOpenAPISchema } from '../';
// TODO: refactor this model, maybe use getters instead of copying all the values // TODO: refactor this model, maybe use getters instead of copying all the values
export class SchemaModel { export class SchemaModel {
@ -69,9 +69,9 @@ export class SchemaModel {
parser.exitRef(schemaOrRef); parser.exitRef(schemaOrRef);
for (let $ref of this.schema.parentRefs || []) { for (const parent$ref of this.schema.parentRefs || []) {
// exit all the refs visited during allOf traverse // exit all the refs visited during allOf traverse
parser.exitRef({ $ref }); parser.exitRef({ $ref: parent$ref });
} }
} }
@ -174,23 +174,25 @@ export class SchemaModel {
const derived = parser.findDerived([...(schema.namedParents || []), this._$ref]); const derived = parser.findDerived([...(schema.namedParents || []), this._$ref]);
if (schema.oneOf) { if (schema.oneOf) {
for (let variant of schema.oneOf) { for (const variant of schema.oneOf) {
if (variant.$ref === undefined) continue; if (variant.$ref === undefined) {
continue;
}
const name = JsonPointer.dirName(variant.$ref); const name = JsonPointer.dirName(variant.$ref);
derived[variant.$ref] = name; derived[variant.$ref] = name;
} }
} }
const mapping = schema.discriminator!.mapping || {}; const mapping = schema.discriminator!.mapping || {};
for (let name in mapping) { for (const name in mapping) {
derived[mapping[name]] = name; derived[mapping[name]] = name;
} }
const refs = Object.keys(derived); const refs = Object.keys(derived);
this.oneOf = refs.map(ref => { this.oneOf = refs.map(ref => {
const schema = new SchemaModel(parser, parser.byRef(ref)!, ref, this.options, true); const innerSchema = new SchemaModel(parser, parser.byRef(ref)!, ref, this.options, true);
schema.title = derived[ref]; innerSchema.title = derived[ref];
return schema; return innerSchema;
}); });
} }
} }

View File

@ -1,14 +1,14 @@
import { OpenAPISecurityRequirement } from '../../types'; import { OpenAPISecurityRequirement } from '../../types';
import { OpenAPIParser } from '../OpenAPIParser';
import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi'; import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi';
import { OpenAPIParser } from '../OpenAPIParser';
export class SecurityRequirementModel { export class SecurityRequirementModel {
schemes: { schemes: Array<{
id: string; id: string;
sectionId: string; sectionId: string;
type: string; type: string;
scopes: string[]; scopes: string[];
}[]; }>;
constructor(requirement: OpenAPISecurityRequirement, parser: OpenAPIParser) { constructor(requirement: OpenAPISecurityRequirement, parser: OpenAPIParser) {
const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {}; const schemes = (parser.spec.components && parser.spec.components.securitySchemes) || {};

View File

@ -1,6 +1,6 @@
import { OpenAPISecurityScheme, Referenced } from '../../types'; import { OpenAPISecurityScheme, Referenced } from '../../types';
import { OpenAPIParser } from '../OpenAPIParser';
import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi'; import { SECURITY_SCHEMES_SECTION } from '../../utils/openapi';
import { OpenAPIParser } from '../OpenAPIParser';
export class SecuritySchemeModel { export class SecuritySchemeModel {
id: string; id: string;

View File

@ -1,4 +1,4 @@
export type CodeSample = { export interface CodeSample {
lang: string; lang: string;
source: string; source: string;
}; }

View File

@ -1,17 +1,18 @@
import { render } from 'react-dom';
import * as React from 'react'; import * as React from 'react';
import { render } from 'react-dom';
import { querySelector } from './utils/dom';
import { RedocStandalone } from './components/RedocStandalone'; import { RedocStandalone } from './components/RedocStandalone';
import { querySelector } from './utils/dom';
export const version = __REDOC_VERSION__; export const version = __REDOC_VERSION__;
export const revision = __REDOC_REVISION__; export const revision = __REDOC_REVISION__;
function attributesMap(element: Element) { function attributesMap(element: Element) {
var res = {}; const res = {};
var elAttrs = element.attributes; const elAttrs = element.attributes;
// tslint:disable-next-line
for (let i = 0; i < elAttrs.length; i++) { for (let i = 0; i < elAttrs.length; i++) {
var attrib = elAttrs[i]; const attrib = elAttrs[i];
res[attrib.name] = attrib.value; res[attrib.name] = attrib.value;
} }
return res; return res;
@ -20,7 +21,7 @@ function attributesMap(element: Element) {
function parseOptionsFromElement(element: Element) { function parseOptionsFromElement(element: Element) {
const attrMap = attributesMap(element); const attrMap = attributesMap(element);
const res = {}; const res = {};
for (let attrName in attrMap) { for (const attrName in attrMap) {
const optionName = attrName.replace(/-(.)/g, (_, $1) => $1.toUpperCase()); const optionName = attrName.replace(/-(.)/g, (_, $1) => $1.toUpperCase());
res[optionName] = attrMap[attrName]; res[optionName] = attrMap[attrName];
// TODO: normalize options // TODO: normalize options

View File

@ -1,5 +1,4 @@
import * as styledComponents from 'styled-components'; import * as styledComponents from 'styled-components';
import { ThemedStyledComponentsModule } from 'styled-components';
import { ThemeInterface } from './theme'; import { ThemeInterface } from './theme';
@ -18,9 +17,9 @@ const {
keyframes, keyframes,
ThemeProvider, ThemeProvider,
withTheme, withTheme,
} = (styledComponents as ThemedStyledComponentsModule<any>) as ThemedStyledComponentsModule< } = (styledComponents as styledComponents.ThemedStyledComponentsModule<
ThemeInterface any
>; >) as styledComponents.ThemedStyledComponentsModule<ThemeInterface>;
export { css, injectGlobal, keyframes, ThemeProvider, withTheme, withProps }; export { css, injectGlobal, keyframes, ThemeProvider, withTheme, withProps };
export default styled; export default styled;

View File

@ -1,6 +1,6 @@
import { Omit } from './'; import { Omit } from './';
export type OpenAPISpec = { export interface OpenAPISpec {
openapi: string; openapi: string;
info: OpenAPIInfo; info: OpenAPIInfo;
servers?: OpenAPIServer[]; servers?: OpenAPIServer[];
@ -9,7 +9,7 @@ export type OpenAPISpec = {
security?: OpenAPISecurityRequirement[]; security?: OpenAPISecurityRequirement[];
tags?: OpenAPITag[]; tags?: OpenAPITag[];
externalDocs?: OpenAPIExternalDocumentation; externalDocs?: OpenAPIExternalDocumentation;
}; }
export interface OpenAPIInfo { export interface OpenAPIInfo {
title: string; title: string;
@ -21,29 +21,28 @@ export interface OpenAPIInfo {
license?: OpenAPILicense; license?: OpenAPILicense;
} }
export type OpenAPIServer = { export interface OpenAPIServer {
url: string; url: string;
description?: string; description?: string;
variables?: { [name: string]: OpenAPIServerVariable }; variables?: { [name: string]: OpenAPIServerVariable };
}; }
export type OpenAPIServerVariable = { export interface OpenAPIServerVariable {
enum?: string[]; enum?: string[];
default: string; default: string;
description?: string; description?: string;
}; }
export type OpenAPIPaths = { [path: string]: OpenAPIPath }; export interface OpenAPIPaths {
export type OpenAPIRef = { [path: string]: OpenAPIPath;
}
export interface OpenAPIRef {
$ref: string; $ref: string;
}; }
export type Referenced<T> = OpenAPIRef | T; export type Referenced<T> = OpenAPIRef | T;
export type OpenAPIPath = export interface OpenAPIPath {
// | OpenAPIRef // paths can't be external in redoc because they are prebundled
// | {
{
summary?: string; summary?: string;
description?: string; description?: string;
get?: OpenAPIOperation; get?: OpenAPIOperation;
@ -55,25 +54,25 @@ export type OpenAPIPath =
patch?: OpenAPIOperation; patch?: OpenAPIOperation;
trace?: OpenAPIOperation; trace?: OpenAPIOperation;
servers?: OpenAPIServer[]; servers?: OpenAPIServer[];
parameters?: Referenced<OpenAPIParameter>[]; parameters?: Array<Referenced<OpenAPIParameter>>;
}; }
export type OpenAPIOperation = { export interface OpenAPIOperation {
tags?: string[]; tags?: string[];
summary?: string; summary?: string;
description?: string; description?: string;
externalDocs?: OpenAPIExternalDocumentation; externalDocs?: OpenAPIExternalDocumentation;
operationId?: string; operationId?: string;
parameters?: Referenced<OpenAPIParameter>[]; parameters?: Array<Referenced<OpenAPIParameter>>;
requestBody?: Referenced<OpenAPIRequestBody>; requestBody?: Referenced<OpenAPIRequestBody>;
responses: OpenAPIResponses; responses: OpenAPIResponses;
callbacks?: { [name: string]: Referenced<OpenAPICallback> }; callbacks?: { [name: string]: Referenced<OpenAPICallback> };
deprecated?: boolean; deprecated?: boolean;
security?: OpenAPISecurityRequirement[]; security?: OpenAPISecurityRequirement[];
servers?: OpenAPIServer[]; servers?: OpenAPIServer[];
}; }
export type OpenAPIParameter = { export interface OpenAPIParameter {
name: string; name: string;
in?: OpenAPIParameterLocation; in?: OpenAPIParameterLocation;
description?: string; description?: string;
@ -87,16 +86,16 @@ export type OpenAPIParameter = {
example?: any; example?: any;
examples?: { [media: string]: Referenced<OpenAPIExample> }; examples?: { [media: string]: Referenced<OpenAPIExample> };
content?: { [media: string]: OpenAPIMediaType }; content?: { [media: string]: OpenAPIMediaType };
}; }
export type OpenAPIExample = { export interface OpenAPIExample {
value: any; value: any;
summary?: string; summary?: string;
description?: string; description?: string;
externalValue?: string; externalValue?: string;
}; }
export type OpenAPISchema = { export interface OpenAPISchema {
$ref?: string; $ref?: string;
type?: string; type?: string;
properties?: { [name: string]: OpenAPISchema }; properties?: { [name: string]: OpenAPISchema };
@ -133,27 +132,27 @@ export type OpenAPISchema = {
minProperties?: number; minProperties?: number;
enum?: any[]; enum?: any[];
example?: any; example?: any;
}; }
export type OpenAPIDiscriminator = { export interface OpenAPIDiscriminator {
propertyName: string; propertyName: string;
mapping?: { [name: string]: string }; mapping?: { [name: string]: string };
}; }
export type OpenAPIMediaType = { export interface OpenAPIMediaType {
schema?: Referenced<OpenAPISchema>; schema?: Referenced<OpenAPISchema>;
example?: any; example?: any;
examples?: { [name: string]: Referenced<OpenAPIExample> }; examples?: { [name: string]: Referenced<OpenAPIExample> };
encoding?: { [field: string]: OpenAPIEncoding }; encoding?: { [field: string]: OpenAPIEncoding };
}; }
export type OpenAPIEncoding = { export interface OpenAPIEncoding {
contentType: string; contentType: string;
headers?: { [name: string]: Referenced<OpenAPIHeader> }; headers?: { [name: string]: Referenced<OpenAPIHeader> };
style: OpenAPIParameterStyle; style: OpenAPIParameterStyle;
explode: boolean; explode: boolean;
allowReserved: boolean; allowReserved: boolean;
}; }
export type OpenAPIParameterLocation = 'query' | 'header' | 'path' | 'cookie'; export type OpenAPIParameterLocation = 'query' | 'header' | 'path' | 'cookie';
export type OpenAPIParameterStyle = export type OpenAPIParameterStyle =
@ -165,30 +164,34 @@ export type OpenAPIParameterStyle =
| 'pipeDelimited' | 'pipeDelimited'
| 'deepObject'; | 'deepObject';
export type OpenAPIRequestBody = { export interface OpenAPIRequestBody {
description?: string; description?: string;
required?: boolean; required?: boolean;
content: { [mime: string]: OpenAPIMediaType }; content: { [mime: string]: OpenAPIMediaType };
}; }
export type OpenAPIResponses = { export interface OpenAPIResponses {
[code: string]: OpenAPIResponse; [code: string]: OpenAPIResponse;
}; }
export type OpenAPIResponse = { export interface OpenAPIResponse {
description?: string; description?: string;
headers?: { [name: string]: Referenced<OpenAPIHeader> }; headers?: { [name: string]: Referenced<OpenAPIHeader> };
content?: { [mime: string]: OpenAPIMediaType }; content?: { [mime: string]: OpenAPIMediaType };
links?: { [name: string]: Referenced<OpenAPILink> }; links?: { [name: string]: Referenced<OpenAPILink> };
}; }
export type OpenAPILink = {}; export interface OpenAPILink {
$ref?: string;
}
export type OpenAPIHeader = Omit<OpenAPIParameter, 'in' | 'name'>; export type OpenAPIHeader = Omit<OpenAPIParameter, 'in' | 'name'>;
export type OpenAPICallback = {}; export interface OpenAPICallback {
$ref?: string;
}
export type OpenAPIComponents = { export interface OpenAPIComponents {
schemas?: { [name: string]: Referenced<OpenAPISchema> }; schemas?: { [name: string]: Referenced<OpenAPISchema> };
responses?: { [name: string]: Referenced<OpenAPIResponse> }; responses?: { [name: string]: Referenced<OpenAPIResponse> };
parameters?: { [name: string]: Referenced<OpenAPIParameter> }; parameters?: { [name: string]: Referenced<OpenAPIParameter> };
@ -198,13 +201,13 @@ export type OpenAPIComponents = {
securitySchemes?: { [name: string]: Referenced<OpenAPISecurityScheme> }; securitySchemes?: { [name: string]: Referenced<OpenAPISecurityScheme> };
links?: { [name: string]: Referenced<OpenAPILink> }; links?: { [name: string]: Referenced<OpenAPILink> };
callbacks?: { [name: string]: Referenced<OpenAPICallback> }; callbacks?: { [name: string]: Referenced<OpenAPICallback> };
}; }
export type OpenAPISecurityRequirement = { export interface OpenAPISecurityRequirement {
[name: string]: string[]; [name: string]: string[];
}; }
export type OpenAPISecurityScheme = { export interface OpenAPISecurityScheme {
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect'; type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect';
description?: string; description?: string;
name?: string; name?: string;
@ -234,27 +237,27 @@ export type OpenAPISecurityScheme = {
}; };
}; };
openIdConnectUrl?: string; openIdConnectUrl?: string;
}; }
export type OpenAPITag = { export interface OpenAPITag {
name: string; name: string;
description?: string; description?: string;
externalDocs?: OpenAPIExternalDocumentation; externalDocs?: OpenAPIExternalDocumentation;
'x-displayName'?: string; 'x-displayName'?: string;
}; }
export type OpenAPIExternalDocumentation = { export interface OpenAPIExternalDocumentation {
description?: string; description?: string;
url?: string; url?: string;
}; }
export type OpenAPIContact = { export interface OpenAPIContact {
name?: string; name?: string;
url?: string; url?: string;
email?: string; email?: string;
}; }
export type OpenAPILicense = { export interface OpenAPILicense {
name: string; name: string;
url?: string; url?: string;
}; }

View File

@ -17,7 +17,7 @@ export class JsonPointer {
* JsonPointerHelper.baseName('/path/foo/subpath', 2) * JsonPointerHelper.baseName('/path/foo/subpath', 2)
*/ */
static baseName(pointer, level = 1) { static baseName(pointer, level = 1) {
let tokens = JsonPointer.parse(pointer); const tokens = JsonPointer.parse(pointer);
return tokens[tokens.length - level]; return tokens[tokens.length - level];
} }
@ -31,7 +31,7 @@ export class JsonPointer {
* JsonPointerHelper.dirName('/path/foo/subpath', 2) * JsonPointerHelper.dirName('/path/foo/subpath', 2)
*/ */
static dirName(pointer, level = 1) { static dirName(pointer, level = 1) {
let tokens = JsonPointer.parse(pointer); const tokens = JsonPointer.parse(pointer);
return JsonPointerLib.compile(tokens.slice(0, tokens.length - level)); return JsonPointerLib.compile(tokens.slice(0, tokens.length - level));
} }
@ -44,8 +44,8 @@ export class JsonPointer {
* JsonPointerHelper.relative('/path', '/path/foo/subpath') * JsonPointerHelper.relative('/path', '/path/foo/subpath')
*/ */
static relative(from, to): string[] { static relative(from, to): string[] {
let fromTokens = JsonPointer.parse(from); const fromTokens = JsonPointer.parse(from);
let toTokens = JsonPointer.parse(to); const toTokens = JsonPointer.parse(to);
return toTokens.slice(fromTokens.length); return toTokens.slice(fromTokens.length);
} }
@ -70,12 +70,12 @@ export class JsonPointer {
*/ */
static join(base, tokens) { static join(base, tokens) {
// TODO: optimize // TODO: optimize
let baseTokens = JsonPointer.parse(base); const baseTokens = JsonPointer.parse(base);
let resTokens = baseTokens.concat(tokens); const resTokens = baseTokens.concat(tokens);
return JsonPointerLib.compile(resTokens); return JsonPointerLib.compile(resTokens);
} }
static get(object: Object, pointer: string) { static get(object: object, pointer: string) {
return JsonPointerLib.get(object, pointer); return JsonPointerLib.get(object, pointer);
} }

View File

@ -15,10 +15,10 @@ export function querySelector(selector: string): Element | null {
export function html2Str(html: string): string { export function html2Str(html: string): string {
return html return html
.split(/<[^>]+>/) .split(/<[^>]+>/)
.map(function(chunk) { .map(chunk => {
return chunk.trim(); return chunk.trim();
}) })
.filter(function(trimmedChunk) { .filter(trimmedChunk => {
return trimmedChunk.length > 0; return trimmedChunk.length > 0;
}) })
.join(' '); .join(' ');

View File

@ -25,7 +25,7 @@ export function mapValues<T, P>(
iteratee: (val: T, key: string, obj: Dict<T>) => P, iteratee: (val: T, key: string, obj: Dict<T>) => P,
): Dict<P> { ): Dict<P> {
const res: { [key: string]: P } = {}; const res: { [key: string]: P } = {};
for (let key in object) { for (const key in object) {
if (object.hasOwnProperty(key)) { if (object.hasOwnProperty(key)) {
res[key] = iteratee(object[key], key, object); res[key] = iteratee(object[key], key, object);
} }
@ -35,21 +35,23 @@ export function mapValues<T, P>(
/** /**
* flattens collection using `prop` field as a children * flattens collection using `prop` field as a children
* @param items collection items * @param collectionItems collection items
* @param prop item property with child elements * @param prop item property with child elements
*/ */
export function flattenByProp<T extends object, P extends keyof T>(items: T[], prop: P): T[] { export function flattenByProp<T extends object, P extends keyof T>(
collectionItems: T[],
prop: P,
): T[] {
const res: T[] = []; const res: T[] = [];
const iterate = (items: T[]) => { const iterate = (items: T[]) => {
for (let i = 0; i < items.length; i++) { for (const item of items) {
const item = items[i];
res.push(item); res.push(item);
if (item[prop]) { if (item[prop]) {
iterate(item[prop]); iterate(item[prop]);
} }
} }
}; };
iterate(items); iterate(collectionItems);
return res; return res;
} }
@ -64,7 +66,7 @@ export function isAbsolutePath(path: string): boolean {
return /^(?:[a-z]+:)?/i.test(path); return /^(?:[a-z]+:)?/i.test(path);
} }
export function isNumeric(n: any): n is Number { export function isNumeric(n: any): n is number {
return !isNaN(parseFloat(n)) && isFinite(n); return !isNaN(parseFloat(n)) && isFinite(n);
} }
@ -92,7 +94,7 @@ export const mergeObjects = <T extends object = object>(target: T, ...sources: T
} }
if (isMergebleObject(target) && isMergebleObject(source)) { if (isMergebleObject(target) && isMergebleObject(source)) {
Object.keys(source).forEach(function(key: string) { Object.keys(source).forEach((key: string) => {
if (isMergebleObject(source[key])) { if (isMergebleObject(source[key])) {
if (!target[key]) { if (!target[key]) {
target[key] = {}; target[key] = {};

View File

@ -1,24 +1,24 @@
import * as Prism from 'prismjs'; import * as Prism from 'prismjs';
import 'prismjs/components/prism-actionscript.js'; import 'prismjs/components/prism-actionscript.js';
import 'prismjs/components/prism-bash.js';
import 'prismjs/components/prism-c.js'; import 'prismjs/components/prism-c.js';
import 'prismjs/components/prism-coffeescript.js';
import 'prismjs/components/prism-cpp.js'; import 'prismjs/components/prism-cpp.js';
import 'prismjs/components/prism-csharp.js'; import 'prismjs/components/prism-csharp.js';
import 'prismjs/components/prism-php.js';
import 'prismjs/components/prism-coffeescript.js';
import 'prismjs/components/prism-go.js'; import 'prismjs/components/prism-go.js';
import 'prismjs/components/prism-haskell.js'; import 'prismjs/components/prism-haskell.js';
import 'prismjs/components/prism-java.js'; import 'prismjs/components/prism-java.js';
import 'prismjs/components/prism-lua.js'; import 'prismjs/components/prism-lua.js';
import 'prismjs/components/prism-markup.js'; // xml
import 'prismjs/components/prism-matlab.js'; import 'prismjs/components/prism-matlab.js';
import 'prismjs/components/prism-objectivec.js';
import 'prismjs/components/prism-perl.js'; import 'prismjs/components/prism-perl.js';
import 'prismjs/components/prism-php.js';
import 'prismjs/components/prism-python.js'; import 'prismjs/components/prism-python.js';
import 'prismjs/components/prism-r.js'; import 'prismjs/components/prism-r.js';
import 'prismjs/components/prism-ruby.js'; import 'prismjs/components/prism-ruby.js';
import 'prismjs/components/prism-bash.js';
import 'prismjs/components/prism-swift.js';
import 'prismjs/components/prism-objectivec.js';
import 'prismjs/components/prism-scala.js'; import 'prismjs/components/prism-scala.js';
import 'prismjs/components/prism-markup.js'; // xml import 'prismjs/components/prism-swift.js';
import { injectGlobal } from '../styled-components'; import { injectGlobal } from '../styled-components';

View File

@ -3,7 +3,7 @@ const COLLAPSE_LEVEL = 2;
export function jsonToHTML(json) { export function jsonToHTML(json) {
level = 1; level = 1;
var output = ''; let output = '';
output += '<div class="redoc-json">'; output += '<div class="redoc-json">';
output += valueToHTML(json); output += valueToHTML(json);
output += '</div>'; output += '</div>';
@ -11,7 +11,7 @@ export function jsonToHTML(json) {
} }
function htmlEncode(t) { function htmlEncode(t) {
return t != undefined return t !== undefined
? t ? t
.toString() .toString()
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
@ -26,9 +26,9 @@ function decorateWithSpan(value, className) {
} }
function valueToHTML(value) { function valueToHTML(value) {
var valueType = typeof value, const valueType = typeof value;
output = ''; let output = '';
if (value == undefined) { if (value === undefined || value === null) {
output += decorateWithSpan('null', 'type-null'); output += decorateWithSpan('null', 'type-null');
} else if (value && value.constructor === Array) { } else if (value && value.constructor === Array) {
level++; level++;
@ -63,12 +63,12 @@ function valueToHTML(value) {
} }
function arrayToHTML(json) { function arrayToHTML(json) {
var collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : ''; const collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : '';
var i, length; let output =
var output =
'<div class="collapser"></div>[<span class="ellipsis"></span><ul class="array collapsible">'; '<div class="collapser"></div>[<span class="ellipsis"></span><ul class="array collapsible">';
var hasContents = false; let hasContents = false;
for (i = 0, length = json.length; i < length; i++) { const length = json.length;
for (let i = 0; i < length; i++) {
hasContents = true; hasContents = true;
output += '<li><div class="hoverable ' + collapsed + '">'; output += '<li><div class="hoverable ' + collapsed + '">';
output += valueToHTML(json[i]); output += valueToHTML(json[i]);
@ -85,16 +85,14 @@ function arrayToHTML(json) {
} }
function objectToHTML(json) { function objectToHTML(json) {
var collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : ''; const collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : '';
var i, const keys = Object.keys(json);
key, const length = keys.length;
length, let output =
keys = Object.keys(json);
var output =
'<div class="collapser"></div>{<span class="ellipsis"></span><ul class="obj collapsible">'; '<div class="collapser"></div>{<span class="ellipsis"></span><ul class="obj collapsible">';
var hasContents = false; let hasContents = false;
for (i = 0, length = keys.length; i < length; i++) { for (let i = 0; i < length; i++) {
key = keys[i]; const key = keys[i];
hasContents = true; hasContents = true;
output += '<li><div class="hoverable ' + collapsed + '">'; output += '<li><div class="hoverable ' + collapsed + '">';
output += '<span class="property">"' + htmlEncode(key) + '"</span>: '; output += '<span class="property">"' + htmlEncode(key) + '"</span>: ';

View File

@ -3,8 +3,8 @@ import { convertObj } from 'swagger2openapi';
import { OpenAPISpec } from '../types'; import { OpenAPISpec } from '../types';
export async function loadAndBundleSpec(specUrlOrObject: object | string): Promise<OpenAPISpec> { export async function loadAndBundleSpec(specUrlOrObject: object | string): Promise<OpenAPISpec> {
const _parser = new JsonSchemaRefParser(); const parser = new JsonSchemaRefParser();
const spec = await _parser.bundle(specUrlOrObject, { const spec = await parser.bundle(specUrlOrObject, {
resolve: { http: { withCredentials: false } }, resolve: { http: { withCredentials: false } },
} as object); } as object);

View File

@ -70,9 +70,8 @@ export function detectType(schema: OpenAPISchema): string {
return schema.type; return schema.type;
} }
const keywords = Object.keys(schemaKeywordTypes); const keywords = Object.keys(schemaKeywordTypes);
for (var i = 0; i < keywords.length; i++) { for (const keyword of keywords) {
let keyword = keywords[i]; const type = schemaKeywordTypes[keyword];
let type = schemaKeywordTypes[keyword];
if (schema[keyword] !== undefined) { if (schema[keyword] !== undefined) {
return type; return type;
} }
@ -127,9 +126,9 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] {
} else { } else {
stringRange = `[ ${schema.minLength} .. ${schema.maxLength} ] characters`; stringRange = `[ ${schema.minLength} .. ${schema.maxLength} ] characters`;
} }
} else if (schema.maxLength != undefined) { } else if (schema.maxLength !== undefined) {
stringRange = `<= ${schema.maxLength} characters`; stringRange = `<= ${schema.maxLength} characters`;
} else if (schema.minLength != undefined) { } else if (schema.minLength !== undefined) {
if (schema.minLength === 1) { if (schema.minLength === 1) {
stringRange = 'non-empty'; stringRange = 'non-empty';
} else { } else {
@ -147,10 +146,10 @@ export function humanizeConstraints(schema: OpenAPISchema): string[] {
numberRange += ' .. '; numberRange += ' .. ';
numberRange += schema.maximum; numberRange += schema.maximum;
numberRange += schema.exclusiveMaximum ? ' )' : ' ]'; numberRange += schema.exclusiveMaximum ? ' )' : ' ]';
} else if (schema.maximum != undefined) { } else if (schema.maximum !== undefined) {
numberRange = schema.exclusiveMaximum ? '< ' : '<= '; numberRange = schema.exclusiveMaximum ? '< ' : '<= ';
numberRange += schema.maximum; numberRange += schema.maximum;
} else if (schema.minimum != undefined) { } else if (schema.minimum !== undefined) {
numberRange = schema.exclusiveMinimum ? '> ' : '>= '; numberRange = schema.exclusiveMinimum ? '> ' : '>= ';
numberRange += schema.minimum; numberRange += schema.minimum;
} }

View File

@ -1,8 +1,8 @@
export function hexToRgb(hex: string): { r: number; g: number; b: number } { export function hexToRgb(hex: string): { r: number; g: number; b: number } {
hex = hex.replace('#', ''); hex = hex.replace('#', '');
var r = parseInt(hex.length == 3 ? hex.slice(0, 1).repeat(2) : hex.slice(0, 2), 16); const r = parseInt(hex.length === 3 ? hex.slice(0, 1).repeat(2) : hex.slice(0, 2), 16);
var g = parseInt(hex.length == 3 ? hex.slice(1, 2).repeat(2) : hex.slice(2, 4), 16); const g = parseInt(hex.length === 3 ? hex.slice(1, 2).repeat(2) : hex.slice(2, 4), 16);
var b = parseInt(hex.length == 3 ? hex.slice(2, 3).repeat(2) : hex.slice(4, 6), 16); const b = parseInt(hex.length === 3 ? hex.slice(2, 3).repeat(2) : hex.slice(4, 6), 16);
return { r, g, b }; return { r, g, b };
} }

26
tslint.json Normal file
View File

@ -0,0 +1,26 @@
{
"extends": ["tslint:latest", "tslint-react"],
"rules": {
"interface-name": false,
"object-literal-sort-keys": false,
"jsx-no-multiline-js": false,
"jsx-wrap-multiline": false,
"max-classes-per-file": false,
"forin": false,
"prefer-conditional-expression": false,
"no-var-requires": false,
"no-object-literal-type-assertion": false,
"no-console": false,
"jsx-curly-spacing": false,
"max-line-length": false,
"quotemark": [true, "single", "avoid-template", "jsx-double"],
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"],
"arrow-parens": [true, "ban-single-arg-parens"],
"no-submodule-imports": [true, "prismjs", "perfect-scrollbar"],
"object-literal-key-quotes": [true, "as-needed"],
"no-unused-expression": [true, "allow-tagged-template"],
"semicolon": [true, "always", "ignore-bound-class-methods"],
"member-access": [true, "no-public"]
}
}

View File

@ -1190,10 +1190,6 @@ colors@0.5.x:
version "0.5.1" version "0.5.1"
resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774"
colors@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
colors@^1.1.2, colors@~1.1.2: colors@^1.1.2, colors@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
@ -1456,10 +1452,6 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
corser@~2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87"
cpx@^1.5.0: cpx@^1.5.0:
version "1.5.0" version "1.5.0"
resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f" resolved "https://registry.yarnpkg.com/cpx/-/cpx-1.5.0.tgz#185be018511d87270dedccc293171e37655ab88f"
@ -1999,15 +1991,6 @@ ecc-jsbn@~0.1.1:
dependencies: dependencies:
jsbn "~0.1.0" jsbn "~0.1.0"
ecstatic@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-2.2.1.tgz#b5087fad439dd9dd49d31e18131454817fe87769"
dependencies:
he "^1.1.1"
mime "^1.2.11"
minimist "^1.1.0"
url-join "^2.0.2"
ee-first@1.1.1: ee-first@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -3107,7 +3090,7 @@ hawk@~6.0.2:
hoek "4.x.x" hoek "4.x.x"
sntp "2.x.x" sntp "2.x.x"
he@1.1.x, he@^1.1.1: he@1.1.x:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
@ -3245,26 +3228,13 @@ http-proxy-middleware@~0.17.4:
lodash "^4.17.2" lodash "^4.17.2"
micromatch "^2.3.11" micromatch "^2.3.11"
http-proxy@^1.16.2, http-proxy@^1.8.1: http-proxy@^1.16.2:
version "1.16.2" version "1.16.2"
resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.16.2.tgz#06dff292952bf64dbe8471fa9df73066d4f37742"
dependencies: dependencies:
eventemitter3 "1.x.x" eventemitter3 "1.x.x"
requires-port "1.x.x" requires-port "1.x.x"
http-server@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/http-server/-/http-server-0.10.0.tgz#b2a446b16a9db87ed3c622ba9beb1b085b1234a7"
dependencies:
colors "1.0.3"
corser "~2.0.0"
ecstatic "^2.0.0"
http-proxy "^1.8.1"
opener "~1.4.0"
optimist "0.6.x"
portfinder "^1.0.13"
union "~0.4.3"
http-signature@~1.1.0: http-signature@~1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
@ -4503,7 +4473,7 @@ mime@1.4.1:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
mime@^1.2.11, mime@^1.3.4, mime@^1.5.0: mime@^1.3.4, mime@^1.5.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
@ -4917,17 +4887,13 @@ openapi-sampler@1.0.0-beta.8:
dependencies: dependencies:
json-pointer "^0.6.0" json-pointer "^0.6.0"
opener@~1.4.0:
version "1.4.3"
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
opn@5.1.0, opn@^5.1.0: opn@5.1.0, opn@^5.1.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519" resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519"
dependencies: dependencies:
is-wsl "^1.1.0" is-wsl "^1.1.0"
optimist@0.6.x, optimist@^0.6.1: optimist@^0.6.1:
version "0.6.1" version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
dependencies: dependencies:
@ -5142,6 +5108,10 @@ perfect-scrollbar@^0.7.1:
version "0.7.1" version "0.7.1"
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-0.7.1.tgz#0c256fb9c5cee401d60a299687a3f9a61487e0d5" resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-0.7.1.tgz#0c256fb9c5cee401d60a299687a3f9a61487e0d5"
perfect-scrollbar@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.3.0.tgz#61da56f94b58870d8e0a617bce649cee17d1e3b2"
performance-now@^0.2.0: performance-now@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
@ -5178,7 +5148,7 @@ pluralize@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
portfinder@^1.0.13, portfinder@^1.0.9: portfinder@^1.0.9:
version "1.0.13" version "1.0.13"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
dependencies: dependencies:
@ -5591,10 +5561,6 @@ qs@6.5.1, qs@~6.5.1:
version "6.5.1" version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
qs@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/qs/-/qs-2.3.3.tgz#e9e85adbe75da0bbe4c8e0476a086290f863b404"
qs@~6.4.0: qs@~6.4.0:
version "6.4.0" version "6.4.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
@ -6974,6 +6940,16 @@ tslib@^1.7.1, tslib@^1.8.0:
version "1.8.1" version "1.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac"
tslib@^1.8.1:
version "1.9.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8"
tslint-react@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-3.4.0.tgz#12ed3fc7063f3df988370206736b50acbce07e59"
dependencies:
tsutils "^2.13.1"
tslint@^5.7.0: tslint@^5.7.0:
version "5.8.0" version "5.8.0"
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.8.0.tgz#1f49ad5b2e77c76c3af4ddcae552ae4e3612eb13" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.8.0.tgz#1f49ad5b2e77c76c3af4ddcae552ae4e3612eb13"
@ -6996,6 +6972,12 @@ tsutils@^2.12.1:
dependencies: dependencies:
tslib "^1.8.0" tslib "^1.8.0"
tsutils@^2.13.1:
version "2.19.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.19.1.tgz#76d7ebdea9d7a7bf4a05f50ead3701b0168708d7"
dependencies:
tslib "^1.8.1"
tty-browserify@0.0.0: tty-browserify@0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@ -7099,12 +7081,6 @@ union-value@^1.0.0:
is-extendable "^0.1.1" is-extendable "^0.1.1"
set-value "^0.4.3" set-value "^0.4.3"
union@~0.4.3:
version "0.4.6"
resolved "https://registry.yarnpkg.com/union/-/union-0.4.6.tgz#198fbdaeba254e788b0efcb630bc11f24a2959e0"
dependencies:
qs "~2.3.3"
uniq@^1.0.1: uniq@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
@ -7142,10 +7118,6 @@ urix@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
url-join@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.2.tgz#c072756967ad24b8b59e5741551caac78f50b8b7"
url-parse@1.0.x: url-parse@1.0.x:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.0.5.tgz#0854860422afdcfefeb6c965c662d4800169927b"