Merge branch 'master' into external-docs

This commit is contained in:
Matthias Mohr 2018-08-08 16:51:34 +02:00 committed by GitHub
commit 964d5237b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 132 additions and 29 deletions

View File

@ -1,3 +1,36 @@
<a name="2.0.0-alpha.34"></a>
# [2.0.0-alpha.34](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.33...v2.0.0-alpha.34) (2018-08-08)
### Bug Fixes
* add some spacing between operation description and parameters ([597688e](https://github.com/Rebilly/ReDoc/commit/597688e))
* description is not rendered if doesn't containt markdown headings ([90ed717](https://github.com/Rebilly/ReDoc/commit/90ed717)), closes [#591](https://github.com/Rebilly/ReDoc/issues/591)
* download button downloads index.html instead of spec with CLI ([334f904](https://github.com/Rebilly/ReDoc/commit/334f904)), closes [#594](https://github.com/Rebilly/ReDoc/issues/594)
* fix Authentication section is not rendered ([2ecc8bc](https://github.com/Rebilly/ReDoc/commit/2ecc8bc)), closes [#590](https://github.com/Rebilly/ReDoc/issues/590)
* fix linebreaks in multiparagraph field descriptions ([8fb9cd6](https://github.com/Rebilly/ReDoc/commit/8fb9cd6))
* preserve md heading level in description ([23559fb](https://github.com/Rebilly/ReDoc/commit/23559fb))
* render additionalProperties set to true ([#597](https://github.com/Rebilly/ReDoc/issues/597)) ([f70ac08](https://github.com/Rebilly/ReDoc/commit/f70ac08)), closes [#596](https://github.com/Rebilly/ReDoc/issues/596)
* schemes without type: object are not expandable ([97e1620](https://github.com/Rebilly/ReDoc/commit/97e1620)), closes [#599](https://github.com/Rebilly/ReDoc/issues/599)
### Features
* Add x-logo alt text support ([#584](https://github.com/Rebilly/ReDoc/issues/584)) ([568ce74](https://github.com/Rebilly/ReDoc/commit/568ce74)), closes [#546](https://github.com/Rebilly/ReDoc/issues/546)
* support label for x-code-samples ([00bd966](https://github.com/Rebilly/ReDoc/commit/00bd966)), closes [#586](https://github.com/Rebilly/ReDoc/issues/586)
<a name="2.0.0-alpha.33"></a>
# [2.0.0-alpha.33](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.32...v2.0.0-alpha.33) (2018-07-31)
### Bug Fixes
* long endpoint url overflow ([d99e918](https://github.com/Rebilly/ReDoc/commit/d99e918))
* allow word-break in code strings in md ([15dfe44](https://github.com/Rebilly/ReDoc/commit/15dfe44))
* show examples for response headers ([ba22b1e](https://github.com/Rebilly/ReDoc/commit/ba22b1e))
<a name="2.0.0-alpha.32"></a>
# [2.0.0-alpha.32](https://github.com/Rebilly/ReDoc/compare/v2.0.0-alpha.31...v2.0.0-alpha.32) (2018-07-26)

View File

@ -1,6 +1,6 @@
{
"name": "redoc",
"version": "2.0.0-alpha.33",
"version": "2.0.0-alpha.34",
"description": "ReDoc",
"repository": {
"type": "git",

View File

@ -82,7 +82,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
<DownloadButton
download={downloadFilename}
target="_blank"
href={downloadLink || '#'}
href={downloadLink}
onClick={this.handleDownloadClick}
>
Download

View File

@ -20,6 +20,7 @@ export const DownloadButton = styled.a`
padding: 4px 8px 4px;
display: inline-block;
text-decoration: none;
cursor: pointer;
${extensionsHook('DownloadButton')};
`;

View File

@ -5,10 +5,10 @@ import { SECTION_ATTR } from '../../services/MenuStore';
import { ExternalDocumentation } from '../ExternalDocumentation/ExternalDocumentation';
import { Markdown } from '../Markdown/Markdown';
import { H1, MiddlePanel, Row, ShareLink } from '../../common-elements';
import { H1, H2, MiddlePanel, Row, ShareLink } from '../../common-elements';
import { MDXComponentMeta } from '../../services/MarkdownRenderer';
import { ContentItemModel } from '../../services/MenuBuilder';
import { OperationModel } from '../../services/models';
import { GroupModel, OperationModel } from '../../services/models';
import { Operation } from '../Operation/Operation';
import { SecurityDefs } from '../SecuritySchemes/SecuritySchemes';
import { StoreConsumer } from '../StoreBuilder';
@ -80,23 +80,24 @@ export class ContentItem extends React.Component<ContentItemProps> {
@observer
export class SectionItem extends React.Component<ContentItemProps> {
render() {
const { name, description, externalDocs } = this.props.item;
const { name, description, externalDocs, level } = this.props.item as GroupModel;
const components = this.props.allowedMdComponents;
const Header = level === 2 ? H2 : H1;
return (
<Row>
<MiddlePanel>
<H1>
<Header>
<ShareLink href={'#' + this.props.item.id} />
{name}
</H1>
</Header>
{components ? (
<Markdown source={description || ''} />
) : (
<StoreConsumer>
{store => (
<Markdown source={description || ''} allowedComponents={components} store={store} />
)}
</StoreConsumer>
) : (
<Markdown source={description || ''} />
)}
{externalDocs && (
<p>

View File

@ -26,16 +26,21 @@ export const StyledMarkdownBlock = withProps<{ dense?: boolean; inline?: boolean
line-height: ${props => props.theme.typography.lineHeight};
p {
&:last-of-type {
&:last-child {
margin-bottom: 0;
}
}
${({ dense }) =>
dense &&
` p {
margin: 0;
}`}
`
p:first-child {
margin-top: 0;
}
p:last-child {
margin-bottom: 0;
}
`}
${({ inline }) =>
inline &&
@ -108,9 +113,9 @@ export const StyledMarkdownBlock = withProps<{ dense?: boolean; inline?: boolean
padding-left: 2em;
margin: 0;
margin-bottom: 1em;
> li {
margin: 1em 0;
}
// > li {
// margin: 0.5em 0;
// }
}
table {

View File

@ -17,6 +17,7 @@ import { ResponsesList } from '../Responses/ResponsesList';
import { ResponseSamples } from '../ResponseSamples/ResponseSamples';
import { OperationModel as OperationType } from '../../services/models';
import styled from '../../styled-components';
const OperationRow = Row.extend`
backface-visibility: hidden;
@ -35,6 +36,10 @@ const OperationRow = Row.extend`
}
`;
const Description = styled(Markdown)`
margin-bottom: ${({ theme }) => theme.spacing.unit * 8};
`;
export interface OperationProps {
operation: OperationType;
}
@ -55,7 +60,7 @@ export class Operation extends React.Component<OperationProps> {
{summary} {deprecated && <Badge type="warning"> Deprecated </Badge>}
</H2>
{options.pathInMiddlePanel && <Endpoint operation={operation} inverted={true} />}
{description !== undefined && <Markdown source={description} />}
{description !== undefined && <Description source={description} />}
{externalDocs && (
<p>
<ExternalDocumentation externalDocs={externalDocs} />

View File

@ -25,6 +25,7 @@ export interface MDXComponentMeta {
export interface MarkdownHeading {
id: string;
name: string;
level: number;
items?: MarkdownHeading[];
description?: string;
}
@ -50,12 +51,14 @@ export class MarkdownRenderer {
saveHeading(
name: string,
level: number,
container: MarkdownHeading[] = this.headings,
parentId?: string,
): MarkdownHeading {
const item = {
id: parentId ? `${parentId}/${safeSlugify(name)}` : `section/${safeSlugify(name)}`,
name,
level,
items: [],
};
container.push(item);
@ -105,10 +108,11 @@ export class MarkdownRenderer {
headingRule = (text: string, level: number, raw: string) => {
if (level === 1) {
this.currentTopHeading = this.saveHeading(text);
this.currentTopHeading = this.saveHeading(text, level);
} else if (level === 2) {
this.saveHeading(
text,
level,
this.currentTopHeading && this.currentTopHeading.items,
this.currentTopHeading && this.currentTopHeading.id,
);

View File

@ -76,11 +76,11 @@ export class OpenAPIParser {
const description = spec.info.description || '';
const legacySecurityRegexp = new RegExp(
COMPONENT_REGEXP.replace('{component}', '<security-definitions>'),
'gmi',
'mi',
);
const securityRegexp = new RegExp(
MDX_COMPONENT_REGEXP.replace('{component}', 'security-definitions'),
'gmi',
'mi',
);
if (!legacySecurityRegexp.test(description) && !securityRegexp.test(description)) {
const comment = buildComponentComment('security-definitions');

View File

@ -0,0 +1,38 @@
import { ApiInfoModel } from '../../models/ApiInfo';
import { OpenAPIParser } from '../../OpenAPIParser';
import { RedocNormalizedOptions } from '../../RedocNormalizedOptions';
const opts = new RedocNormalizedOptions({});
describe('Models', () => {
describe('ResponseModel', () => {
let parser: OpenAPIParser;
beforeEach(() => {
parser = new OpenAPIParser({ openapi: '3.0.0' } as any, undefined, opts);
});
test('should correctly populate description field without md headings', () => {
parser.spec = {
openapi: '3.0.0',
info: {
description: 'Test description',
},
} as any;
const info = new ApiInfoModel(parser);
expect(info.description).toEqual('Test description');
});
test('should correctly populate description up to the first md heading', () => {
parser.spec = {
openapi: '3.0.0',
info: {
description: 'Test description\nsome text\n## Heading\n test',
},
} as any;
const info = new ApiInfoModel(parser);
expect(info.description).toEqual('Test description\nsome text\n');
});
});
});

View File

@ -23,12 +23,12 @@ describe('Models', () => {
});
test('default should be sucessful by default', () => {
let resp = new ResponseModel(parser, 'default', false, {}, opts);
const resp = new ResponseModel(parser, 'default', false, {}, opts);
expect(resp.type).toEqual('success');
});
test('default should be error if defaultAsError is true', () => {
let resp = new ResponseModel(parser, 'default', true, {}, opts);
const resp = new ResponseModel(parser, 'default', true, {}, opts);
expect(resp.type).toEqual('error');
});
});

View File

@ -14,7 +14,10 @@ export class ApiInfoModel implements OpenAPIInfo {
constructor(private parser: OpenAPIParser) {
Object.assign(this, parser.spec.info);
this.description = parser.spec.info.description || '';
this.description = this.description.substring(0, this.description.search(/^##?\s+/m));
const firstHeadingLinePos = this.description.search(/^##?\s+/m);
if (firstHeadingLinePos > -1) {
this.description = this.description.substring(0, firstHeadingLinePos);
}
}
get downloadLink(): string | undefined {

View File

@ -25,6 +25,7 @@ export class GroupModel implements IMenuItem {
@observable expanded: boolean = false;
depth: number;
level: number;
//#endregion
constructor(
@ -36,6 +37,7 @@ export class GroupModel implements IMenuItem {
this.id = (tagOrGroup as MarkdownHeading).id || type + '/' + safeSlugify(tagOrGroup.name);
this.type = type;
this.name = tagOrGroup['x-displayName'] || tagOrGroup.name;
this.level = (tagOrGroup as MarkdownHeading).level || 1;
this.description = tagOrGroup.description || '';
this.parent = parent;
this.externalDocs = (tagOrGroup as OpenAPITag).externalDocs;

View File

@ -106,7 +106,7 @@ export class SchemaModel {
this.constraints = humanizeConstraints(schema);
this.displayType = this.type;
this.displayFormat = this.format;
this.isPrimitive = isPrimitiveType(schema);
this.isPrimitive = isPrimitiveType(schema, this.type);
this.default = schema.default;
this.readOnly = !!schema.readOnly;
this.writeOnly = !!schema.writeOnly;
@ -246,14 +246,14 @@ function buildFields(
sortByRequired(fields, schema.required);
}
if (typeof additionalProps === 'object') {
if (typeof additionalProps === 'object' || additionalProps === true) {
fields.push(
new FieldModel(
parser,
{
name: 'property name *',
required: false,
schema: additionalProps,
schema: additionalProps === true ? {} : additionalProps,
kind: 'additionalProperties',
},
$ref + '/additionalProperties',

View File

@ -187,6 +187,17 @@ describe('Utils', () => {
};
expect(isPrimitiveType(schema)).toEqual(false);
});
it('should work with externally provided type', () => {
const schema = {
properties: {
a: {
type: 'string',
},
},
};
expect(isPrimitiveType(schema, 'object')).toEqual(false);
});
});
describe('openapi mergeParams', () => {

View File

@ -105,18 +105,18 @@ export function detectType(schema: OpenAPISchema): string {
return 'any';
}
export function isPrimitiveType(schema: OpenAPISchema) {
export function isPrimitiveType(schema: OpenAPISchema, type: string | undefined = schema.type) {
if (schema.oneOf !== undefined || schema.anyOf !== undefined) {
return false;
}
if (schema.type === 'object') {
if (type === 'object') {
return schema.properties !== undefined
? Object.keys(schema.properties).length === 0
: schema.additionalProperties === undefined;
}
if (schema.type === 'array') {
if (type === 'array') {
if (schema.items === undefined) {
return true;
}