Merge commit '111bcdb0b9e97d4ec7fe33b2dbb418b226759288' into releases

This commit is contained in:
RedocBot 2017-08-28 08:20:42 +00:00 committed by travis@localhost
commit 57c50ddd16
22 changed files with 145 additions and 73 deletions

30
.editorconfig Normal file
View File

@ -0,0 +1,30 @@
[*]
charset=utf-8
end_of_line=lf
insert_final_newline=false
indent_style=space
indent_size=2
[{.eslintrc,.babelrc,.stylelintrc,jest.config,*.json,*.jsb3,*.jsb2,*.bowerrc}]
indent_style=space
indent_size=2
[*.scss]
indent_style=space
indent_size=2
[*.styl]
indent_style=space
indent_size=2
[*.coffee]
indent_style=space
indent_size=2
[{.analysis_options,*.yml,*.yaml}]
indent_style=space
indent_size=2
[tslint.json]
indent_style=space
indent_size=2

View File

@ -1,3 +1,23 @@
<a name="1.18.0"></a>
# [1.18.0](https://github.com/Rebilly/ReDoc/compare/v1.16.1...v1.18.0) (2017-08-28)
### Bug Fixes
* increase padding top for `.api-info-wrapper` when left sidebar is hiding to avoid header overlaying by top menu ([514fc29](https://github.com/Rebilly/ReDoc/commit/514fc29))
* add `display: inline-block` for `.openapi-button` ([86b4db4](https://github.com/Rebilly/ReDoc/commit/86b4db4)),
closes [#321](https://github.com/Rebilly/ReDoc/issues/321)
* add margins around list-items in markdown ([b165785](https://github.com/Rebilly/ReDoc/commit/b165785))
### Features
* generate download link for specs defined by an object ([60e8cb4](https://github.com/Rebilly/ReDoc/commit/60e8cb4)), closes [#289](https://github.com/Rebilly/ReDoc/issues/289)
* support text-plain response sample ([b84177c](https://github.com/Rebilly/ReDoc/commit/b84177c)), closes [#270](https://github.com/Rebilly/ReDoc/issues/270)
* clickable logo that points to specific url ([cb3d318](https://github.com/Rebilly/ReDoc/commit/cb3d318)), closes
[#322](https://github.com/Rebilly/ReDoc/issues/322)
* support x-example for parameters ([f792273](https://github.com/Rebilly/ReDoc/commit/f792273)), closes
[#297](https://github.com/Rebilly/ReDoc/issues/297)
<a name="1.17.0"></a> <a name="1.17.0"></a>
# [1.17.0](https://github.com/Rebilly/ReDoc/compare/v1.16.1...v1.17.0) (2017-08-02) # [1.17.0](https://github.com/Rebilly/ReDoc/compare/v1.16.1...v1.17.0) (2017-08-02)

View File

@ -1,4 +1,5 @@
# ReDoc <img alt="ReDoc logo" src="/docs/images/redoc-logo.png" width="400px" />
**OpenAPI/Swagger-generated API Reference Documentation** **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) [![Tested on APIs.guru](http://api.apis.guru/badges/tested_on.svg)](https://APIs.guru) [![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) [![Stories in Ready](https://badge.waffle.io/Rebilly/ReDoc.png?label=ready&title=Ready)](https://waffle.io/Rebilly/ReDoc) [![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) [![Tested on APIs.guru](http://api.apis.guru/badges/tested_on.svg)](https://APIs.guru) [![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) [![Stories in Ready](https://badge.waffle.io/Rebilly/ReDoc.png?label=ready&title=Ready)](https://waffle.io/Rebilly/ReDoc)

View File

@ -242,6 +242,7 @@ paths:
in: header in: header
required: false required: false
type: string type: string
x-example: Bearer <TOKEN>
- name: petId - name: petId
in: path in: path
description: Pet id to delete description: Pet id to delete
@ -619,6 +620,7 @@ paths:
examples: examples:
application/json: OK application/json: OK
application/xml: <message> OK </message> application/xml: <message> OK </message>
text/plain: OK
headers: headers:
X-Rate-Limit: X-Rate-Limit:
type: integer type: integer

BIN
docs/images/redoc-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@ -2,7 +2,7 @@
<h1>{{info.title}} <span class="api-info-version">({{info.version}})</span></h1> <h1>{{info.title}} <span class="api-info-version">({{info.version}})</span></h1>
<p class="download-openapi" *ngIf="specUrl"> <p class="download-openapi" *ngIf="specUrl">
Download OpenAPI specification: Download OpenAPI specification:
<a class="openapi-button" download target="_blank" attr.href='{{specUrl}}'> Download </a> <a class="openapi-button" [attr.download]="downloadFilename" target="_blank" [attr.href]="specUrl"> Download </a>
</p> </p>
<p> <p>
<!-- TODO: create separate components for contact and license ? --> <!-- TODO: create separate components for contact and license ? -->

View File

@ -9,6 +9,10 @@
@media (max-width: $right-panel-squash-breakpoint) { @media (max-width: $right-panel-squash-breakpoint) {
width: 100%; width: 100%;
} }
@media (max-width: $side-menu-mobile-breakpoint) {
padding-top: $section-spacing + 20px;
}
} }
.openapi-button { .openapi-button {
@ -17,6 +21,7 @@
font-weight: normal; font-weight: normal;
margin-left: 0.5em; margin-left: 0.5em;
padding: 3px 8px 4px; padding: 3px 8px 4px;
display: inline-block;
} }
:host /deep/ [section] { :host /deep/ [section] {

View File

@ -1,5 +1,6 @@
'use strict'; 'use strict';
import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core'; import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { SpecManager, BaseComponent } from '../base'; import { SpecManager, BaseComponent } from '../base';
import { OptionsService, Marker } from '../../services/index'; import { OptionsService, Marker } from '../../services/index';
@ -11,11 +12,13 @@ import { OptionsService, Marker } from '../../services/index';
}) })
export class ApiInfo extends BaseComponent implements OnInit { export class ApiInfo extends BaseComponent implements OnInit {
info: any = {}; info: any = {};
specUrl: String; specUrl: String | SafeResourceUrl;
downloadFilename = '';
constructor(specMgr: SpecManager, constructor(specMgr: SpecManager,
private optionsService: OptionsService, private optionsService: OptionsService,
elRef: ElementRef, elRef: ElementRef,
marker: Marker marker: Marker,
private sanitizer: DomSanitizer
) { ) {
super(specMgr); super(specMgr);
marker.addElement(elRef.nativeElement); marker.addElement(elRef.nativeElement);
@ -24,6 +27,12 @@ export class ApiInfo extends BaseComponent implements OnInit {
init() { init() {
this.info = this.componentSchema.info; this.info = this.componentSchema.info;
this.specUrl = this.specMgr.specUrl; this.specUrl = this.specMgr.specUrl;
if (!this.specUrl && window.Blob && window.URL) {
const blob = new Blob([JSON.stringify(this.specMgr.rawSpec, null, 2)], {type : 'application/json'});
this.specUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
this.downloadFilename = 'swagger.json';
}
if (!isNaN(parseInt(this.info.version.toString().substring(0, 1)))) { if (!isNaN(parseInt(this.info.version.toString().substring(0, 1)))) {
this.info.version = 'v' + this.info.version; this.info.version = 'v' + this.info.version;
} }

View File

@ -1 +1,4 @@
<img *ngIf="logo.imgUrl" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}"> <a *ngIf="logo.url" href="{{logo.url}}">
<img *ngIf="logo.imgUrl" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">
</a>
<img *ngIf="logo.imgUrl && !logo.url" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">

View File

@ -17,6 +17,9 @@ export class ApiLogo extends BaseComponent implements OnInit {
init() { init() {
let logoInfo = this.componentSchema.info['x-logo']; let logoInfo = this.componentSchema.info['x-logo'];
if ('url' in this.componentSchema.info['contact']) {
this.logo.url = this.componentSchema.info['contact']['url'];
}
if (!logoInfo) return; if (!logoInfo) return;
this.logo.imgUrl = logoInfo.url; this.logo.imgUrl = logoInfo.url;
this.logo.bgColor = logoInfo.backgroundColor || 'transparent'; this.logo.bgColor = logoInfo.backgroundColor || 'transparent';

View File

@ -64,7 +64,8 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
font-weight: bold; font-weight: bold;
} }
.param-type, .param-array-format { .param-type,
.param-array-format {
vertical-align: middle; vertical-align: middle;
line-height: $param-name-height; line-height: $param-name-height;
color: rgba($black, 0.4); color: rgba($black, 0.4);
@ -73,7 +74,8 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
.param-type { .param-type {
font-weight: normal; font-weight: normal;
word-break: break-all; word-break: break-all;
&.array::before, &.tuple::before { &.array::before,
&.tuple::before {
color: $black; color: $black;
font-weight: $base-font-weight; font-weight: $base-font-weight;
.param-collection-format-multi + & { .param-collection-format-multi + & {
@ -144,7 +146,6 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
} }
.param:first-of-type { .param:first-of-type {
> .param-name::before { > .param-name::before {
content: ''; content: '';
display: block; display: block;
@ -158,7 +159,6 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
.param:last-of-type, .param:last-of-type,
.param.last { .param.last {
> .param-name { > .param-name {
position: relative; position: relative;
@ -236,14 +236,24 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
} }
} }
.param-example {
font-size: 0.95em;
&::before {
content: 'Example: ';
}
}
.param-enum-value, .param-enum-value,
.param-default-value { .param-default-value,
background-color: $background-color; .param-example-value {
border: 1px solid rgba($secondary-color, 0.2); font-family: Courier, monospace;
background-color: rgba($secondary-color, 0.02);
border: 1px solid rgba($secondary-color, 0.1);
margin: 2px 3px; margin: 2px 3px;
padding: 0 5px; padding: 0.1em 0.2em 0.2em;
border-radius: 2px; border-radius: $border-radius;
color: $secondary-color; color: $text-color;
display: inline-block; display: inline-block;
min-width: 20px; min-width: 20px;
text-align: center; text-align: center;

View File

@ -22,6 +22,9 @@
<div class="param-default" *ngIf="param.default != null"> <div class="param-default" *ngIf="param.default != null">
<span class="param-default-value">{{param.default | json}}</span> <span class="param-default-value">{{param.default | json}}</span>
</div> </div>
<div class="param-example" *ngIf="param.example != null">
<span class="param-example-value">{{param.example | json}}</span>
</div>
<div *ngIf="param.enum || param._enumItem" class="param-enum"> <div *ngIf="param.enum || param._enumItem" class="param-enum">
<span *ngFor="let enumItem of param.enum" class="param-enum-value {{enumItem.type}}"> <span *ngFor="let enumItem of param.enum" class="param-enum-value {{enumItem.type}}">
{{enumItem.val | json}} {{enumItem.val | json}}

View File

@ -275,16 +275,11 @@ footer {
code { code {
font-family: Courier, monospace; font-family: Courier, monospace;
background-color: rgba(38,50,56,0.04); background-color: rgba(38,50,56,0.04);
padding: 0.1em 0 0.2em 0; padding: 0.1em 0.2em 0.2em;
font-size: 1em; font-size: 1em;
border-radius: $border-radius; border-radius: $border-radius;
color: $red; color: $red;
border: 1px solid rgba(38,50,56,0.1); border: 1px solid rgba(38,50,56, 0.1);
&:before, &:after {
letter-spacing: -0.2em;
//content: "\00a0";
}
} }
p:last-of-type { p:last-of-type {
@ -311,6 +306,9 @@ footer {
font-family: $base-font, $base-font-family; font-family: $base-font, $base-font-family;
font-weight: $base-font-weight; font-weight: $base-font-weight;
line-height: $base-line-height; line-height: $base-line-height;
> li {
margin: 1em 0;
}
} }
table { table {

View File

@ -20,7 +20,15 @@
<div class="action-buttons"> <div class="action-buttons">
<span copy-button [copyText]="xmlSample" class="hint--top-left hint--inversed"> <a>Copy</a> </span> <span copy-button [copyText]="xmlSample" class="hint--top-left hint--inversed"> <a>Copy</a> </span>
</div> </div>
<pre class="xml-sample" [innerHtml]="xmlSample | prism:'xml'"></pre> <pre class="response-sample" [innerHtml]="xmlSample | prism:'xml'"></pre>
</div>
</tab>
<tab tabTitle="text/plain" *ngIf="textSample">
<div class="snippet">
<div class="action-buttons">
<span copy-button [copyText]="xmlSample" class="hint--top-left hint--inversed"> <a>Copy</a> </span>
</div>
<pre class="response-sample">{{textSample}}</pre>
</div> </div>
</tab> </tab>
</tabs> </tabs>

View File

@ -131,7 +131,7 @@ pre {
padding-left: 6px; padding-left: 6px;
} }
.redoc-json, .xml-sample { .redoc-json, .response-sample {
overflow-x: auto; overflow-x: auto;
padding: 20px; padding: 20px;
border-radius: $border-radius*2; border-radius: $border-radius*2;

View File

@ -6,7 +6,7 @@ import * as OpenAPISampler from 'openapi-sampler';
import JsonPointer from '../../utils/JsonPointer'; import JsonPointer from '../../utils/JsonPointer';
import { BaseComponent, SpecManager } from '../base'; import { BaseComponent, SpecManager } from '../base';
import { SchemaNormalizer } from '../../services/schema-normalizer.service'; import { SchemaNormalizer } from '../../services/schema-normalizer.service';
import { getJsonLikeSample, getXmlLikeSample} from '../../utils/helpers'; import { getJsonLikeSample, getXmlLikeSample, getTextLikeSample } from '../../utils/helpers';
@Component({ @Component({
selector: 'schema-sample', selector: 'schema-sample',
@ -21,6 +21,7 @@ export class SchemaSample extends BaseComponent implements OnInit {
element: any; element: any;
sample: any; sample: any;
xmlSample: string; xmlSample: string;
textSample: string;
enableButtons: boolean = false; enableButtons: boolean = false;
private _normalizer:SchemaNormalizer; private _normalizer:SchemaNormalizer;
@ -51,10 +52,8 @@ export class SchemaSample extends BaseComponent implements OnInit {
base.examples = requestExamples; base.examples = requestExamples;
} }
let xmlLikeSample = base.examples && getXmlLikeSample(base.examples); this.xmlSample = base.examples && getXmlLikeSample(base.examples);
if (xmlLikeSample) { this.textSample = base.examples && getTextLikeSample(base.examples);
this.xmlSample = xmlLikeSample;
}
let jsonLikeSample = base.examples && getJsonLikeSample(base.examples); let jsonLikeSample = base.examples && getJsonLikeSample(base.examples);
if (jsonLikeSample) { if (jsonLikeSample) {

View File

@ -119,6 +119,9 @@ const injectors = {
injectTo._displayType = propertySchema.title ? injectTo._displayType = propertySchema.title ?
`${propertySchema.title} (${propertySchema.type})` : propertySchema.type; `${propertySchema.title} (${propertySchema.type})` : propertySchema.type;
} }
if (injectTo['x-example'] && !propertySchema.example) {
injectTo.example = propertySchema['x-example'];
}
injectTo._widgetType = 'trivial'; injectTo._widgetType = 'trivial';
} }
}, },

View File

@ -142,6 +142,10 @@ export function isXmlLike(contentType: string): boolean {
return contentType.search(/xml/i) !== -1; return contentType.search(/xml/i) !== -1;
} }
export function isTextLike(contentType: string): boolean {
return contentType.search(/text\/plain/i) !== -1;
}
export function getJsonLikeSample(samples: Object = {}) { export function getJsonLikeSample(samples: Object = {}) {
const jsonLikeKeys = Object.keys(samples).filter(isJsonLike); const jsonLikeKeys = Object.keys(samples).filter(isJsonLike);
@ -161,3 +165,14 @@ export function getXmlLikeSample(samples: Object = {}) {
return samples[xmlLikeKeys[0]]; return samples[xmlLikeKeys[0]];
} }
export function getTextLikeSample(samples: Object = {}) {
const textLikeKeys = Object.keys(samples).filter(isTextLike);
if (!textLikeKeys.length) {
return false;
}
return samples[textLikeKeys[0]];
}

View File

@ -35,17 +35,6 @@ export class KeysPipe implements PipeTransform {
} }
} }
@Pipe({ name: 'jsonPointerEscape' })
export class JsonPointerEscapePipe implements PipeTransform {
transform(value:string) {
if (isBlank(value)) return value;
if (!isString(value)) {
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
}
return JsonPointer.escape(value);
}
}
@Pipe({ name: 'marked' }) @Pipe({ name: 'marked' })
export class MarkedPipe implements PipeTransform { export class MarkedPipe implements PipeTransform {
renderer: MdRenderer; renderer: MdRenderer;
@ -58,7 +47,7 @@ export class MarkedPipe implements PipeTransform {
transform(value:string) { transform(value:string) {
if (isBlank(value)) return value; if (isBlank(value)) return value;
if (!isString(value)) { if (!isString(value)) {
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value); throw new InvalidPipeArgumentException(MarkedPipe, value);
} }
let res = `<span class="redoc-markdown-block">${this.renderer.renderMd(value)}</span>`; let res = `<span class="redoc-markdown-block">${this.renderer.renderMd(value)}</span>`;
return this.unstrustedSpec ? res : this.sanitizer.bypassSecurityTrustHtml(res); return this.unstrustedSpec ? res : this.sanitizer.bypassSecurityTrustHtml(res);
@ -95,7 +84,7 @@ export class PrismPipe implements PipeTransform {
} }
if (isBlank(value)) return value; if (isBlank(value)) return value;
if (!isString(value)) { if (!isString(value)) {
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value); throw new InvalidPipeArgumentException(PrismPipe, value);
} }
let lang = args[0].toString().trim().toLowerCase(); let lang = args[0].toString().trim().toLowerCase();
if (langMap[lang]) lang = langMap[lang]; if (langMap[lang]) lang = langMap[lang];
@ -138,5 +127,5 @@ export class CollectionFormatPipe implements PipeTransform {
} }
export const REDOC_PIPES = [ export const REDOC_PIPES = [
JsonPointerEscapePipe, MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter, KeysPipe, CollectionFormatPipe MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter, KeysPipe, CollectionFormatPipe
]; ];

View File

@ -26,6 +26,7 @@ export interface DescendantInfo {
@Injectable() @Injectable()
export class SpecManager { export class SpecManager {
public _schema: any = {}; public _schema: any = {};
public rawSpec: any;
public apiUrl: string; public apiUrl: string;
public apiProtocol: string; public apiProtocol: string;
public swagger: string; public swagger: string;
@ -48,6 +49,7 @@ export class SpecManager {
if (typeof urlOrObject === 'string') { if (typeof urlOrObject === 'string') {
this.specUrl = urlOrObject; this.specUrl = urlOrObject;
} }
this.rawSpec = schema;
this._schema = snapshot(schema); this._schema = snapshot(schema);
try { try {
this.init(); this.init();

View File

@ -1,7 +1,7 @@
{ {
"name": "redoc", "name": "redoc",
"description": "Swagger-generated API Reference Documentation", "description": "Swagger-generated API Reference Documentation",
"version": "1.17.0", "version": "1.18.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/Rebilly/ReDoc" "url": "git://github.com/Rebilly/ReDoc"

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
import {KeysPipe, JsonPointerEscapePipe, MarkedPipe} from '../../lib/utils/pipes'; import {KeysPipe, MarkedPipe} from '../../lib/utils/pipes';
describe('Pipes', () => { describe('Pipes', () => {
describe('KeysPipe', () => { describe('KeysPipe', () => {
@ -33,34 +33,6 @@ describe('Pipes', () => {
}); });
}); });
describe('JsonPointerEscapePipe', () => {
let unescaped;
let escaped;
var pipe;
beforeEach(() => {
unescaped = 'test/path~1';
escaped = 'test~1path~01';
pipe = new JsonPointerEscapePipe();
});
describe('JsonPointerEscapePipe transform', () => {
it('should escpae pointer', () => {
var val = pipe.transform(unescaped);
val.should.be.equal(escaped);
});
it('should not support other objects', () => {
(() => pipe.transform(45)).should.throw();
(() => pipe.transform({})).should.throw();
});
it('should not throw on blank input', () => {
(() => pipe.transform()).should.not.throw();
});
});
});
describe('MarkedPipe', () => { describe('MarkedPipe', () => {
let unmarked; let unmarked;
let marked; let marked;