Implement Copy/Expand/Collapse buttons

This commit is contained in:
Roman Hotsiy 2016-07-17 18:07:51 +03:00
parent c6e093ec75
commit 6c2bfd8dee
No known key found for this signature in database
GPG Key ID: 5CB7B3ACABA57CB0
14 changed files with 333 additions and 34 deletions

View File

@ -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 {

View File

@ -14,10 +14,14 @@
</div>
<div class="method-samples">
<h5>Definition</h5>
<span class="method-endpoint">
<h5 class="http-method" [ngClass]="data.httpMethod">{{data.httpMethod}}</h5>
<span class="api-url">{{data.apiUrl}}</span><span class="path">{{data.path}}</span>
</span>
<div class="method-endpoint">
<h5 class="http-method" [ngClass]="data.httpMethod">{{data.httpMethod}}</h5>
<span select-on-click><!--
--><span class="api-url">{{data.apiUrl}}</span><span class="path">{{data.path}}</span><!--
--></span>
</div>
<div *ngIf="data.bodyParam">
<br>
<request-samples [pointer]="pointer" [schemaPointer]="data.bodyParam._pointer">

View File

@ -134,6 +134,10 @@ responses-samples {
text-transform: uppercase;
}
[select-on-click] {
cursor: pointer;
}
@media (max-width: 1100px) {
.methods:before {
display: none;

View File

@ -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 {

View File

@ -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 {

View File

@ -5,6 +5,11 @@
<schema-sample [pointer]="data.schemaPointer" [skipReadOnly]="true"> </schema-sample>
</tab>
<tab *ngFor="let sample of data.samples" [tabTitle]="sample.lang">
<pre [innerHtml]="sample.source | prism:sample.lang"></pre>
<div class="code-sample">
<div class="action-buttons">
<span copy-button [copyText]="sample.source" class="hint--top hint--inversed"> <a>Copy</a> </span>
</div>
<pre [innerHtml]="sample.source | prism:sample.lang"></pre>
</div>
</tab>
</tabs>

View File

@ -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;
}

View File

@ -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,

View File

@ -1,5 +1,10 @@
<div class="snippet">
<!-- in case sample is not available for some reason -->
<pre *ngIf="data.sample == undefined"> Sample unavailable </pre>
<div class="action-buttons">
<span> <a *ngIf="enableButtons" (click)="collapseAll()">Collapse all</a> </span>
<span> <a *ngIf="enableButtons" (click)="expandAll()">Expand all</a> </span>
<span copy-button [copyText]="data.sample | json" class="hint--top hint--inversed"> <a>Copy</a> </span>
</div>
<pre [innerHtml]="data.sample | jsonFormatter"></pre>
</div>

View File

@ -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;
}

View File

@ -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');
}
}
}

View File

@ -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 ((<any>document.body).createTextRange) {
range = (<any>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 ( (<any>document).selection ) {
(<any>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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}