Merge commit '942cd5dc53534c6ff952872ace41ce632888a4f4' into releases

This commit is contained in:
RedocBot 2017-02-22 16:02:31 +00:00 committed by travis@localhost
commit d97374d3c4
20 changed files with 443 additions and 287 deletions

View File

@ -1,3 +1,16 @@
# 1.8.0 (2017-02-03)
### Features/Improvements
* In-page search :tada: []#51](https://github.com/Rebilly/ReDoc/issues/51)
* Render externalDocs [#103](https://github.com/Rebilly/ReDoc/issues/103)
* Undeprecate x-traitTag
### Bug fixes
* Tags with x-traitTag: true are now greyed out in ReDoc output bug [#194](https://github.com/Rebilly/ReDoc/issues/194)
* CSS: request body model-tree wrapping problem [#185](https://github.com/Rebilly/ReDoc/issues/185)
* Strange request to `example.com` causing CSP error [#178](https://github.com/Rebilly/ReDoc/issues/178)
* Fix latest empty menu-items not getting active [#194](https://github.com/Rebilly/ReDoc/issues/194)
* Fixed crash when level-2 heading goes before level-1 in description [#179](https://github.com/Rebilly/ReDoc/issues/179) (by [@jsmartfo](https://github.com/jsmartfo))
# 1.7.0 (2017-01-06) # 1.7.0 (2017-01-06)
### Features/Improvements ### Features/Improvements
* Add support for grouping items in menu via [`x-tagGroups`](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-taggroups) * Add support for grouping items in menu via [`x-tagGroups`](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md#x-taggroups)

View File

@ -31,8 +31,8 @@
- Multiple ReDoc instances on single page ([example](demo/examples/multiple-apis/index.html)) - Multiple ReDoc instances on single page ([example](demo/examples/multiple-apis/index.html))
## Roadmap ## Roadmap
- [x] performance optimizations - [x] ~~performance optimizations~~
- [ ] better navigation (menu improvements + search) - [x] ~~better navigation (menu improvements + search)~~
- [ ] ability to simple branding/styling - [ ] ability to simple branding/styling
- [ ] built-in API Console - [ ] built-in API Console
- [ ] docs pre-rendering (performance and SEO) - [ ] docs pre-rendering (performance and SEO)
@ -43,6 +43,14 @@ We host the latest and all the previous ReDoc releases on GitHub Pages-based **C
- `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js - `v1.x.x` release: https://rebilly.github.io/ReDoc/releases/v1.x.x/redoc.min.js
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js this file is updated with each release of ReDoc and may introduce breaking changes. **Not recommended to use in production.** Use particular release or `v1.x.x`. - `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js this file is updated with each release of ReDoc and may introduce breaking changes. **Not recommended to use in production.** Use particular release or `v1.x.x`.
## Some Real-life usages
- [Rebilly](https://rebilly.github.io/RebillyAPI)
- [Docker Engine](https://docs.docker.com/engine/api/v1.25/)
- [Zuora](https://www.zuora.com/developer/api-reference/)
- [Shopify Draft Orders](https://help.shopify.com/api/draft-orders)
- [Discourse](https://docs.discourse.org)
- [APIs.guru](https://apis.guru/api-doc/)
## Deployment ## Deployment
### TL;DR ### TL;DR

View File

@ -87,7 +87,11 @@ module.exports = function (options) {
}, },
{ {
test: /\.scss$/, test: /\.scss$/,
loaders: ['style-loader', 'css-loader?-import', "sass-loader"], use: [
'style-loader',
'css-loader?-import',
'sass-loader'
],
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.scss$/] exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.scss$/]
}, },
{ {

View File

@ -68,7 +68,7 @@
</td> </td>
<td class="param-info"> <td class="param-info">
<div> <div>
<span class="param-type {{prop.type}}" [ngClass]="{'with-hint': prop._displayTypeHint, 'tuple': prop._isTuple}" <span class="param-type {{prop.type}}" [ngClass]="{'with-hint': prop._displayTypeHint, 'tuple': prop._isTuple, 'array': prop._isArray}"
title="{{prop._displayTypeHint}}"> {{prop._displayType}} {{prop._displayFormat}} title="{{prop._displayTypeHint}}"> {{prop._displayType}} {{prop._displayFormat}}
<span class="param-range" *ngIf="prop._range"> {{prop._range}} </span> <span class="param-range" *ngIf="prop._range"> {{prop._range}} </span>
</span> </span>

View File

@ -93,6 +93,7 @@ zippy {
.param.complex > .param-name svg { .param.complex > .param-name svg {
height: 1.2em; height: 1.2em;
width: 1.2em;
vertical-align: middle; vertical-align: middle;
transition: all 0.3s ease; transition: all 0.3s ease;
} }

View File

@ -6,7 +6,7 @@ import { Input, HostBinding, Component, OnChanges } from '@angular/core';
template: ` template: `
<span [style.width]='progress + "%"'> </span> <span [style.width]='progress + "%"'> </span>
`, `,
styleUrls: ['loading-bar.scss'] styleUrls: ['loading-bar.css']
}) })
export class LoadingBar implements OnChanges { export class LoadingBar implements OnChanges {
@Input() progress:number = 0; @Input() progress:number = 0;

View File

@ -1,6 +1,6 @@
<header *ngIf="data.responses.length"> Response samples </header> <header *ngIf="data.responses.length"> Response samples </header>
<tabs *ngIf="data.responses.length"> <tabs *ngIf="data.responses.length">
<tab *ngFor="let response of data.responses" tabTitle="{{response.code}} {{response.description}}" <tab *ngFor="let response of data.responses" [tabTitle]="response.code + ' ' + response.description | marked"
[tabStatus]="response.type"> [tabStatus]="response.type">
<schema-sample [pointer]="response.pointer"></schema-sample> <schema-sample [pointer]="response.pointer"></schema-sample>
</tab> </tab>

View File

@ -51,8 +51,6 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy {
private $resourcesNav: any; private $resourcesNav: any;
private $scrollParent: any; private $scrollParent: any;
private firstChange = true;
constructor(specMgr:SpecManager, elementRef:ElementRef, constructor(specMgr:SpecManager, elementRef:ElementRef,
private scrollService:ScrollService, private menuService:MenuService, private scrollService:ScrollService, private menuService:MenuService,
optionsService:OptionsService, private detectorRef:ChangeDetectorRef, private marker:Marker) { optionsService:OptionsService, private detectorRef:ChangeDetectorRef, private marker:Marker) {
@ -84,15 +82,12 @@ export class SideMenu extends BaseComponent implements OnInit, OnDestroy {
//safari doesn't update bindings if not run changeDetector manually :( //safari doesn't update bindings if not run changeDetector manually :(
this.detectorRef.detectChanges(); this.detectorRef.detectChanges();
if (this.firstChange) { this.scrollActiveIntoView();
this.scrollActiveIntoView();
this.firstChange = false;
}
} }
scrollActiveIntoView() { scrollActiveIntoView() {
let $item = this.$element.querySelector('li.active, label.active'); let $item = this.$element.querySelector('li.active, label.active');
if ($item) $item.scrollIntoView(); if ($item) $item.scrollIntoViewIfNeeded();
} }
activateAndScroll(item) { activateAndScroll(item) {

View File

@ -28,3 +28,35 @@ if (!IS_PRODUCTION) {
Error.stackTraceLimit = Infinity; Error.stackTraceLimit = Infinity;
require('zone.js/dist/long-stack-trace-zone'); require('zone.js/dist/long-stack-trace-zone');
} }
interface Element {
scrollIntoViewIfNeeded(centerIfNeeded?: boolean): void;
};
if (!(<any>Element).prototype.scrollIntoViewIfNeeded) {
(<any>Element).prototype.scrollIntoViewIfNeeded = function (centerIfNeeded) {
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = this.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded) {
parent.scrollTop = this.offsetTop - parent.offsetTop - parent.clientHeight / 2 - parentBorderTopWidth + this.clientHeight / 2;
}
if ((overLeft || overRight) && centerIfNeeded) {
parent.scrollLeft = this.offsetLeft - parent.offsetLeft - parent.clientWidth / 2 - parentBorderLeftWidth + this.clientWidth / 2;
}
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded) {
this.scrollIntoView(alignWithTop);
}
};
}

View File

@ -17,4 +17,19 @@ describe('Spec Helper', () => {
(<jasmine.Spy>console.warn).and.callThrough(); (<jasmine.Spy>console.warn).and.callThrough();
}); });
}); });
describe('preprocessProperties', () => {
it('should not throw when type array and items are not defined', () => {
let schema = {
type: 'object',
properties: {
prop1: {
type: 'array'
}
}
};
(() => SchemaHelper.preprocessProperties(schema, '#/', {})).should.not.throw();
});
});
}); });

View File

@ -5,7 +5,7 @@ import { WarningsService } from './warnings.service';
import * as slugify from 'slugify'; import * as slugify from 'slugify';
interface PropertyPreprocessOptions { interface PropertyPreprocessOptions {
childFor: string; childFor?: string;
skipReadOnly?: boolean; skipReadOnly?: boolean;
discriminator?: string; discriminator?: string;
} }
@ -54,6 +54,7 @@ const injectors = {
return propertySchema.type === 'array' && !Array.isArray(propertySchema.items); return propertySchema.type === 'array' && !Array.isArray(propertySchema.items);
}, },
inject: (injectTo, propertySchema = injectTo, propPointer) => { inject: (injectTo, propertySchema = injectTo, propPointer) => {
if (!propertySchema.items) propertySchema.items = {};
if (!(SchemaHelper.detectType(propertySchema.items) === 'object')) { if (!(SchemaHelper.detectType(propertySchema.items) === 'object')) {
injectTo._isArray = true; injectTo._isArray = true;
injectTo._pointer = propertySchema.items._pointer injectTo._pointer = propertySchema.items._pointer
@ -63,6 +64,7 @@ const injectors = {
} else { } else {
injectors.object.inject(injectTo, propertySchema.items); injectors.object.inject(injectTo, propertySchema.items);
} }
if (!injectTo.description) injectTo.description = propertySchema.items.description;
injectTo._widgetType = 'array'; injectTo._widgetType = 'array';
} }
}, },
@ -207,7 +209,11 @@ export class SchemaHelper {
static preprocessProperties(schema:any, pointer:string, opts: PropertyPreprocessOptions) { static preprocessProperties(schema:any, pointer:string, opts: PropertyPreprocessOptions) {
let requiredMap = {}; let requiredMap = {};
if (schema.required) { if (schema.required) {
schema.required.forEach(prop => requiredMap[prop] = true); if (Array.isArray(schema.required)) {
schema.required.forEach(prop => requiredMap[prop] = true);
} else {
WarningsService.warn(`required must be an array: "${typeof schema.required}" found at ${pointer}`);
}
} }
let props = schema.properties && Object.keys(schema.properties).map(propName => { let props = schema.properties && Object.keys(schema.properties).map(propName => {

View File

@ -29,7 +29,7 @@ export class SchemaNormalizer {
if (opts.childFor) this._dereferencer.visit(opts.childFor); if (opts.childFor) this._dereferencer.visit(opts.childFor);
if (schema['x-redoc-normalized']) return schema; if (schema['x-redoc-normalized']) return schema;
let res = SchemaWalker.walk(schema, ptr, (subSchema, ptr) => { let res = SchemaWalker.walk(schema, ptr, (subSchema, ptr) => {
let resolved = this._dereferencer.dereference(subSchema, ptr); let resolved = this._dereferencer.dereference(subSchema, ptr);
if (resolved.allOf) { if (resolved.allOf) {
resolved._pointer = resolved._pointer || ptr; resolved._pointer = resolved._pointer || ptr;

View File

@ -1,5 +1,5 @@
<ul> <ul>
<li *ngFor="let tab of tabs" [ngClass]="{active: tab.active}" (click)="selectTab(tab)" <li *ngFor="let tab of tabs" [ngClass]="{active: tab.active}" (click)="selectTab(tab)"
class="tab-{{tab.tabStatus}}">{{tab.tabTitle}}</li> class="tab-{{tab.tabStatus}}" [innerHTML]="tab.tabTitle"></li>
</ul> </ul>
<ng-content></ng-content> <ng-content></ng-content>

View File

@ -16,6 +16,10 @@ li {
cursor: pointer; cursor: pointer;
} }
li /deep/ .redoc-markdown-block p {
display: inline;
}
.tab-success, .tab-error, .tab-redirect, .tab-info { .tab-success, .tab-error, .tab-redirect, .tab-info {
&:before { &:before {
content: ""; content: "";

View File

@ -53,6 +53,7 @@ $zippy-redirect-bg-color: rgba($zippy-redirect-color, .08);
.zippy-indicator svg { .zippy-indicator svg {
height: 1.2em; height: 1.2em;
width: 1.2em;
vertical-align: top; vertical-align: top;
transition: all 0.3s ease; transition: all 0.3s ease;
transform: rotateZ(-180deg); transform: rotateZ(-180deg);

View File

@ -8,6 +8,7 @@ import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { MdRenderer } from './md-renderer'; import { MdRenderer } from './md-renderer';
import { SwaggerOperation, SwaggerParameter } from './swagger-typings'; import { SwaggerOperation, SwaggerParameter } from './swagger-typings';
import { snapshot } from './helpers';
function getDiscriminator(obj) { function getDiscriminator(obj) {
return obj.discriminator || obj['x-extendedDiscriminator']; return obj.discriminator || obj['x-extendedDiscriminator'];
@ -37,7 +38,7 @@ export class SpecManager {
if (typeof urlOrObject === 'string') { if (typeof urlOrObject === 'string') {
this._url = urlOrObject; this._url = urlOrObject;
} }
this._schema = schema; this._schema = snapshot(schema);
try { try {
this.init(); this.init();
resolve(this._schema); resolve(this._schema);
@ -67,7 +68,7 @@ export class SpecManager {
} }
let host = this._schema.host || urlParts.host; let host = this._schema.host || urlParts.host;
this.basePath = this._schema.basePath || '/'; this.basePath = this._schema.basePath || '';
this.apiUrl = protocol + '://' + host + this.basePath; this.apiUrl = protocol + '://' + host + this.basePath;
if (this.apiUrl.endsWith('/')) { if (this.apiUrl.endsWith('/')) {
this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length - 1); this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length - 1);

View File

@ -1,7 +1,7 @@
{ {
"name": "redoc", "name": "redoc",
"description": "Swagger-generated API Reference Documentation", "description": "Swagger-generated API Reference Documentation",
"version": "1.8.0", "version": "1.8.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/Rebilly/ReDoc" "url": "git://github.com/Rebilly/ReDoc"
@ -47,32 +47,37 @@
"author": "Roman Hotsiy", "author": "Roman Hotsiy",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@angular/common": "^2.4.5", "@angular/common": "^2.4.8",
"@angular/compiler": "^2.4.5", "@angular/compiler": "^2.4.8",
"@angular/compiler-cli": "^2.4.5", "@angular/compiler-cli": "^2.4.8",
"@angular/core": "^2.4.5", "@angular/core": "^2.4.8",
"@angular/platform-browser": "^2.4.5", "@angular/platform-browser": "^2.4.8",
"@angular/platform-browser-dynamic": "^2.4.5", "@angular/platform-browser-dynamic": "^2.4.8",
"@angular/platform-server": "^2.4.5", "@angular/platform-server": "^2.4.8",
"@types/core-js": "^0.9.31", "@types/core-js": "^0.9.31",
"@types/jasmine": "^2.2.32", "@types/jasmine": "^2.5.43",
"@types/requirejs": "^2.1.26", "@types/requirejs": "^2.1.26",
"@types/should": "^8.1.28", "@types/should": "^8.1.28",
"@types/swagger-schema-official": "^2.0.0", "@types/swagger-schema-official": "^2.0.0",
"angular2-template-loader": "^0.6.0", "@types/webpack": "^2.2.6",
"awesome-typescript-loader": "~3.0.0-beta.17", "angular2-template-loader": "^0.6.2",
"awesome-typescript-loader": "^3.0.6",
"branch-release": "^1.0.3", "branch-release": "^1.0.3",
"chalk": "^1.1.3", "chalk": "^1.1.3",
"codelyzer": "^2.0.0-beta.4", "codelyzer": "^2.0.1",
"core-js": "^2.4.1", "core-js": "^2.4.1",
"coveralls": "^2.11.9", "coveralls": "^2.11.16",
"css-loader": "^0.26.0", "css-loader": "^0.26.0",
"deploy-to-gh-pages": "^1.1.2", "deploy-to-gh-pages": "^1.1.2",
"dropkickjs": "^2.1.10",
"hint.css": "^2.3.2",
"http-server": "^0.9.0", "http-server": "^0.9.0",
"istanbul-instrumenter-loader": "^1.2.0", "istanbul-instrumenter-loader": "^2.0.0",
"jasmine-core": "^2.4.1", "jasmine-core": "^2.4.1",
"jasmine-spec-reporter": "^3.1.0", "jasmine-spec-reporter": "^3.1.0",
"karma": "^1.4.1", "json-pointer": "^0.6.0",
"json-schema-ref-parser": "^3.1.2",
"karma": "^1.5.0",
"karma-chrome-launcher": "^2.0.0", "karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.1.1", "karma-coverage": "^1.1.1",
"karma-coveralls": "^1.1.2", "karma-coveralls": "^1.1.2",
@ -85,51 +90,35 @@
"karma-sinon": "^1.0.4", "karma-sinon": "^1.0.4",
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.1", "karma-webpack": "^2.0.1",
"lunr": "^1.0.0",
"mark.js": "github:julmot/mark.js",
"ngc-webpack": "^1.2.0", "ngc-webpack": "^1.2.0",
"node-sass": "^4.5.0", "node-sass": "^4.5.0",
"openapi-sampler": "^0.4.0",
"phantomjs-prebuilt": "^2.1.7", "phantomjs-prebuilt": "^2.1.7",
"protractor": "^5.1.0", "prismjs": "^1.5.1",
"protractor": "^5.1.1",
"raw-loader": "^0.5.1", "raw-loader": "^0.5.1",
"rimraf": "^2.5.4", "remarkable": "^1.6.2",
"rxjs": "^5.1.0", "rimraf": "^2.6.0",
"sass-loader": "^4.1.1", "rxjs": "^5.2.0",
"sass-loader": "^6.0.2",
"scrollparent": "^1.0.0",
"shelljs": "^0.7.0", "shelljs": "^0.7.0",
"should": "^11.1.0", "should": "^11.1.0",
"sinon": "^1.17.2", "sinon": "^1.17.2",
"slugify": "^1.0.2",
"source-map-loader": "^0.1.5", "source-map-loader": "^0.1.5",
"stream-http": "^2.6.1",
"string-replace-webpack-plugin": "0.0.5", "string-replace-webpack-plugin": "0.0.5",
"style-loader": "^0.13.1", "style-loader": "^0.13.1",
"ts-helpers": "^1.1.1", "ts-helpers": "^1.1.1",
"tslint": "^4.3.1", "tslint": "^4.3.1",
"typescript": "^2.1.5", "typescript": "^2.1.5",
"webpack": "^2.2.1", "webpack": "^2.2.1",
"webpack-dev-server": "^2.2.0-rc.0", "webpack-dev-server": "^2.4.1",
"webpack-merge": "^2.6.1", "webpack-merge": "^3.0.0",
"zone.js": "^0.7.2" "zone.js": "^0.7.7"
}, },
"dependencies": { "dependencies": {}
"dropkickjs": "^2.1.10",
"hint.css": "^2.3.2",
"json-pointer": "^0.6.0",
"json-schema-ref-parser": "^3.1.2",
"lunr": "^0.7.2",
"mark.js": "github:julmot/mark.js",
"openapi-sampler": "^0.3.3",
"prismjs": "^1.5.1",
"remarkable": "^1.6.2",
"scrollparent": "^1.0.0",
"slugify": "^1.0.2",
"stream-http": "^2.6.1"
},
"peerDependencies": {
"@angular/common": "^2.4.5",
"@angular/compiler": "^2.4.5",
"@angular/core": "^2.4.5",
"@angular/platform-browser": "^2.4.5",
"@angular/platform-browser-dynamic": "^2.4.5",
"@angular/platform-server": "^2.4.5",
"core-js": "^2.4.1",
"rxjs": "^5.0.1",
"zone.js": "^0.7.2"
}
} }

View File

@ -63,6 +63,13 @@ describe('Utils', () => {
specMgr.apiUrl.should.be.equal('http://petstore.swagger.io/v2'); specMgr.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
}); });
it('should use empty basePath when basePath is not present', () => {
specMgr._schema.basePath = undefined;
specMgr._url = 'https://petstore.swagger.io';
specMgr.init();
specMgr.basePath.should.be.equal('');
});
describe('byPointer method', () => { describe('byPointer method', () => {
it('should return correct schema part', ()=> { it('should return correct schema part', ()=> {
let part = specMgr.byPointer('/tags/0'); let part = specMgr.byPointer('/tags/0');

View File

@ -11,6 +11,7 @@
"noEmitHelpers": true, "noEmitHelpers": true,
"strictNullChecks": false, "strictNullChecks": false,
"baseUrl": "./src", "baseUrl": "./src",
"typeRoots": [ "./node_modules/@types" ],
"paths": { "paths": {
}, },
"lib": [ "lib": [
@ -18,7 +19,7 @@
"dom" "dom"
], ],
"types": [ "types": [
"node" "webpack"
] ]
}, },
"exclude": [ "exclude": [

519
yarn.lock

File diff suppressed because it is too large Load Diff