mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-11 03:16:48 +03:00
Implement Copy/Expand/Collapse buttons
This commit is contained in:
parent
c6e093ec75
commit
6c2bfd8dee
|
@ -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 {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -134,6 +134,10 @@ responses-samples {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
[select-on-click] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.methods:before {
|
||||
display: none;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
83
lib/services/clipboard.service.ts
Normal file
83
lib/services/clipboard.service.ts
Normal 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;
|
||||
}
|
||||
}
|
52
lib/shared/components/CopyButton/copy-button.directive.ts
Normal file
52
lib/shared/components/CopyButton/copy-button.directive.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user