Skip to content

Commit

Permalink
Merge branch 'master' into only-required-parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
TATSUNO Yasuhiro authored Oct 4, 2018
2 parents f4f02e9 + 56ad844 commit 84f3a25
Show file tree
Hide file tree
Showing 16 changed files with 260 additions and 74 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

**OpenAPI/Swagger-generated API Reference Documentation**

[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![dependencies Status](https://david-dm.org/Rebilly/ReDoc/status.svg)](https://david-dm.org/Rebilly/ReDoc) [![devDependencies Status](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![dependencies Status](https://david-dm.org/Rebilly/ReDoc/status.svg)](https://david-dm.org/Rebilly/ReDoc) [![devDependencies Status](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)

[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc)
[![bundle size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js?compression=gzip&max=300000)](https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js) [![npm](https://img.shields.io/npm/dm/redoc.svg)](https://www.npmjs.com/package/redoc) [![](https://data.jsdelivr.com/v1/package/npm/redoc/badge)](https://www.jsdelivr.com/package/npm/redoc)


</div>
Expand Down Expand Up @@ -135,6 +135,10 @@ For npm:

## Usage as a React component

Install peer dependencies required by ReDoc if you don't have them installed already:

npm i react react-dom mobx@^4.2.0 styled-components

Import `RedocStandalone` component from 'redoc' module:

```js
Expand Down Expand Up @@ -215,6 +219,8 @@ You can use all of the following options with standalone version on <redoc> tag
* `hideHostname` - if set, the protocol and hostname is not shown in the operation definition.
* `expandResponses` - specify which responses to expand by default by response codes. Values should be passed as comma-separated list without spaces e.g. `expandResponses="200,201"`. Special value `"all"` expands all responses by default. Be careful: this option can slow-down documentation rendering time.
* `requiredPropsFirst` - show required properties first ordered in the same order as in `required` array.
* `sortPropsAlphabetically` - sort properties alphabetically
* `showExtensions` - show vendor extensions ("x-" fields). Extensions used by ReDoc are ignored. Can be boolean or an array of `string` with names of extensions to display
* `noAutoAuth` - do not inject Authentication section automatically
* `pathInMiddlePanel` - show path link and HTTP verb in the middle panel instead of the right one
* `hideLoading` - do not show loading animation. Useful for small docs
Expand Down
3 changes: 2 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "redoc-cli",
"version": "0.6.2",
"version": "0.6.4",
"description": "ReDoc's Command Line Interface",
"main": "index.js",
"bin": "index.js",
Expand All @@ -15,6 +15,7 @@
"react": "^16.4.2",
"react-dom": "^16.4.2",
"redoc": "^2.0.0-alpha.37",
"styled-components": "^3.4.0",
"tslib": "^1.9.3",
"yargs": "^12.0.1"
},
Expand Down
3 changes: 2 additions & 1 deletion demo/webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const tsLoader = env => ({
options: {
compilerOptions: {
module: env.bench ? 'esnext' : 'es2015',
declaration: false,
},
},
});
Expand Down Expand Up @@ -108,7 +109,7 @@ export default (env: { playground?: boolean; bench?: boolean } = {}, { mode }) =
},
},
{
test: /node_modules\/(swagger2openapi|reftools)\/.*\.js$/,
test: /node_modules\/(swagger2openapi|reftools|oas-resolver|oas-kit-common|oas-schema-walker)\/.*\.js$/,
use: {
loader: 'ts-loader',
options: {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"json-schema-ref-parser": "^5.1.2",
"lunr": "^2.3.2",
"mark.js": "^8.11.1",
"marked": "0.3.18",
"marked": "^0.5.1",
"memoize-one": "^4.0.0",
"mobx-react": "^5.2.5",
"openapi-sampler": "1.0.0-beta.14",
Expand Down
41 changes: 41 additions & 0 deletions src/components/Fields/Extensions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import styled from '../../styled-components';

import { OptionsContext } from '../OptionsProvider';

import { StyledMarkdownBlock } from '../Markdown/styled.elements';

const Extension = styled(StyledMarkdownBlock)`
opacity: 0.9;
margin: 2px 0;
`;

const ExtensionLable = styled.span`
font-style: italic;
`;

export interface ExtensionsProps {
extensions: {
[k: string]: any;
};
}

export class Extensions extends React.PureComponent<ExtensionsProps> {
render() {
return (
<OptionsContext.Consumer>
{options => (
<>
{options.showExtensions &&
Object.keys(this.props.extensions).map(key => (
<Extension key={key}>
<ExtensionLable>{key}</ExtensionLable>:{' '}
<code>{JSON.stringify(this.props.extensions[key])}</code>
</Extension>
))}
</>
)}
</OptionsContext.Consumer>
);
}
}
2 changes: 2 additions & 0 deletions src/components/Fields/FieldDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { Markdown } from '../Markdown/Markdown';
import { EnumValues } from './EnumValues';
import { Extensions } from './Extensions';
import { FieldProps } from './Field';
import { ConstraintsView } from './FieldContstraints';
import { FieldDetail } from './FieldDetail';
Expand Down Expand Up @@ -51,6 +52,7 @@ export class FieldDetails extends React.PureComponent<FieldProps> {
<FieldDetail label={'Default:'} value={schema.default} />
{!renderDiscriminatorSwitch && <EnumValues type={schema.type} values={schema.enum} />}{' '}
{showExamples && <FieldDetail label={'Example:'} value={example} />}
{<Extensions extensions={{ ...field.extensions, ...schema.extensions }} />}
<div>
<Markdown compact={true} source={description} />
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Markdown/AdvancedMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export class AdvancedMarkdown extends React.Component<AdvancedMarkdownProps> {
{ key: idx },
);
}
return <part.component key={idx} {...{ ...part.attrs, ...part.propsSelector(store) }} />;
return <part.component key={idx} {...{ ...part.props, ...part.propsSelector(store) }} />;
});
}
}
2 changes: 2 additions & 0 deletions src/components/Operation/Operation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { ResponseSamples } from '../ResponseSamples/ResponseSamples';

import { OperationModel as OperationType } from '../../services/models';
import styled from '../../styled-components';
import { Extensions } from '../Fields/Extensions';

const OperationRow = styled(Row)`
backface-visibility: hidden;
Expand Down Expand Up @@ -58,6 +59,7 @@ export class Operation extends React.Component<OperationProps> {
{externalDocs && <ExternalDocumentation externalDocs={externalDocs} />}
</Description>
)}
<Extensions extensions={operation.extensions} />
<SecurityRequirements securities={operation.security} />
<Parameters parameters={operation.parameters} body={operation.requestBody} />
<ResponsesList responses={operation.responses} />
Expand Down
115 changes: 58 additions & 57 deletions src/services/MarkdownRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ marked.setOptions({
},
});

export const LEGACY_REGEXP = '^\\s*<!-- ReDoc-Inject:\\s+?<{component}\\s*?/?>\\s+?-->\\s*$';
export const MDX_COMPONENT_REGEXP = '^\\s*<{component}\\s*?/>\\s*$';
export const LEGACY_REGEXP = '^ {0,3}<!-- ReDoc-Inject:\\s+?<({component}).*?/?>\\s+?-->\\s*$';

// prettier-ignore
export const MDX_COMPONENT_REGEXP = '(?:^ {0,3}<({component})([\\s\\S]*?)>([\\s\\S]*?)</\\2>' // with children
+ '|^ {0,3}<({component})([\\s\\S]*?)(?:/>|\\n{2,}))'; // self-closing

export const COMPONENT_REGEXP = '(?:' + LEGACY_REGEXP + '|' + MDX_COMPONENT_REGEXP + ')';

export interface MDXComponentMeta {
component: React.ComponentType;
propsSelector: (store?: AppStore) => any;
attrs?: object;
props?: object;
}

export interface MarkdownHeading {
Expand All @@ -37,11 +41,8 @@ export function buildComponentComment(name: string) {

export class MarkdownRenderer {
static containsComponent(rawText: string, componentName: string) {
const anyCompRegexp = new RegExp(
COMPONENT_REGEXP.replace(/{component}/g, componentName),
'gmi',
);
return anyCompRegexp.test(rawText);
const compRegexp = new RegExp(COMPONENT_REGEXP.replace(/{component}/g, componentName), 'gmi');
return compRegexp.test(rawText);
}

headings: MarkdownHeading[] = [];
Expand Down Expand Up @@ -147,79 +148,79 @@ export class MarkdownRenderer {
return res;
}

// TODO: rewrite this completelly! Regexp-based 👎
// Use marked ecosystem
// regexp-based 👎: remark is slow and too big so for now using marked + regexps soup
renderMdWithComponents(rawText: string): Array<string | MDXComponentMeta> {
const components = this.options && this.options.allowedMdComponents;
if (!components || Object.keys(components).length === 0) {
return [this.renderMd(rawText)];
}

const componentDefs: string[] = [];
const names = '(?:' + Object.keys(components).join('|') + ')';
const names = Object.keys(components).join('|');
const componentsRegexp = new RegExp(COMPONENT_REGEXP.replace(/{component}/g, names), 'mig');

const anyCompRegexp = new RegExp(
COMPONENT_REGEXP.replace(/{component}/g, '(' + names + '.*?)'),
'gmi',
);
let match = anyCompRegexp.exec(rawText);
const htmlParts: string[] = [];
const componentDefs: MDXComponentMeta[] = [];

let match = componentsRegexp.exec(rawText);
let lasxtIdx = 0;
while (match) {
componentDefs.push(match[1] || match[2]);
match = anyCompRegexp.exec(rawText);
htmlParts.push(rawText.substring(lasxtIdx, match.index));
lasxtIdx = componentsRegexp.lastIndex;
const compName = match[1] || match[2] || match[5];
const componentMeta = components[compName];

const props = match[3] || match[6];
const children = match[4];

if (componentMeta) {
componentDefs.push({
component: componentMeta.component,
propsSelector: componentMeta.propsSelector,
props: { ...parseProps(props), ...componentMeta.props, children },
});
}
match = componentsRegexp.exec(rawText);
}
htmlParts.push(rawText.substring(lasxtIdx));

const splitCompRegexp = new RegExp(
COMPONENT_REGEXP.replace(/{component}/g, names + '.*?'),
'mi',
);
const htmlParts = rawText.split(splitCompRegexp);
const res: any[] = [];
for (let i = 0; i < htmlParts.length; i++) {
const htmlPart = htmlParts[i];
if (htmlPart) {
res.push(this.renderMd(htmlPart));
}
if (componentDefs[i]) {
const { componentName, attrs } = parseComponent(componentDefs[i]);
if (!componentName) {
continue;
}
res.push({
...components[componentName],
attrs,
});
res.push(componentDefs[i]);
}
}
return res;
}
}

function parseComponent(
htmlTag: string,
): {
componentName?: string;
attrs: any;
} {
const match = /([\w_-]+)(\s+[\w_-]+\s*={[^}]*?})*/.exec(htmlTag);
if (match === null || match.length <= 1) {
return { componentName: undefined, attrs: {} };
function parseProps(props: string): object {
if (!props) {
return {};
}
const componentName = match[1];
const attrs = {};
for (let i = 2; i < match.length; i++) {
if (!match[i]) {
continue;
}
const [name, value] = match[i]
.trim()
.split('=')
.map(p => p.trim());

// tslint:disable-next-line
attrs[name] = value.startsWith('{') ? eval(value.substr(1, value.length - 2)) : eval(value);
const regex = /([\w-]+)\s*=\s*(?:{([^}]+?)}|"([^"]+?)")/gim;
const parsed = {};
let match;
// tslint:disable-next-line
while ((match = regex.exec(props)) !== null) {
if (match[3]) {
// string prop match (in double quotes)
parsed[match[1]] = match[3];
} else if (match[2]) {
// jsx prop match (in curly braces)
let val;
try {
val = JSON.parse(match[2]);
} catch (e) {
/* noop */
}
parsed[match[1]] = val;
}
}
return {
componentName,
attrs,
};

return parsed;
}
21 changes: 21 additions & 0 deletions src/services/RedocNormalizedOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface RedocRawOptions {
hideHostname?: boolean | string;
expandResponses?: string | 'all';
requiredPropsFirst?: boolean | string;
sortPropsAlphabetically?: boolean | string;
noAutoAuth?: boolean | string;
nativeScrollbars?: boolean | string;
pathInMiddlePanel?: boolean | string;
Expand All @@ -18,6 +19,7 @@ export interface RedocRawOptions {
hideDownloadButton?: boolean | string;
disableSearch?: boolean | string;
onlyRequiredInSamples?: boolean | string;
showExtensions?: boolean | string | string[];

unstable_ignoreMimeParameters?: boolean;

Expand Down Expand Up @@ -89,18 +91,35 @@ export class RedocNormalizedOptions {
return () => 0;
}

static normalizeShowExtensions(value: RedocRawOptions['showExtensions']): string[] | boolean {
if (typeof value === 'undefined') {
return false;
}
if (value === '') {
return true;
}

if (typeof value === 'string') {
return value.split(',').map(ext => ext.trim());
}

return value;
}

theme: ResolvedThemeInterface;
scrollYOffset: () => number;
hideHostname: boolean;
expandResponses: { [code: string]: boolean } | 'all';
requiredPropsFirst: boolean;
sortPropsAlphabetically: boolean;
noAutoAuth: boolean;
nativeScrollbars: boolean;
pathInMiddlePanel: boolean;
untrustedSpec: boolean;
hideDownloadButton: boolean;
disableSearch: boolean;
onlyRequiredInSamples: boolean;
showExtensions: boolean | string[];

/* tslint:disable-next-line */
unstable_ignoreMimeParameters: boolean;
Expand All @@ -120,13 +139,15 @@ export class RedocNormalizedOptions {
this.hideHostname = RedocNormalizedOptions.normalizeHideHostname(raw.hideHostname);
this.expandResponses = RedocNormalizedOptions.normalizeExpandResponses(raw.expandResponses);
this.requiredPropsFirst = argValueToBoolean(raw.requiredPropsFirst);
this.sortPropsAlphabetically = argValueToBoolean(raw.sortPropsAlphabetically);
this.noAutoAuth = argValueToBoolean(raw.noAutoAuth);
this.nativeScrollbars = argValueToBoolean(raw.nativeScrollbars);
this.pathInMiddlePanel = argValueToBoolean(raw.pathInMiddlePanel);
this.untrustedSpec = argValueToBoolean(raw.untrustedSpec);
this.hideDownloadButton = argValueToBoolean(raw.hideDownloadButton);
this.disableSearch = argValueToBoolean(raw.disableSearch);
this.onlyRequiredInSamples = argValueToBoolean(raw.onlyRequiredInSamples);
this.showExtensions = RedocNormalizedOptions.normalizeShowExtensions(raw.showExtensions);

this.unstable_ignoreMimeParameters = argValueToBoolean(raw.unstable_ignoreMimeParameters);

Expand Down
Loading

0 comments on commit 84f3a25

Please sign in to comment.