-
+
+
{components ? (
-
- ) : (
{store => (
)}
+ ) : (
+
)}
{externalDocs && (
diff --git a/src/components/Markdown/styled.elements.ts b/src/components/Markdown/styled.elements.ts
index 249f2ecd..512b4531 100644
--- a/src/components/Markdown/styled.elements.ts
+++ b/src/components/Markdown/styled.elements.ts
@@ -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 {
diff --git a/src/components/Operation/Operation.tsx b/src/components/Operation/Operation.tsx
index 2df45087..6aaffa99 100644
--- a/src/components/Operation/Operation.tsx
+++ b/src/components/Operation/Operation.tsx
@@ -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 {
{summary} {deprecated && Deprecated }
{options.pathInMiddlePanel && }
- {description !== undefined && }
+ {description !== undefined && }
{externalDocs && (
diff --git a/src/services/MarkdownRenderer.ts b/src/services/MarkdownRenderer.ts
index ab2835c6..95afec75 100644
--- a/src/services/MarkdownRenderer.ts
+++ b/src/services/MarkdownRenderer.ts
@@ -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,
);
diff --git a/src/services/OpenAPIParser.ts b/src/services/OpenAPIParser.ts
index 8037705a..05cdf77d 100644
--- a/src/services/OpenAPIParser.ts
+++ b/src/services/OpenAPIParser.ts
@@ -76,11 +76,11 @@ export class OpenAPIParser {
const description = spec.info.description || '';
const legacySecurityRegexp = new RegExp(
COMPONENT_REGEXP.replace('{component}', ''),
- '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');
diff --git a/src/services/__tests__/models/ApiInfo.test.ts b/src/services/__tests__/models/ApiInfo.test.ts
new file mode 100644
index 00000000..5adb6036
--- /dev/null
+++ b/src/services/__tests__/models/ApiInfo.test.ts
@@ -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');
+ });
+ });
+});
diff --git a/src/services/__tests__/models/Response.test.ts b/src/services/__tests__/models/Response.test.ts
index 77b861bb..bfd2f6f9 100644
--- a/src/services/__tests__/models/Response.test.ts
+++ b/src/services/__tests__/models/Response.test.ts
@@ -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');
});
});
diff --git a/src/services/models/ApiInfo.ts b/src/services/models/ApiInfo.ts
index 29455012..ceb38d00 100644
--- a/src/services/models/ApiInfo.ts
+++ b/src/services/models/ApiInfo.ts
@@ -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 {
diff --git a/src/services/models/Group.model.ts b/src/services/models/Group.model.ts
index 52d9108b..34f2b73c 100644
--- a/src/services/models/Group.model.ts
+++ b/src/services/models/Group.model.ts
@@ -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;
diff --git a/src/services/models/Schema.ts b/src/services/models/Schema.ts
index 0a5a9319..576a7475 100644
--- a/src/services/models/Schema.ts
+++ b/src/services/models/Schema.ts
@@ -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',
diff --git a/src/utils/__tests__/openapi.test.ts b/src/utils/__tests__/openapi.test.ts
index 9e61c908..34e847d2 100644
--- a/src/utils/__tests__/openapi.test.ts
+++ b/src/utils/__tests__/openapi.test.ts
@@ -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', () => {
diff --git a/src/utils/openapi.ts b/src/utils/openapi.ts
index 2aecb645..080d010f 100644
--- a/src/utils/openapi.ts
+++ b/src/utils/openapi.ts
@@ -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;
}