mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-27 19:13:44 +03:00
Merge commit '111bcdb0b9e97d4ec7fe33b2dbb418b226759288' into releases
This commit is contained in:
commit
57c50ddd16
30
.editorconfig
Normal file
30
.editorconfig
Normal file
|
@ -0,0 +1,30 @@
|
|||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
insert_final_newline=false
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[{.eslintrc,.babelrc,.stylelintrc,jest.config,*.json,*.jsb3,*.jsb2,*.bowerrc}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.scss]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.styl]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[*.coffee]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[{.analysis_options,*.yml,*.yaml}]
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
|
||||
[tslint.json]
|
||||
indent_style=space
|
||||
indent_size=2
|
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -1,3 +1,23 @@
|
|||
<a name="1.18.0"></a>
|
||||
# [1.18.0](https://github.com/Rebilly/ReDoc/compare/v1.16.1...v1.18.0) (2017-08-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* increase padding top for `.api-info-wrapper` when left sidebar is hiding to avoid header overlaying by top menu ([514fc29](https://github.com/Rebilly/ReDoc/commit/514fc29))
|
||||
* add `display: inline-block` for `.openapi-button` ([86b4db4](https://github.com/Rebilly/ReDoc/commit/86b4db4)),
|
||||
closes [#321](https://github.com/Rebilly/ReDoc/issues/321)
|
||||
* add margins around list-items in markdown ([b165785](https://github.com/Rebilly/ReDoc/commit/b165785))
|
||||
|
||||
### Features
|
||||
|
||||
* generate download link for specs defined by an object ([60e8cb4](https://github.com/Rebilly/ReDoc/commit/60e8cb4)), closes [#289](https://github.com/Rebilly/ReDoc/issues/289)
|
||||
* support text-plain response sample ([b84177c](https://github.com/Rebilly/ReDoc/commit/b84177c)), closes [#270](https://github.com/Rebilly/ReDoc/issues/270)
|
||||
* clickable logo that points to specific url ([cb3d318](https://github.com/Rebilly/ReDoc/commit/cb3d318)), closes
|
||||
[#322](https://github.com/Rebilly/ReDoc/issues/322)
|
||||
* support x-example for parameters ([f792273](https://github.com/Rebilly/ReDoc/commit/f792273)), closes
|
||||
[#297](https://github.com/Rebilly/ReDoc/issues/297)
|
||||
|
||||
<a name="1.17.0"></a>
|
||||
# [1.17.0](https://github.com/Rebilly/ReDoc/compare/v1.16.1...v1.17.0) (2017-08-02)
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# ReDoc
|
||||
<img alt="ReDoc logo" src="/docs/images/redoc-logo.png" width="400px" />
|
||||
|
||||
**OpenAPI/Swagger-generated API Reference Documentation**
|
||||
|
||||
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![Tested on APIs.guru](http://api.apis.guru/badges/tested_on.svg)](https://APIs.guru) [![dependencies Status](https://david-dm.org/Rebilly/ReDoc/status.svg)](https://david-dm.org/Rebilly/ReDoc) [![devDependencies Status](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![Stories in Ready](https://badge.waffle.io/Rebilly/ReDoc.png?label=ready&title=Ready)](https://waffle.io/Rebilly/ReDoc)
|
||||
|
|
|
@ -242,6 +242,7 @@ paths:
|
|||
in: header
|
||||
required: false
|
||||
type: string
|
||||
x-example: Bearer <TOKEN>
|
||||
- name: petId
|
||||
in: path
|
||||
description: Pet id to delete
|
||||
|
@ -619,6 +620,7 @@ paths:
|
|||
examples:
|
||||
application/json: OK
|
||||
application/xml: <message> OK </message>
|
||||
text/plain: OK
|
||||
headers:
|
||||
X-Rate-Limit:
|
||||
type: integer
|
||||
|
|
BIN
docs/images/redoc-logo.png
Normal file
BIN
docs/images/redoc-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
|
@ -2,7 +2,7 @@
|
|||
<h1>{{info.title}} <span class="api-info-version">({{info.version}})</span></h1>
|
||||
<p class="download-openapi" *ngIf="specUrl">
|
||||
Download OpenAPI specification:
|
||||
<a class="openapi-button" download target="_blank" attr.href='{{specUrl}}'> Download </a>
|
||||
<a class="openapi-button" [attr.download]="downloadFilename" target="_blank" [attr.href]="specUrl"> Download </a>
|
||||
</p>
|
||||
<p>
|
||||
<!-- TODO: create separate components for contact and license ? -->
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
@media (max-width: $right-panel-squash-breakpoint) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
padding-top: $section-spacing + 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.openapi-button {
|
||||
|
@ -17,6 +21,7 @@
|
|||
font-weight: normal;
|
||||
margin-left: 0.5em;
|
||||
padding: 3px 8px 4px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
:host /deep/ [section] {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use strict';
|
||||
import { Component, ChangeDetectionStrategy, OnInit, ElementRef } from '@angular/core';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { SpecManager, BaseComponent } from '../base';
|
||||
import { OptionsService, Marker } from '../../services/index';
|
||||
|
||||
|
@ -11,11 +12,13 @@ import { OptionsService, Marker } from '../../services/index';
|
|||
})
|
||||
export class ApiInfo extends BaseComponent implements OnInit {
|
||||
info: any = {};
|
||||
specUrl: String;
|
||||
specUrl: String | SafeResourceUrl;
|
||||
downloadFilename = '';
|
||||
constructor(specMgr: SpecManager,
|
||||
private optionsService: OptionsService,
|
||||
elRef: ElementRef,
|
||||
marker: Marker
|
||||
marker: Marker,
|
||||
private sanitizer: DomSanitizer
|
||||
) {
|
||||
super(specMgr);
|
||||
marker.addElement(elRef.nativeElement);
|
||||
|
@ -24,6 +27,12 @@ export class ApiInfo extends BaseComponent implements OnInit {
|
|||
init() {
|
||||
this.info = this.componentSchema.info;
|
||||
this.specUrl = this.specMgr.specUrl;
|
||||
if (!this.specUrl && window.Blob && window.URL) {
|
||||
const blob = new Blob([JSON.stringify(this.specMgr.rawSpec, null, 2)], {type : 'application/json'});
|
||||
this.specUrl = this.sanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(blob));
|
||||
this.downloadFilename = 'swagger.json';
|
||||
}
|
||||
|
||||
if (!isNaN(parseInt(this.info.version.toString().substring(0, 1)))) {
|
||||
this.info.version = 'v' + this.info.version;
|
||||
}
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
<img *ngIf="logo.imgUrl" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">
|
||||
<a *ngIf="logo.url" href="{{logo.url}}">
|
||||
<img *ngIf="logo.imgUrl" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">
|
||||
</a>
|
||||
<img *ngIf="logo.imgUrl && !logo.url" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">
|
||||
|
|
|
@ -17,6 +17,9 @@ export class ApiLogo extends BaseComponent implements OnInit {
|
|||
|
||||
init() {
|
||||
let logoInfo = this.componentSchema.info['x-logo'];
|
||||
if ('url' in this.componentSchema.info['contact']) {
|
||||
this.logo.url = this.componentSchema.info['contact']['url'];
|
||||
}
|
||||
if (!logoInfo) return;
|
||||
this.logo.imgUrl = logoInfo.url;
|
||||
this.logo.bgColor = logoInfo.backgroundColor || 'transparent';
|
||||
|
|
|
@ -64,7 +64,8 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.param-type, .param-array-format {
|
||||
.param-type,
|
||||
.param-array-format {
|
||||
vertical-align: middle;
|
||||
line-height: $param-name-height;
|
||||
color: rgba($black, 0.4);
|
||||
|
@ -73,7 +74,8 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
|||
.param-type {
|
||||
font-weight: normal;
|
||||
word-break: break-all;
|
||||
&.array::before, &.tuple::before {
|
||||
&.array::before,
|
||||
&.tuple::before {
|
||||
color: $black;
|
||||
font-weight: $base-font-weight;
|
||||
.param-collection-format-multi + & {
|
||||
|
@ -144,7 +146,6 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
|||
}
|
||||
|
||||
.param:first-of-type {
|
||||
|
||||
> .param-name::before {
|
||||
content: '';
|
||||
display: block;
|
||||
|
@ -158,7 +159,6 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
|||
|
||||
.param:last-of-type,
|
||||
.param.last {
|
||||
|
||||
> .param-name {
|
||||
position: relative;
|
||||
|
||||
|
@ -236,14 +236,24 @@ $sub-schema-offset: ($bullet-size / 2) + $bullet-margin;
|
|||
}
|
||||
}
|
||||
|
||||
.param-example {
|
||||
font-size: 0.95em;
|
||||
|
||||
&::before {
|
||||
content: 'Example: ';
|
||||
}
|
||||
}
|
||||
|
||||
.param-enum-value,
|
||||
.param-default-value {
|
||||
background-color: $background-color;
|
||||
border: 1px solid rgba($secondary-color, 0.2);
|
||||
.param-default-value,
|
||||
.param-example-value {
|
||||
font-family: Courier, monospace;
|
||||
background-color: rgba($secondary-color, 0.02);
|
||||
border: 1px solid rgba($secondary-color, 0.1);
|
||||
margin: 2px 3px;
|
||||
padding: 0 5px;
|
||||
border-radius: 2px;
|
||||
color: $secondary-color;
|
||||
padding: 0.1em 0.2em 0.2em;
|
||||
border-radius: $border-radius;
|
||||
color: $text-color;
|
||||
display: inline-block;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
<div class="param-default" *ngIf="param.default != null">
|
||||
<span class="param-default-value">{{param.default | json}}</span>
|
||||
</div>
|
||||
<div class="param-example" *ngIf="param.example != null">
|
||||
<span class="param-example-value">{{param.example | json}}</span>
|
||||
</div>
|
||||
<div *ngIf="param.enum || param._enumItem" class="param-enum">
|
||||
<span *ngFor="let enumItem of param.enum" class="param-enum-value {{enumItem.type}}">
|
||||
{{enumItem.val | json}}
|
||||
|
|
|
@ -275,16 +275,11 @@ footer {
|
|||
code {
|
||||
font-family: Courier, monospace;
|
||||
background-color: rgba(38,50,56,0.04);
|
||||
padding: 0.1em 0 0.2em 0;
|
||||
padding: 0.1em 0.2em 0.2em;
|
||||
font-size: 1em;
|
||||
border-radius: $border-radius;
|
||||
color: $red;
|
||||
border: 1px solid rgba(38,50,56,0.1);
|
||||
|
||||
&:before, &:after {
|
||||
letter-spacing: -0.2em;
|
||||
//content: "\00a0";
|
||||
}
|
||||
border: 1px solid rgba(38,50,56, 0.1);
|
||||
}
|
||||
|
||||
p:last-of-type {
|
||||
|
@ -311,6 +306,9 @@ footer {
|
|||
font-family: $base-font, $base-font-family;
|
||||
font-weight: $base-font-weight;
|
||||
line-height: $base-line-height;
|
||||
> li {
|
||||
margin: 1em 0;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
|
|
|
@ -20,7 +20,15 @@
|
|||
<div class="action-buttons">
|
||||
<span copy-button [copyText]="xmlSample" class="hint--top-left hint--inversed"> <a>Copy</a> </span>
|
||||
</div>
|
||||
<pre class="xml-sample" [innerHtml]="xmlSample | prism:'xml'"></pre>
|
||||
<pre class="response-sample" [innerHtml]="xmlSample | prism:'xml'"></pre>
|
||||
</div>
|
||||
</tab>
|
||||
<tab tabTitle="text/plain" *ngIf="textSample">
|
||||
<div class="snippet">
|
||||
<div class="action-buttons">
|
||||
<span copy-button [copyText]="xmlSample" class="hint--top-left hint--inversed"> <a>Copy</a> </span>
|
||||
</div>
|
||||
<pre class="response-sample">{{textSample}}</pre>
|
||||
</div>
|
||||
</tab>
|
||||
</tabs>
|
||||
|
|
|
@ -131,7 +131,7 @@ pre {
|
|||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.redoc-json, .xml-sample {
|
||||
.redoc-json, .response-sample {
|
||||
overflow-x: auto;
|
||||
padding: 20px;
|
||||
border-radius: $border-radius*2;
|
||||
|
|
|
@ -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 { getJsonLikeSample, getXmlLikeSample} from '../../utils/helpers';
|
||||
import { getJsonLikeSample, getXmlLikeSample, getTextLikeSample } from '../../utils/helpers';
|
||||
|
||||
@Component({
|
||||
selector: 'schema-sample',
|
||||
|
@ -21,6 +21,7 @@ export class SchemaSample extends BaseComponent implements OnInit {
|
|||
element: any;
|
||||
sample: any;
|
||||
xmlSample: string;
|
||||
textSample: string;
|
||||
enableButtons: boolean = false;
|
||||
|
||||
private _normalizer:SchemaNormalizer;
|
||||
|
@ -51,10 +52,8 @@ export class SchemaSample extends BaseComponent implements OnInit {
|
|||
base.examples = requestExamples;
|
||||
}
|
||||
|
||||
let xmlLikeSample = base.examples && getXmlLikeSample(base.examples);
|
||||
if (xmlLikeSample) {
|
||||
this.xmlSample = xmlLikeSample;
|
||||
}
|
||||
this.xmlSample = base.examples && getXmlLikeSample(base.examples);
|
||||
this.textSample = base.examples && getTextLikeSample(base.examples);
|
||||
|
||||
let jsonLikeSample = base.examples && getJsonLikeSample(base.examples);
|
||||
if (jsonLikeSample) {
|
||||
|
|
|
@ -119,6 +119,9 @@ const injectors = {
|
|||
injectTo._displayType = propertySchema.title ?
|
||||
`${propertySchema.title} (${propertySchema.type})` : propertySchema.type;
|
||||
}
|
||||
if (injectTo['x-example'] && !propertySchema.example) {
|
||||
injectTo.example = propertySchema['x-example'];
|
||||
}
|
||||
injectTo._widgetType = 'trivial';
|
||||
}
|
||||
},
|
||||
|
|
|
@ -142,6 +142,10 @@ export function isXmlLike(contentType: string): boolean {
|
|||
return contentType.search(/xml/i) !== -1;
|
||||
}
|
||||
|
||||
export function isTextLike(contentType: string): boolean {
|
||||
return contentType.search(/text\/plain/i) !== -1;
|
||||
}
|
||||
|
||||
export function getJsonLikeSample(samples: Object = {}) {
|
||||
const jsonLikeKeys = Object.keys(samples).filter(isJsonLike);
|
||||
|
||||
|
@ -161,3 +165,14 @@ export function getXmlLikeSample(samples: Object = {}) {
|
|||
|
||||
return samples[xmlLikeKeys[0]];
|
||||
}
|
||||
|
||||
|
||||
export function getTextLikeSample(samples: Object = {}) {
|
||||
const textLikeKeys = Object.keys(samples).filter(isTextLike);
|
||||
|
||||
if (!textLikeKeys.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return samples[textLikeKeys[0]];
|
||||
}
|
||||
|
|
|
@ -35,17 +35,6 @@ export class KeysPipe implements PipeTransform {
|
|||
}
|
||||
}
|
||||
|
||||
@Pipe({ name: 'jsonPointerEscape' })
|
||||
export class JsonPointerEscapePipe implements PipeTransform {
|
||||
transform(value:string) {
|
||||
if (isBlank(value)) return value;
|
||||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
|
||||
}
|
||||
return JsonPointer.escape(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({ name: 'marked' })
|
||||
export class MarkedPipe implements PipeTransform {
|
||||
renderer: MdRenderer;
|
||||
|
@ -58,7 +47,7 @@ export class MarkedPipe implements PipeTransform {
|
|||
transform(value:string) {
|
||||
if (isBlank(value)) return value;
|
||||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
|
||||
throw new InvalidPipeArgumentException(MarkedPipe, value);
|
||||
}
|
||||
let res = `<span class="redoc-markdown-block">${this.renderer.renderMd(value)}</span>`;
|
||||
return this.unstrustedSpec ? res : this.sanitizer.bypassSecurityTrustHtml(res);
|
||||
|
@ -95,7 +84,7 @@ export class PrismPipe implements PipeTransform {
|
|||
}
|
||||
if (isBlank(value)) return value;
|
||||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
|
||||
throw new InvalidPipeArgumentException(PrismPipe, value);
|
||||
}
|
||||
let lang = args[0].toString().trim().toLowerCase();
|
||||
if (langMap[lang]) lang = langMap[lang];
|
||||
|
@ -138,5 +127,5 @@ export class CollectionFormatPipe implements PipeTransform {
|
|||
}
|
||||
|
||||
export const REDOC_PIPES = [
|
||||
JsonPointerEscapePipe, MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter, KeysPipe, CollectionFormatPipe
|
||||
MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter, KeysPipe, CollectionFormatPipe
|
||||
];
|
||||
|
|
|
@ -26,6 +26,7 @@ export interface DescendantInfo {
|
|||
@Injectable()
|
||||
export class SpecManager {
|
||||
public _schema: any = {};
|
||||
public rawSpec: any;
|
||||
public apiUrl: string;
|
||||
public apiProtocol: string;
|
||||
public swagger: string;
|
||||
|
@ -48,6 +49,7 @@ export class SpecManager {
|
|||
if (typeof urlOrObject === 'string') {
|
||||
this.specUrl = urlOrObject;
|
||||
}
|
||||
this.rawSpec = schema;
|
||||
this._schema = snapshot(schema);
|
||||
try {
|
||||
this.init();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"description": "Swagger-generated API Reference Documentation",
|
||||
"version": "1.17.0",
|
||||
"version": "1.18.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Rebilly/ReDoc"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import {KeysPipe, JsonPointerEscapePipe, MarkedPipe} from '../../lib/utils/pipes';
|
||||
import {KeysPipe, MarkedPipe} from '../../lib/utils/pipes';
|
||||
|
||||
describe('Pipes', () => {
|
||||
describe('KeysPipe', () => {
|
||||
|
@ -33,34 +33,6 @@ describe('Pipes', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('JsonPointerEscapePipe', () => {
|
||||
let unescaped;
|
||||
let escaped;
|
||||
var pipe;
|
||||
|
||||
beforeEach(() => {
|
||||
unescaped = 'test/path~1';
|
||||
escaped = 'test~1path~01';
|
||||
pipe = new JsonPointerEscapePipe();
|
||||
});
|
||||
|
||||
describe('JsonPointerEscapePipe transform', () => {
|
||||
it('should escpae pointer', () => {
|
||||
var val = pipe.transform(unescaped);
|
||||
val.should.be.equal(escaped);
|
||||
});
|
||||
|
||||
it('should not support other objects', () => {
|
||||
(() => pipe.transform(45)).should.throw();
|
||||
(() => pipe.transform({})).should.throw();
|
||||
});
|
||||
|
||||
it('should not throw on blank input', () => {
|
||||
(() => pipe.transform()).should.not.throw();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('MarkedPipe', () => {
|
||||
let unmarked;
|
||||
let marked;
|
||||
|
|
Loading…
Reference in New Issue
Block a user