diff --git a/build/tasks/build.js b/build/tasks/build.js index c5cd692f..ec122a9e 100644 --- a/build/tasks/build.js +++ b/build/tasks/build.js @@ -50,34 +50,6 @@ gulp.task('tsc', function() { exec('tsc -p ./tsconfig.json'); }); - -// function compileTs(files, es5) { -// var tsProject = ts.createProject('tsconfig.json'); -// var allFiles = [].concat(files, ['typings/**/*.d.ts']); -// var res = gulp.src(allFiles, { -// base: config.src, -// outDir: config.tmp -// }) -// .pipe(tslint()) -// .pipe(tslint.report('prose', { -// summarizeFailureOutput: true, -// emitError: !watchMode -// })) -// .pipe(sourcemaps.init()) -// .pipe(ts(tsProject)) -// .on('error', function () { -// if (watchMode) { -// return; -// } -// process.exit(1); -// }); -// return res.js -// .pipe(sourcemaps.write('.', { -// includeContent: inline -// })) -// .pipe(gulp.dest(config.tmp)); -// } - gulp.task('inlineTemplates', ['sass'], function() { return gulp.src('.tmp/**/*.js', { base: './tmp' }) .pipe(replace(/'(.*?)\.css'/g, '\'$1.scss\'')) diff --git a/lib/components/JsonSchema/json-schema-common.scss b/lib/components/JsonSchema/json-schema-common.scss index e805e8eb..e6d24407 100644 --- a/lib/components/JsonSchema/json-schema-common.scss +++ b/lib/components/JsonSchema/json-schema-common.scss @@ -5,67 +5,69 @@ $cell-spacing: 25px; $cell-padding: 10px; $bullet-margin: 10px; $line-border: $lines-width solid $tree-lines-color; -$line-border-erase: ($lines-width + 1px) solid white; +$line-border-erase: ($lines-width + 1px) solid #fff; $param-name-height: 20px; -$sub-schema-offset: ($bullet-size/2) + $bullet-margin; +$sub-schema-offset: ($bullet-size / 2) + $bullet-margin; .param-name { - - font-size: 0.929em; - padding: $cell-padding 0 $cell-padding 0; - font-weight: $regular; - box-sizing: border-box; - line-height: $param-name-height; - border-left: $line-border; - white-space: nowrap; position: relative; + border-left: $line-border; + padding: $cell-padding 0; vertical-align: top; + line-height: $param-name-height; + + white-space: nowrap; + font-size: 0.929em; + font-weight: $regular; + + box-sizing: border-box; } .param-name-wrap { - padding-right: $cell-spacing; display: inline-block; + padding-right: $cell-spacing; font-family: $headers-font, $headers-font-family; } .param-info { - padding: $cell-padding 0; - box-sizing: border-box; border-bottom: 1px solid #ccc; + padding: $cell-padding 0; width: 75%; line-height: 1em; + + box-sizing: border-box; } .param-range { - color: rgba($primary-color, .7); position: relative; top: 1px; - padding: 0 4px; + margin-right: 6px; + margin-left: 6px; border-radius: $border-radius; background-color: rgba($primary-color, .1); - margin-left: 6px; - margin-right: 6px; + padding: 0 4px; + color: rgba($primary-color, 0.7); } .param-description { - font-size: 13px; + font-size: 13px; } .param-required { - color: red; - font-weight: bold; - font-size: 12px; - line-height: $param-name-height; vertical-align: middle; + line-height: $param-name-height; + color: #f00; + font-size: 12px; + font-weight: bold; } .param-type { + vertical-align: middle; + line-height: $param-name-height; color: rgba($black, 0.4); font-size: 0.929em; - line-height: $param-name-height; - vertical-align: middle; font-weight: normal; } @@ -158,11 +160,11 @@ $sub-schema-offset: ($bullet-size/2) + $bullet-margin; font-size: 13px; &:before { - content: "Values: {" + content: 'Values: {'; } &:after { - content: "}" + content: '}'; } > .enum-value { diff --git a/lib/components/Method/method.html b/lib/components/Method/method.html index 333ceaa3..6821882b 100644 --- a/lib/components/Method/method.html +++ b/lib/components/Method/method.html @@ -14,10 +14,14 @@
Definition
- -
{{data.httpMethod}}
- {{data.apiUrl}}{{data.path}} -
+ +
+
{{data.httpMethod}}
+ {{data.apiUrl}}{{data.path}} +
+

diff --git a/lib/components/Method/method.scss b/lib/components/Method/method.scss index 75043e51..aa373c10 100644 --- a/lib/components/Method/method.scss +++ b/lib/components/Method/method.scss @@ -134,6 +134,10 @@ responses-samples { text-transform: uppercase; } +[select-on-click] { + cursor: pointer; +} + @media (max-width: 1100px) { .methods:before { display: none; diff --git a/lib/components/Method/method.ts b/lib/components/Method/method.ts index 781b2afe..3df30528 100644 --- a/lib/components/Method/method.ts +++ b/lib/components/Method/method.ts @@ -3,6 +3,8 @@ import { Input } from '@angular/core'; import JsonPointer from '../../utils/JsonPointer'; import { RedocComponent, BaseComponent, SpecManager} from '../base'; +import { SelectOnClick } from '../../shared/components/SelectOnClick/select-on-click.directive'; + import { ParamsList } from '../ParamsList/params-list'; import { ResponsesList } from '../ResponsesList/responses-list'; import { ResponsesSamples } from '../ResponsesSamples/responses-samples'; @@ -14,7 +16,7 @@ import { SchemaHelper } from '../../services/schema-helper.service'; selector: 'method', templateUrl: './method.html', styleUrls: ['./method.css'], - directives: [ ParamsList, ResponsesList, ResponsesSamples, SchemaSample, RequestSamples ], + directives: [ ParamsList, ResponsesList, ResponsesSamples, SchemaSample, RequestSamples, SelectOnClick ], detect: true }) export class Method extends BaseComponent { diff --git a/lib/components/Redoc/redoc.scss b/lib/components/Redoc/redoc.scss index dcbb2bd4..fe47c8da 100644 --- a/lib/components/Redoc/redoc.scss +++ b/lib/components/Redoc/redoc.scss @@ -139,6 +139,17 @@ api-logo { color: $red; border: 1px solid rgba(38,50,56,0.1); } + + .hint--inversed { + &:before { + border-top-color: #fff; + } + + &:after { + background: #fff; + color: #383838; + } + } } footer { diff --git a/lib/components/RequestSamples/request-samples.html b/lib/components/RequestSamples/request-samples.html index 53a9ef20..05715ba8 100644 --- a/lib/components/RequestSamples/request-samples.html +++ b/lib/components/RequestSamples/request-samples.html @@ -5,6 +5,11 @@ -

+    
+
+ Copy +
+

+    
diff --git a/lib/components/RequestSamples/request-samples.scss b/lib/components/RequestSamples/request-samples.scss index 84569503..60c3fc5a 100644 --- a/lib/components/RequestSamples/request-samples.scss +++ b/lib/components/RequestSamples/request-samples.scss @@ -1,5 +1,37 @@ @import '../../shared/styles/variables'; +.action-buttons { + display: block; + opacity: 0; + transition: opacity 0.3s ease; + transform: translateY(100%); + + > span { + float: right; + } + + > span > a { + padding: 2px 10px; + color: #ffffff; + cursor: pointer; + background-color: darken($black, 4%); + + &:hover { + background-color: $black; + } + } + + &:after { + display: block; + content: ''; + clear: both; + } +} + +.code-sample:hover > .action-buttons { + opacity: 1; +} + header { font-family: $headers-font; font-size: $h5; @@ -39,4 +71,10 @@ pre { word-break: break-all; word-wrap: break-word; white-space: pre-wrap; + margin-top: 0; + overflow-x: auto; + padding: 20px; + border-radius: 4px; + background-color: #222d32; + margin-bottom: 36px; } diff --git a/lib/components/RequestSamples/request-samples.ts b/lib/components/RequestSamples/request-samples.ts index 487fcc9f..4b3fcc83 100644 --- a/lib/components/RequestSamples/request-samples.ts +++ b/lib/components/RequestSamples/request-samples.ts @@ -9,11 +9,13 @@ import { SchemaSample } from '../SchemaSample/schema-sample'; import { PrismPipe } from '../../utils/pipes'; import { RedocEventsService } from '../../services/index'; +import { CopyButton } from '../../shared/components/CopyButton/copy-button.directive'; + @RedocComponent({ selector: 'request-samples', templateUrl: './request-samples.html', styleUrls: ['./request-samples.css'], - directives: [SchemaSample, Tabs, Tab], + directives: [SchemaSample, Tabs, Tab, CopyButton], inputs: ['schemaPointer'], pipes: [PrismPipe], detect: true, diff --git a/lib/components/SchemaSample/schema-sample.html b/lib/components/SchemaSample/schema-sample.html index 8d0f2973..9e34e6dd 100644 --- a/lib/components/SchemaSample/schema-sample.html +++ b/lib/components/SchemaSample/schema-sample.html @@ -1,5 +1,10 @@
 Sample unavailable 
+

 
diff --git a/lib/components/SchemaSample/schema-sample.scss b/lib/components/SchemaSample/schema-sample.scss index 0df65e80..c0480079 100644 --- a/lib/components/SchemaSample/schema-sample.scss +++ b/lib/components/SchemaSample/schema-sample.scss @@ -3,7 +3,56 @@ pre { background-color: transparent; padding: 0; + margin: 0; + clear: both; } + +.action-buttons { + display: block; + opacity: 0; + transition: opacity 0.3s ease; + transform: translateY(100%); + + > span { + float: right; + } + + > span > a { + padding: 2px 10px; + color: #ffffff; + cursor: pointer; + + &:before { + content: '|'; + display: inline-block; + transform: translateX(-10px); + } + + &:last-child:before { + display: none; + margin-left: 0; + } + + &:first-child { + margin-right: 0; + } + + &:hover { + background-color: $black; + } + } + + &:after { + display: block; + content: ''; + clear: both; + } +} + +.snippet:hover .action-buttons { + opacity: 1; +} + :host { .property { //font-weight: bold; @@ -107,7 +156,7 @@ pre { display: none; } - .collapsed>.ellipsis { + .collapsed > .ellipsis { display: inherit; } diff --git a/lib/components/SchemaSample/schema-sample.ts b/lib/components/SchemaSample/schema-sample.ts index 6a2a0d11..86abd1b2 100644 --- a/lib/components/SchemaSample/schema-sample.ts +++ b/lib/components/SchemaSample/schema-sample.ts @@ -8,15 +8,19 @@ import { RedocComponent, BaseComponent, SpecManager } from '../base'; import { JsonFormatter } from '../../utils/JsonFormatterPipe'; import { SchemaNormalizer } from '../../services/schema-normalizer.service'; +import { CopyButton } from '../../shared/components/CopyButton/copy-button.directive'; + @RedocComponent({ selector: 'schema-sample', templateUrl: './schema-sample.html', pipes: [JsonFormatter], + directives: [CopyButton], styleUrls: ['./schema-sample.css'] }) export class SchemaSample extends BaseComponent { element: any; data: any; + enableButtons: boolean = false; @Input() skipReadOnly:boolean; private _normalizer:SchemaNormalizer; @@ -74,6 +78,10 @@ export class SchemaSample extends BaseComponent { } this.cache(sample); this.data.sample = sample; + + if (typeof sample === 'object') { + this.enableButtons = true; + } } cache(sample) { @@ -108,4 +116,21 @@ export class SchemaSample extends BaseComponent { } }); } + + expandAll() { + let elements = this.element.getElementsByClassName('collapsible'); + for (let i = 0; i < elements.length; i++) { + let collapsed = elements[i]; + collapsed.parentNode.classList.remove('collapsed'); + } + } + + collapseAll() { + let elements = this.element.getElementsByClassName('collapsible'); + for (let i = 0; i < elements.length; i++) { + let expanded = elements[i]; + if (expanded.parentNode.classList.contains('redoc-json')) continue; + expanded.parentNode.classList.add('collapsed'); + } + } } diff --git a/lib/services/clipboard.service.ts b/lib/services/clipboard.service.ts new file mode 100644 index 00000000..da618fe3 --- /dev/null +++ b/lib/services/clipboard.service.ts @@ -0,0 +1,83 @@ +'use strict'; + +export class Clipboard { + static isSupported():boolean { + return document.queryCommandSupported && document.queryCommandSupported('copy'); + } + + static selectElement(element:any):void { + let range; + let selection; + if ((document.body).createTextRange) { + range = (document.body).createTextRange(); + range.moveToElementText(element); + range.select(); + } else if (document.createRange && window.getSelection) { + selection = window.getSelection(); + range = document.createRange(); + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + } + } + + static deselect():void { + if ( (document).selection ) { + (document).selection.empty(); + } else if ( window.getSelection ) { + window.getSelection().removeAllRanges(); + } + } + + static copySelected():boolean { + let result; + try { + result = document.execCommand('copy'); + } catch (err) { + result = false; + } + return result; + } + + static copyElement(element:any):boolean { + Clipboard.selectElement(element); + let res = Clipboard.copySelected(); + if (res) Clipboard.deselect(); + return res; + } + + static copyCustom(text:string):boolean { + let textArea = document.createElement('textarea'); + textArea.style.position = 'fixed'; + textArea.style.top = '0'; + textArea.style.left = '0'; + + // Ensure it has a small width and height. Setting to 1px / 1em + // doesn't work as this gives a negative w/h on some browsers. + textArea.style.width = '2em'; + textArea.style.height = '2em'; + + // We don't need padding, reducing the size if it does flash render. + textArea.style.padding = '0'; + + // Clean up any borders. + textArea.style.border = 'none'; + textArea.style.outline = 'none'; + textArea.style.boxShadow = 'none'; + + // Avoid flash of white box if rendered for any reason. + textArea.style.background = 'transparent'; + + + textArea.value = text; + + document.body.appendChild(textArea); + + textArea.select(); + + let res = Clipboard.copySelected(); + + document.body.removeChild(textArea); + return res; + } +} diff --git a/lib/shared/components/CopyButton/copy-button.directive.ts b/lib/shared/components/CopyButton/copy-button.directive.ts new file mode 100644 index 00000000..9007fbe9 --- /dev/null +++ b/lib/shared/components/CopyButton/copy-button.directive.ts @@ -0,0 +1,52 @@ +'use strict'; + +import { Directive, Input, HostListener, Renderer, ElementRef, OnInit} from '@angular/core'; +import { Clipboard } from '../../../services/clipboard.service'; + +@Directive({ + selector: '[copy-button]' +}) +export class CopyButton implements OnInit { + $element: any; + cancelScrollBinding: any; + $redocEl: any; + @Input() copyText: string; + @Input() copyElement:any; + @Input() hintElement:any; + + constructor(private renderer: Renderer, private element: ElementRef) {} + + ngOnInit () { + if (!Clipboard.isSupported()) { + this.element.nativeElement.parentNode.removeChild(this.element.nativeElement); + } + this.renderer.setElementAttribute(this.element.nativeElement, 'data-hint', 'Copy to Clipboard!'); + } + + @HostListener('click') + onClick() { + let copied; + if (this.copyText) { + copied = Clipboard.copyCustom(this.copyText); + } else { + copied = Clipboard.copyElement(this.copyElement); + } + + if (copied) { + this.renderer.setElementAttribute(this.element.nativeElement, 'data-hint', 'Copied!'); + } else { + let hintElem = this.hintElement || this.copyElement; + if (!hintElem) return; + this.renderer.setElementAttribute(hintElem, 'data-hint', 'Press "ctrl + c" to copy'); + this.renderer.setElementClass(hintElem, 'hint--top', true); + this.renderer.setElementClass(hintElem, 'hint--always', true); + } + } + + @HostListener('mouseleave') + onLeave() { + setTimeout(() => { + this.renderer.setElementAttribute(this.element.nativeElement, 'data-hint', 'Copy to Clipboard'); + }, 500); + } +} diff --git a/lib/shared/components/SelectOnClick/select-on-click.directive.ts b/lib/shared/components/SelectOnClick/select-on-click.directive.ts new file mode 100644 index 00000000..1b651f82 --- /dev/null +++ b/lib/shared/components/SelectOnClick/select-on-click.directive.ts @@ -0,0 +1,17 @@ +'use strict'; + +import { Directive, HostListener, ElementRef} from '@angular/core'; +import { Clipboard } from '../../../services/clipboard.service'; + +@Directive({ + selector: '[select-on-click]' +}) +export class SelectOnClick { + $element: any; + constructor(private element: ElementRef) {} + + @HostListener('click') + onClick() { + Clipboard.selectElement(this.element.nativeElement); + } +} diff --git a/package.json b/package.json index 5a2a3798..7fe16147 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "redoc", "description": "Swagger-generated API Reference Documentation", - "version": "0.15.3", + "version": "0.16.0", "repository": { "type": "git", "url": "git://github.com/Rebilly/ReDoc" diff --git a/protractor.conf.js b/protractor.conf.js index aac8415f..ddf61283 100644 --- a/protractor.conf.js +++ b/protractor.conf.js @@ -50,18 +50,21 @@ if (travis) { 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, build: process.env.TRAVIS_BUILD_NUMBER, name: 'Redoc Safari Latest/OSX build ' + process.env.TRAVIS_BUILD_NUMBER, - idleTimeout: 180 + idleTimeout: 180, + maxDuration: 1800*2 },{ browserName: 'firefox', 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, build: process.env.TRAVIS_BUILD_NUMBER, - name: 'Redoc Firefox Latest/Win build ' + process.env.TRAVIS_BUILD_NUMBER + name: 'Redoc Firefox Latest/Win build ' + process.env.TRAVIS_BUILD_NUMBER, + maxDuration: 1800*2 },{ browserName: 'internet explorer', version: '11.0', 'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER, build: process.env.TRAVIS_BUILD_NUMBER, - name: 'Redoc IE11/Win build ' + process.env.TRAVIS_BUILD_NUMBER + name: 'Redoc IE11/Win build ' + process.env.TRAVIS_BUILD_NUMBER, + maxDuration: 1800*2 }]; } else { config.directConnect = true;