mirror of
https://github.com/Redocly/redoc.git
synced 2025-08-08 06:04:56 +03:00
Merge branch 'master' into external-docs
This commit is contained in:
commit
964d5237b6
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -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)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"version": "2.0.0-alpha.33",
|
||||
"version": "2.0.0-alpha.34",
|
||||
"description": "ReDoc",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -82,7 +82,7 @@ export class ApiInfo extends React.Component<ApiInfoProps> {
|
|||
<DownloadButton
|
||||
download={downloadFilename}
|
||||
target="_blank"
|
||||
href={downloadLink || '#'}
|
||||
href={downloadLink}
|
||||
onClick={this.handleDownloadClick}
|
||||
>
|
||||
Download
|
||||
|
|
|
@ -20,6 +20,7 @@ export const DownloadButton = styled.a`
|
|||
padding: 4px 8px 4px;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
|
||||
${extensionsHook('DownloadButton')};
|
||||
`;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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');
|
||||
|
|
38
src/services/__tests__/models/ApiInfo.test.ts
Normal file
38
src/services/__tests__/models/ApiInfo.test.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user