From d2c790fa143f3ae9e762e543ba84001a15921202 Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Mon, 8 May 2017 14:11:38 +0300 Subject: [PATCH 1/8] chore: remove unused package --- package.json | 1 - yarn.lock | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/package.json b/package.json index 88671e68..79a64785 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "codelyzer": "^3.0.1", "conventional-changelog-cli": "^1.3.1", "core-js": "^2.4.1", - "coveralls": "^2.13.1", "css-loader": "^0.28.1", "deploy-to-gh-pages": "^1.3.3", "dropkickjs": "^2.1.10", diff --git a/yarn.lock b/yarn.lock index 31414201..2b1a1555 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1117,16 +1117,6 @@ corser@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/corser/-/corser-2.0.1.tgz#8eda252ecaab5840dcd975ceb90d9370c819ff87" -coveralls@^2.13.1: - version "2.13.1" - resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.1.tgz#d70bb9acc1835ec4f063ff9dac5423c17b11f178" - dependencies: - js-yaml "3.6.1" - lcov-parse "0.0.10" - log-driver "1.2.5" - minimist "1.2.0" - request "2.79.0" - coveralls@~2.11.2: version "2.11.16" resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.11.16.tgz#da9061265142ddee954f68379122be97be8ab4b1" From cb106cc6687fd863f9653f68bed3725e2853ac08 Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Fri, 12 May 2017 11:03:14 +0300 Subject: [PATCH 2/8] feat: display xml examples if present in response examples --- demo/swagger.yaml | 1 + .../RequestSamples/request-samples.scss | 2 +- .../ResponsesSamples/responses-samples.ts | 4 +- .../SchemaSample/schema-sample.html | 34 ++++++++++---- .../SchemaSample/schema-sample.scss | 30 ++++++++++++- lib/components/SchemaSample/schema-sample.ts | 12 +++-- lib/utils/helpers.ts | 20 +++++++-- lib/vendor.ts | 1 + tests/unit/helpers.spec.ts | 45 +++++++++++++++---- 9 files changed, 121 insertions(+), 28 deletions(-) diff --git a/demo/swagger.yaml b/demo/swagger.yaml index f21ecef5..63a0bcbb 100644 --- a/demo/swagger.yaml +++ b/demo/swagger.yaml @@ -617,6 +617,7 @@ paths: type: string examples: application/json: OK + application/xml: OK headers: X-Rate-Limit: type: integer diff --git a/lib/components/RequestSamples/request-samples.scss b/lib/components/RequestSamples/request-samples.scss index 24f42018..cc06902c 100644 --- a/lib/components/RequestSamples/request-samples.scss +++ b/lib/components/RequestSamples/request-samples.scss @@ -64,7 +64,7 @@ header { } :host /deep/ tabs ul { - padding-top: 10px; + padding-top: 10px; } .code-sample pre { diff --git a/lib/components/ResponsesSamples/responses-samples.ts b/lib/components/ResponsesSamples/responses-samples.ts index 6e076adb..e977b275 100644 --- a/lib/components/ResponsesSamples/responses-samples.ts +++ b/lib/components/ResponsesSamples/responses-samples.ts @@ -3,7 +3,7 @@ import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { BaseComponent, SpecManager } from '../base'; import JsonPointer from '../../utils/JsonPointer'; -import { statusCodeType, getJsonLike } from '../../utils/helpers'; +import { statusCodeType, getJsonLikeSample } from '../../utils/helpers'; function isNumeric(n) { @@ -11,7 +11,7 @@ function isNumeric(n) { } function hasExample(response) { - return ((response.examples && getJsonLike(response.examples)) || + return ((response.examples && getJsonLikeSample(response.examples)) || response.schema); } diff --git a/lib/components/SchemaSample/schema-sample.html b/lib/components/SchemaSample/schema-sample.html index a1a3ec8a..b7791525 100644 --- a/lib/components/SchemaSample/schema-sample.html +++ b/lib/components/SchemaSample/schema-sample.html @@ -1,10 +1,26 @@ -
- -
 Sample unavailable 
-
- Copy - Expand all - Collapse all + +
+ +
 Sample unavailable 
+ +

   
-

-
+ + + + + + + +
+
+ Copy +
+

+    
+
+
diff --git a/lib/components/SchemaSample/schema-sample.scss b/lib/components/SchemaSample/schema-sample.scss index 45fdf161..b85f561c 100644 --- a/lib/components/SchemaSample/schema-sample.scss +++ b/lib/components/SchemaSample/schema-sample.scss @@ -4,6 +4,34 @@ display: block; } + +/* tabs */ + +:host /deep/ tabs { + margin-top: 1em; + > ul { + margin: 0; + padding: 0; + + > li { + padding: 2px 10px; + display: inline-block; + background: #131a1d; + border-bottom: 1px solid trasparent; + color: $sample-panel-headers-color; + + &.active { + color: white; + border-bottom: 1px solid $sample-panel-headers-color; + } + } + } + + .action-buttons { + margin-top: -2em; + } +} + pre { background-color: transparent; padding: 0; @@ -99,7 +127,7 @@ pre { padding-left: 6px; } - .redoc-json { + .redoc-json, .xml-sample { overflow-x: auto; padding: 20px; border-radius: $border-radius*2; diff --git a/lib/components/SchemaSample/schema-sample.ts b/lib/components/SchemaSample/schema-sample.ts index a774bd91..00fa9558 100644 --- a/lib/components/SchemaSample/schema-sample.ts +++ b/lib/components/SchemaSample/schema-sample.ts @@ -6,7 +6,7 @@ import * as OpenAPISampler from 'openapi-sampler'; import JsonPointer from '../../utils/JsonPointer'; import { BaseComponent, SpecManager } from '../base'; import { SchemaNormalizer } from '../../services/schema-normalizer.service'; -import { getJsonLike } from '../../utils/helpers'; +import { getJsonLikeSample, getXmlLikeSample} from '../../utils/helpers'; @Component({ selector: 'schema-sample', @@ -20,6 +20,7 @@ export class SchemaSample extends BaseComponent implements OnInit { element: any; sample: any; + xmlSample: string; enableButtons: boolean = false; private _normalizer:SchemaNormalizer; @@ -34,7 +35,7 @@ export class SchemaSample extends BaseComponent implements OnInit { this.bindEvents(); let base:any = this.componentSchema; - let sample; + let sample, xmlSample; // got pointer not directly to the schema but e.g. to the response obj if (this.componentSchema.schema) { @@ -50,7 +51,12 @@ export class SchemaSample extends BaseComponent implements OnInit { base.examples = requestExamples; } - let jsonLikeSample = base.examples && getJsonLike(base.examples); + let xmlLikeSample = base.examples && getXmlLikeSample(base.examples); + if (xmlLikeSample) { + this.xmlSample = xmlLikeSample; + } + + let jsonLikeSample = base.examples && getJsonLikeSample(base.examples); if (jsonLikeSample) { sample = jsonLikeSample; } else { diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index da30e1fc..1f7cc1b0 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -123,12 +123,26 @@ export function isJsonLike(contentType: string): boolean { return contentType.search(/json/i) !== -1; } -export function getJsonLike(object: Object) { - const jsonLikeKeys = Object.keys(object).filter(isJsonLike); +export function isXmlLike(contentType: string): boolean { + return contentType.search(/xml/i) !== -1; +} + +export function getJsonLikeSample(samples: Object) { + const jsonLikeKeys = Object.keys(samples).filter(isJsonLike); if (!jsonLikeKeys.length) { return false; } - return object[jsonLikeKeys.shift()]; + return samples[jsonLikeKeys[0]]; +} + +export function getXmlLikeSample(samples: Object) { + const xmlLikeKeys = Object.keys(samples).filter(isXmlLike); + + if (!xmlLikeKeys.length) { + return false; + } + + return samples[xmlLikeKeys[0]]; } diff --git a/lib/vendor.ts b/lib/vendor.ts index e6d13941..edbd8669 100644 --- a/lib/vendor.ts +++ b/lib/vendor.ts @@ -18,6 +18,7 @@ import 'prismjs/components/prism-bash.js'; import 'prismjs/components/prism-swift.js'; import 'prismjs/components/prism-objectivec.js'; import 'prismjs/components/prism-scala.js'; +import 'prismjs/components/prism-markup.js'; // xml import 'dropkickjs/build/css/dropkick.css'; import 'prismjs/themes/prism-dark.css'; diff --git a/tests/unit/helpers.spec.ts b/tests/unit/helpers.spec.ts index 3cba8224..d6dbcdbd 100644 --- a/tests/unit/helpers.spec.ts +++ b/tests/unit/helpers.spec.ts @@ -1,6 +1,12 @@ 'use strict'; -import {statusCodeType, isJsonLike, getJsonLike } from '../../lib/utils/helpers'; +import { + statusCodeType, + isJsonLike, + getJsonLikeSample, + isXmlLike, + getXmlLikeSample +} from '../../lib/utils/helpers'; describe('Utils', () => { describe('statusCodeType', () => { @@ -41,24 +47,45 @@ describe('Utils', () => { }); }); - describe('getJsonLike', () => { + describe('getJsonLikeSample', () => { it('Should return a value when a JSON-like key exists', () => { const examples = { - "application/vnd.api+json": { - "message": "Hello World" + 'application/vnd.api+json': { + 'message': 'Hello World' }, - "application/xml": "Hello World" + 'application/xml': 'Hello World' }; - (getJsonLike(examples).message).should.be.equal("Hello World"); + (getJsonLikeSample(examples).message).should.be.equal('Hello World'); }); it('Should return undefined when no JSON-like key exists', () => { const examples = { - "application/xml": "Hello World" + 'application/xml': 'Hello World' }; - getJsonLike(examples).should.be.equal(false); + getJsonLikeSample(examples).should.be.equal(false); }); - }) + }); + + describe('getXmlLikeSample', () => { + it('Should return a value when a XML-like key exists', () => { + const examples = { + 'application/vnd.api+json': { + 'message': 'Hello World' + }, + 'application/vnd.api+xml': 'Hello World' + }; + + (getXmlLikeSample(examples)).should.be.equal('Hello World'); + }); + + it('Should return undefined when no XML-like key exists', () => { + const examples = { + 'application/json': 'Hello World' + }; + + getXmlLikeSample(examples).should.be.equal(false); + }); + }); }); From 131b43747852290fb5780226d357a96c7068e7a6 Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Fri, 12 May 2017 11:21:47 +0300 Subject: [PATCH 3/8] fix: URL changes so fast fixes #252 --- lib/services/hash.service.ts | 12 +++++++++++- lib/utils/helpers.ts | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/services/hash.service.ts b/lib/services/hash.service.ts index 4cdb5964..df381559 100644 --- a/lib/services/hash.service.ts +++ b/lib/services/hash.service.ts @@ -1,15 +1,20 @@ 'use strict'; import { Injectable } from '@angular/core'; import { PlatformLocation } from '@angular/common'; - import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { debounce } from '../utils/'; + @Injectable() export class Hash { public value = new BehaviorSubject(null); private noEmit:boolean = false; + private debouncedUpdate: (hash:string, rewrite: boolean) => void; + constructor(private location: PlatformLocation) { this.bind(); + + this.debouncedUpdate = debounce(this._update.bind(this), 100); } start() { @@ -28,6 +33,10 @@ export class Hash { } update(hash: string|null, rewriteHistory:boolean = false) { + this.debouncedUpdate(hash, rewriteHistory); + } + + private _update(hash: string|null, rewriteHistory:boolean = false) { if (hash == undefined) return; if (rewriteHistory) { window.history.replaceState(null, '', window.location.href.split('#')[0] + '#' + hash); @@ -39,4 +48,5 @@ export class Hash { this.noEmit = false; }); } + } diff --git a/lib/utils/helpers.ts b/lib/utils/helpers.ts index 1f7cc1b0..9ba5da9c 100644 --- a/lib/utils/helpers.ts +++ b/lib/utils/helpers.ts @@ -99,6 +99,21 @@ export function throttle(fn, threshhold, scope) { }; } +export function debounce(func, wait, immediate = false) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +} + export const isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 || (function (p) { return p.toString() === '[object SafariRemoteNotification]'; })(!window['safari'] || safari.pushNotification); From 7a5d315e09680ea969b472207dee881fd21a4314 Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Fri, 12 May 2017 11:35:47 +0300 Subject: [PATCH 4/8] fix: do not show discriminator dropdown if it is empty --- lib/components/JsonSchema/json-schema.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/JsonSchema/json-schema.html b/lib/components/JsonSchema/json-schema.html index 8ab87420..07c7853b 100644 --- a/lib/components/JsonSchema/json-schema.html +++ b/lib/components/JsonSchema/json-schema.html @@ -83,7 +83,7 @@ {{prop.pattern}}
-
+
From c0698bb21569854a1a4c62f7b01edb8f69fc8fa1 Mon Sep 17 00:00:00 2001 From: Roman Hotsiy Date: Fri, 12 May 2017 12:38:05 +0300 Subject: [PATCH 5/8] fix: prevent possible xss using `untrusted-spec` option --- README.md | 1 + demo/index-gh.html | 2 +- demo/index.html | 2 +- lib/services/options.service.ts | 3 +++ lib/utils/pipes.ts | 12 +++++++----- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 068a26be..eb6f15b6 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ ReDoc makes use of the following [vendor extensions](http://swagger.io/specifica ### `` tag attributes * `spec-url` - relative or absolute url to your spec file; +* `untrusted-spec` - if set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. **Disabled by default** for performance reasons. **Enable this option if you work with untrusted user data!** * `scroll-y-offset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc; `scroll-y-offset` can be specified in various ways: * **number**: A fixed number of pixels to be used as offset; diff --git a/demo/index-gh.html b/demo/index-gh.html index 17724a03..5835dfc9 100644 --- a/demo/index-gh.html +++ b/demo/index-gh.html @@ -22,7 +22,7 @@ frameborder="0" scrolling="0" width="130px" height="30px"> - + diff --git a/demo/index.html b/demo/index.html index ee983bf4..8ef1e02a 100644 --- a/demo/index.html +++ b/demo/index.html @@ -22,7 +22,7 @@ frameborder="0" scrolling="0" width="130px" height="30px"> - +