mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-24 01:23:43 +03:00
Merge branch 'master' into releases
This commit is contained in:
commit
35651acd10
85
README.md
85
README.md
|
@ -7,10 +7,91 @@
|
|||
|
||||
Swagger-generated API Reference Documentation
|
||||
|
||||
**Under development**
|
||||
|
||||
[Live demo](http://rebilly.github.io/ReDoc/)
|
||||
|
||||
## Deployment
|
||||
|
||||
## tl;dr
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ReDoc</title>
|
||||
<!-- needed for adaptive design -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!--
|
||||
ReDoc uses font options from the parent element
|
||||
So override default browser styles
|
||||
-->
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'>
|
||||
</redoc>
|
||||
<script src="bower_components/redoc/dist/redoc.min.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
#### 1. Install redoc
|
||||
Install using [bower](bower.io):
|
||||
|
||||
bower install redoc
|
||||
|
||||
or using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
|
||||
|
||||
npm install redoc --save
|
||||
|
||||
Alternatively you can just download [`redoc.min.js`](https://raw.githubusercontent.com/Rebilly/ReDoc/releases/dist/redoc.min.js).
|
||||
|
||||
#### 2. Reference redoc script in HTML
|
||||
Then reference [`redoc.min.js`](https://raw.githubusercontent.com/Rebilly/ReDoc/releases/dist/redoc.min.js) in your HTML page:
|
||||
```html
|
||||
<script src="bower_components/redoc/dist/redoc.min.js"> </script>
|
||||
```
|
||||
For npm:
|
||||
```html
|
||||
<script src="node_modules/redoc/dist/redoc.min.js"> </script>
|
||||
```
|
||||
|
||||
#### 3. Add `<redoc>` element to your page
|
||||
```html
|
||||
<redoc spec-url="<url to your spec>"></redoc>
|
||||
```
|
||||
|
||||
#### 4. Enjoy :smile:
|
||||
|
||||
## Configuration
|
||||
|
||||
* `spec-url` - relative or absolute url to your spec file
|
||||
* `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
|
||||
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset.
|
||||
* **function**: A getter function. Must return a number representing the offset (in pixels).
|
||||
|
||||
## Advanced usage
|
||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
||||
```js
|
||||
Redoc.init(specUrl, options)
|
||||
```
|
||||
|
||||
`options` is javascript object with camel-cased versions of options names as the keys. For example:
|
||||
```js
|
||||
Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
|
||||
scrollYOffset: 50
|
||||
})
|
||||
```
|
||||
-----------
|
||||
## Running locally
|
||||
1. Clone repository
|
||||
`git clone https://github.com/Rebilly/ReDoc.git`
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"description": "Swagger-generated API Reference Documentation",
|
||||
"main": "dist/redoc.full.min.js",
|
||||
"main": "dist/redoc.min.js",
|
||||
"authors": [
|
||||
"Roman Hotsiy"
|
||||
],
|
||||
|
|
|
@ -3,7 +3,7 @@ module.exports = {
|
|||
html: 'lib/**/*.html',
|
||||
scss: 'lib/**/*.scss',
|
||||
sourceEntryPoint: 'lib/index.js',
|
||||
outputName: 'redoc.full',
|
||||
outputName: 'redoc',
|
||||
output: 'dist/',
|
||||
tmp: '.tmp/',
|
||||
demo: 'demo/**/*',
|
||||
|
|
|
@ -55,7 +55,7 @@ var JS_DEV_DEPS_MIN = [
|
|||
|
||||
gulp.task('sass', function () {
|
||||
return gulp.src(paths.scss, { base: './' })
|
||||
.pipe(sass.sync().on('error', sass.logError))
|
||||
.pipe(sass.sync({outputStyle: 'compressed'}).on('error', sass.logError))
|
||||
.pipe(gulp.dest(paths.tmp));
|
||||
});
|
||||
|
||||
|
|
|
@ -3,21 +3,21 @@
|
|||
<head>
|
||||
<title>ReDoc</title>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<header> ReDoc </header>
|
||||
<input id="schema-url-input" value='swagger.json'>
|
||||
<button id="load-button"> Explore </button>
|
||||
</nav>
|
||||
<form id="schema-url-form">
|
||||
<input id="schema-url-input" value='swagger.json'>
|
||||
<button type="submit"> Explore </button>
|
||||
</form>
|
||||
</nav>
|
||||
|
||||
<redoc scroll-y-offset="body > nav" spec-url='swagger.json'>
|
||||
Loading...
|
||||
</redoc>
|
||||
<redoc scroll-y-offset="body > nav" spec-url='swagger.json'></redoc>
|
||||
|
||||
<!-- ReDoc built file with all dependencies included -->
|
||||
<script src="dist/redoc.full.js"> </script>
|
||||
<script src="dist/redoc.js"> </script>
|
||||
<script src="main.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -19,21 +19,55 @@ nav header {
|
|||
float: left;
|
||||
margin-left: 20px;
|
||||
font-size: 25px;
|
||||
color: white;
|
||||
position: absolute;
|
||||
color: #00329F;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav input {
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
max-width: 500px;
|
||||
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
||||
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
}
|
||||
|
||||
nav input:focus {
|
||||
border-color: #66afe9;
|
||||
outline: 0;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);
|
||||
}
|
||||
|
||||
nav button {
|
||||
border: 1px solid #FFFFFF;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background-color: #21476D;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
padding: 2px 10px;
|
||||
|
||||
touch-action: manipulation;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
nav button:hover {
|
||||
background-color: #e6e6e6;
|
||||
border-color: #adadad;
|
||||
}
|
||||
nav button:active {
|
||||
background-color: #d4d4d4;
|
||||
border-color: #8c8c8c;
|
||||
}
|
||||
|
||||
nav {
|
||||
|
@ -41,8 +75,25 @@ nav {
|
|||
height: 50px;
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
background-color: #053361;
|
||||
background-color: white;
|
||||
border-bottom: 1px solid #ccc;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
nav header {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
nav input {
|
||||
width: 70%;
|
||||
}
|
||||
nav header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
;(function() {
|
||||
'use strict';
|
||||
|
||||
var loadButton = document.getElementById('load-button');
|
||||
var schemaUrlForm = document.getElementById('schema-url-form');
|
||||
var schemaUrlInput = document.getElementById('schema-url-input');
|
||||
loadButton.addEventListener('click', function() {
|
||||
schemaUrlForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
Redoc.init(schemaUrlInput.value);
|
||||
return false;
|
||||
})
|
||||
})();
|
||||
|
|
5413
demo/rebilly.json
5413
demo/rebilly.json
File diff suppressed because it is too large
Load Diff
|
@ -29,7 +29,7 @@
|
|||
},{
|
||||
"name": "JSONP",
|
||||
"x-traitTag": true,
|
||||
"description": "If you're writing an AJAX application, and you'd like to wrap our response with a callback, all you have to do is specify a callback parameter with any API call:\n```\n https://api.instagram.com/v1/tags/coffee/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&callback=callbackFunction\n```\nWould respond with:\n```js\ncallbackFunction({\n ...\n});\n```",
|
||||
"description": "If you're writing an AJAX application, and you'd like to wrap our response with a callback, all you have to do is specify a callback parameter with any API call:\n```\n https://api.instagram.com/v1/tags/coffee/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&callback=callbackFunction\n```\nWould respond with:\n```js\ncallbackFunction({\n ...\n});\n``` \n > Example of markdown blockquote",
|
||||
"externalDocs": {
|
||||
"description": "Find out more",
|
||||
"url": "http://swagger.io"
|
||||
|
|
|
@ -1,34 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
import {Component, View, OnInit, OnDestroy, ElementRef} from 'angular2/core';
|
||||
import {Directive, ElementRef} from 'angular2/core';
|
||||
import {BrowserDomAdapter} from 'angular2/platform/browser';
|
||||
|
||||
@Component({
|
||||
selector: 'sticky-sidebar',
|
||||
@Directive({
|
||||
selector: '[sticky-sidebar]',
|
||||
inputs: ['scrollParent', 'scrollYOffset']
|
||||
})
|
||||
@View({
|
||||
template: `
|
||||
<div class="sticky-sidebar">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
`,
|
||||
lifecycle: [OnInit, OnDestroy]
|
||||
})
|
||||
export default class StickySidebar {
|
||||
constructor(elementRef, adapter) {
|
||||
constructor(elementRef, dom) {
|
||||
this.element = elementRef.nativeElement;
|
||||
this.adapter = adapter;
|
||||
this.dom = dom;
|
||||
|
||||
// initial styling
|
||||
this.adapter.setStyle(this.element, 'position', 'absolute');
|
||||
this.adapter.setStyle(this.element, 'top', '0');
|
||||
this.adapter.setStyle(this.element, 'bottom', '0');
|
||||
this.adapter.setStyle(this.element, 'max-height', '100%');
|
||||
this.dom.setStyle(this.element, 'position', 'absolute');
|
||||
this.dom.setStyle(this.element, 'top', '0');
|
||||
this.dom.setStyle(this.element, 'bottom', '0');
|
||||
this.dom.setStyle(this.element, 'max-height', '100%');
|
||||
}
|
||||
|
||||
bind() {
|
||||
this.cancelScrollBinding = this.adapter.onAndCancel(this.scrollParent, 'scroll', () => { this.updatePosition(); });
|
||||
this.cancelScrollBinding = this.dom.onAndCancel(this.scrollParent, 'scroll', () => { this.updatePosition(); });
|
||||
this.updatePosition();
|
||||
}
|
||||
|
||||
|
@ -45,13 +37,13 @@ export default class StickySidebar {
|
|||
}
|
||||
|
||||
stick() {
|
||||
this.adapter.setStyle(this.element, 'position', 'fixed');
|
||||
this.adapter.setStyle(this.element, 'top', this.scrollYOffset() + 'px');
|
||||
this.dom.setStyle(this.element, 'position', 'fixed');
|
||||
this.dom.setStyle(this.element, 'top', this.scrollYOffset() + 'px');
|
||||
}
|
||||
|
||||
unstick() {
|
||||
this.adapter.setStyle(this.element, 'position', 'absolute');
|
||||
this.adapter.setStyle(this.element, 'top', 0);
|
||||
this.dom.setStyle(this.element, 'position', 'absolute');
|
||||
this.dom.setStyle(this.element, 'top', 0);
|
||||
}
|
||||
|
||||
get scrollY() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import { getChildDebugElement } from 'tests/helpers';
|
||||
import { getChildDebugElementByType } from 'tests/helpers';
|
||||
import {Component, View, provide} from 'angular2/core';
|
||||
import {BrowserDomAdapter} from 'angular2/platform/browser';
|
||||
|
||||
|
@ -29,7 +29,7 @@ describe('Common components', () => {
|
|||
beforeEach((done) => {
|
||||
builder.createAsync(TestApp).then(_fixture => {
|
||||
fixture = _fixture;
|
||||
let debugEl = getChildDebugElement(fixture.debugElement, 'sticky-sidebar');
|
||||
let debugEl = getChildDebugElementByType(fixture.debugElement, StickySidebar);
|
||||
component = debugEl.componentInstance;
|
||||
done();
|
||||
}, err => done.fail(err));
|
||||
|
@ -67,8 +67,8 @@ describe('Common components', () => {
|
|||
`<div style="padding-top: 20px">
|
||||
<div style="height: 20px; position: fixed; top: 0;"> </div>
|
||||
<div style="position: relative">
|
||||
<sticky-sidebar [scrollParent]="scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||
</sticky-sidebar>
|
||||
<div sticky-sidebar [scrollParent]="scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
||||
|
|
|
@ -48,8 +48,8 @@ describe('Common components', () => {
|
|||
let tabs = childDebugEls.map(debugEl => debugEl.componentInstance);
|
||||
let [tab1, tab2] = tabs;
|
||||
|
||||
tab1.active.should.be.true;
|
||||
tab2.active.should.be.false;
|
||||
tab1.active.should.be.true();
|
||||
tab2.active.should.be.false();
|
||||
});
|
||||
|
||||
it('should change active tab on click', () => {
|
||||
|
|
|
@ -6,7 +6,7 @@ $side-bar-bg-color: #FAFAFA;
|
|||
|
||||
$side-menu-item-color: #384248;
|
||||
$side-menu-even-bg-color: #F0F0F0;
|
||||
$side-menu-active-bg-color: #E6E6E6;
|
||||
$side-menu-active-bg-color: #DEDEDE;
|
||||
$side-menu-item-hpadding: 20px;
|
||||
$side-menu-item-vpadding: 5px;
|
||||
|
||||
|
@ -16,3 +16,5 @@ $sample-panel-headers-color: #8A9094;
|
|||
$sample-panel-color: #CFD2D3;
|
||||
|
||||
$tree-lines-color: #7D97CE;
|
||||
|
||||
$side-menu-mobile-breakpoint: 1000px;
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
|
||||
.api-info-header {
|
||||
color: $headers-color;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
|
160
lib/components/JsonSchema/json-schema-common.scss
Normal file
160
lib/components/JsonSchema/json-schema-common.scss
Normal file
|
@ -0,0 +1,160 @@
|
|||
@import '../../common/styles/variables';
|
||||
$lines-width: 1px;
|
||||
$bullet-size: 7px;
|
||||
$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;
|
||||
|
||||
$param-name-height: 20px;
|
||||
|
||||
$sub-schema-offset: ($bullet-size/2) + $bullet-margin;
|
||||
|
||||
.param-schema {
|
||||
padding-left: $sub-schema-offset - $lines-width;
|
||||
border-left: $line-border;
|
||||
}
|
||||
|
||||
.param-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.param-schema:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: $sub-schema-offset;
|
||||
top: ($param-name-height/2) + $cell-padding;
|
||||
bottom: 0;
|
||||
border-left: $line-border;
|
||||
}
|
||||
|
||||
.param-name {
|
||||
font-size: 14px;
|
||||
padding: $cell-padding $cell-spacing $cell-padding 0;
|
||||
font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
line-height: $param-name-height;
|
||||
border-left: $line-border;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.param-info {
|
||||
width: 100%;
|
||||
padding: $cell-padding 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.param {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.param-required {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
line-height: $param-name-height;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.param-type {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
line-height: $param-name-height;
|
||||
vertical-align: middle;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.param-type.array:before {
|
||||
content: "Array of ";
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.param-type.string {
|
||||
color: rgba(0, 80, 0, 0.7);
|
||||
}
|
||||
|
||||
.param-type.integer, .param-type.number {
|
||||
color: rgba(74, 139, 179, 0.8);
|
||||
}
|
||||
|
||||
.param-type.object {
|
||||
color: rgba(0, 50, 159, 0.7);
|
||||
}
|
||||
|
||||
.param-type.boolean {
|
||||
color: firebrick;
|
||||
}
|
||||
|
||||
.param-type.with-hint {
|
||||
&:before, &:after {
|
||||
content: "\\00a0";
|
||||
}
|
||||
background-color: rgba(0, 50, 159, 0.1);
|
||||
padding: 0.2em 0;
|
||||
font-size: 0.85em;
|
||||
border-radius: 3px;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.param-type-trivial {
|
||||
margin: 10px 10px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* tree */
|
||||
|
||||
// Bullet
|
||||
.param-name > span:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: $bullet-size;
|
||||
height: $bullet-size;
|
||||
background-color: $tree-lines-color;
|
||||
margin: 0 $bullet-margin;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.param-name > span:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-top: $line-border;
|
||||
width: $bullet-margin;
|
||||
left: 0;
|
||||
top: ($param-name-height/2) + $cell-padding;
|
||||
}
|
||||
|
||||
.param-wrap:first-of-type > .param > .param-name:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -$lines-width;
|
||||
top: 0;
|
||||
border-left: $line-border-erase;
|
||||
height: ($param-name-height/2) + $cell-padding;
|
||||
}
|
||||
|
||||
.param-wrap:last-of-type > .param > .param-name {
|
||||
position: static;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -$lines-width;
|
||||
border-left: $line-border-erase;
|
||||
top: ($param-name-height/2) + $cell-padding + $lines-width;
|
||||
background-color: white;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.param-wrap:last-of-type > .param-schema {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.param-schema .param-wrap:first-of-type .param-name:before {
|
||||
display: none !important;
|
||||
}
|
35
lib/components/JsonSchema/json-schema-lazy.js
Normal file
35
lib/components/JsonSchema/json-schema-lazy.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
'use strict';
|
||||
|
||||
import {Component, View, ElementRef} from 'angular2/core';
|
||||
import {CORE_DIRECTIVES} from 'angular2/common';
|
||||
import JsonSchema from './json-schema';
|
||||
import {DynamicComponentLoader} from 'angular2/src/core/linker/dynamic_component_loader';
|
||||
import OptionsManager from '../../options';
|
||||
|
||||
@Component({
|
||||
selector: 'json-schema-lazy',
|
||||
inputs: ['pointer']
|
||||
})
|
||||
@View({
|
||||
template: '',
|
||||
directives: [CORE_DIRECTIVES]
|
||||
})
|
||||
export default class JsonSchemaLazy {
|
||||
|
||||
constructor(elementRef, dcl) {
|
||||
this.elementRef = elementRef;
|
||||
this.dcl = dcl;
|
||||
}
|
||||
|
||||
load() {
|
||||
if (OptionsManager.instance().options.disableLazySchemas) return;
|
||||
if (this.loaded) return;
|
||||
if (this.pointer) {
|
||||
this.dcl.loadNextToLocation(JsonSchema, this.elementRef).then((compRef) => {
|
||||
compRef.instance.pointer = this.pointer;
|
||||
});
|
||||
}
|
||||
this.loaded = true;
|
||||
}
|
||||
}
|
||||
JsonSchemaLazy.parameters = [[ElementRef], [DynamicComponentLoader]];
|
87
lib/components/JsonSchema/json-schema-lazy.spec.js
Normal file
87
lib/components/JsonSchema/json-schema-lazy.spec.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
'use strict';
|
||||
|
||||
import { getChildDebugElement } from 'tests/helpers';
|
||||
import {Component, View, provide} from 'angular2/core';
|
||||
import {DynamicComponentLoader} from 'angular2/src/core/linker/dynamic_component_loader';
|
||||
|
||||
import {
|
||||
TestComponentBuilder,
|
||||
inject,
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
it
|
||||
} from 'angular2/testing';
|
||||
|
||||
import JsonSchemaLazy from 'lib/components/JsonSchema/json-schema-lazy';
|
||||
import SchemaManager from 'lib/utils/SchemaManager';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
describe('JsonSchemaLazy Component', () => {
|
||||
let builder;
|
||||
let component;
|
||||
let schemaMgr = new SchemaManager();
|
||||
let fixture;
|
||||
let loader;
|
||||
let appRef = {
|
||||
instance: {}
|
||||
};
|
||||
beforeEachProviders(() => [
|
||||
provide(SchemaManager, {useValue: schemaMgr})
|
||||
]);
|
||||
beforeEach(inject([TestComponentBuilder, DynamicComponentLoader], (tcb, dcl) => {
|
||||
builder = tcb;
|
||||
loader = dcl;
|
||||
spyOn(loader, 'loadNextToLocation').and.returnValue({then: (fn) => fn(appRef)});
|
||||
}));
|
||||
beforeEach((done) => {
|
||||
builder.createAsync(TestApp).then(_fixture => {
|
||||
fixture = _fixture;
|
||||
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema-lazy');
|
||||
component = debugEl.componentInstance;
|
||||
done();
|
||||
}, err => done.fail(err));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
loader.loadNextToLocation.and.callThrough();
|
||||
});
|
||||
|
||||
it('should init component', () => {
|
||||
expect(component).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should run loadNextToLocation on load', () => {
|
||||
component.pointer = '#/def';
|
||||
fixture.detectChanges();
|
||||
component.load();
|
||||
expect(loader.loadNextToLocation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not run loadNextToLocation if already loaded', () => {
|
||||
component.pointer = '#/def';
|
||||
fixture.detectChanges();
|
||||
component.load();
|
||||
component.load();
|
||||
expect(loader.loadNextToLocation.calls.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it('should init json-schema with correct pointer', () => {
|
||||
component.pointer = '#/def';
|
||||
fixture.detectChanges();
|
||||
component.load();
|
||||
expect(appRef.instance.pointer).toEqual(component.pointer);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains a Method. */
|
||||
@Component({selector: 'test-app'})
|
||||
@View({
|
||||
directives: [JsonSchemaLazy],
|
||||
providers: [SchemaManager, DynamicComponentLoader],
|
||||
template:
|
||||
`<json-schema-lazy></json-schema-lazy>`
|
||||
})
|
||||
class TestApp {
|
||||
}
|
|
@ -1,4 +1,7 @@
|
|||
<span *ngIf="isTrivial" class="param-type param-type-trivial" [ngClass]="type">{{_displayType}}</span>
|
||||
<span *ngIf="isTrivial" class="param-wrap">
|
||||
<span class="param-type param-type-trivial {{type}}"
|
||||
[ngClass]="{'with-hint': _displayTypeHint}" title="{{_displayTypeHint}}">{{_displayType}}</span>
|
||||
</span>
|
||||
<div *ngIf="!isTrivial" class="params-wrap" [ngClass]="{'params-array': isArray}">
|
||||
<div *ngFor="#prop of data.properties" class="param-wrap">
|
||||
<div class="param" [ngClass]="{'discriminator': prop.isDiscriminator}">
|
||||
|
@ -7,7 +10,8 @@
|
|||
</div>
|
||||
<div class="param-info">
|
||||
<div>
|
||||
<span class="param-type" [ngClass]="prop.type">{{prop._displayType}} {{prop._displayFormat}}</span>
|
||||
<span class="param-type {{prop.type}}" [ngClass]="{'with-hint': prop._displayTypeHint}"
|
||||
title="{{prop._displayTypeHint}}"> {{prop._displayType}} {{prop._displayFormat}}</span>
|
||||
<span *ngIf="prop.isRequired" class="param-required">Required</span>
|
||||
</div>
|
||||
<div class="param-description" innerHtml="{{prop.description | marked}}"></div>
|
||||
|
|
|
@ -63,14 +63,15 @@ export default class JsonSchema extends BaseComponent {
|
|||
|
||||
if (!schema.properties) {
|
||||
this.isTrivial = true;
|
||||
this._displayType = `${schema.type} (Custom key-value pairs)`;
|
||||
this._displayType = schema.type;
|
||||
this._displayTypeHint = 'This field may contain data of any type';
|
||||
return;
|
||||
}
|
||||
|
||||
let discriminatorFieldIdx = -1;
|
||||
let props = Object.keys(schema.properties).map((prop, idx) => {
|
||||
let propData = schema.properties[prop];
|
||||
this.injectPropData(prop, propData, schema);
|
||||
propData = this.injectPropData(prop, propData, schema);
|
||||
if (propData.isDiscriminator) discriminatorFieldIdx = idx;
|
||||
return propData;
|
||||
});
|
||||
|
@ -97,6 +98,7 @@ export default class JsonSchema extends BaseComponent {
|
|||
}
|
||||
|
||||
injectPropData(prop, propData, schema) {
|
||||
propData = Object.assign({}, propData);
|
||||
propData._name = prop;
|
||||
propData.isRequired = this.requiredMap[prop];
|
||||
propData._displayType = propData.type;
|
||||
|
@ -108,16 +110,24 @@ export default class JsonSchema extends BaseComponent {
|
|||
itemType = propData.items.title || 'object';
|
||||
propData._pointer = propData.items._pointer || JsonPointer.join(this.pointer, ['properties', prop, 'items']);
|
||||
}
|
||||
propData._displayType = `array of ${itemType}`;
|
||||
propData._displayType = `${itemType}`;
|
||||
propData.format = itemFormat;
|
||||
propData._isArray = true;
|
||||
propData.type = 'array ' + propData.items.type;
|
||||
}
|
||||
|
||||
if (propData.type === 'object') {
|
||||
propData._displayType = propData.title || 'object';
|
||||
}
|
||||
|
||||
if (!propData.type) {
|
||||
propData._displayType = '< * >';
|
||||
propData._displayTypeHint = 'This field may contain data of any type';
|
||||
}
|
||||
|
||||
if (propData.format) propData._displayFormat = `<${propData.format}>`;
|
||||
|
||||
return propData;
|
||||
}
|
||||
|
||||
init() {
|
||||
|
|
|
@ -1,129 +1,4 @@
|
|||
@import '../../common/styles/variables';
|
||||
$lines-width: 1px;
|
||||
$bullet-size: 7px;
|
||||
$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;
|
||||
|
||||
$param-name-height: 20px;
|
||||
|
||||
$sub-schema-offset: ($bullet-size/2) + $bullet-margin;
|
||||
|
||||
.param-schema {
|
||||
padding-left: $sub-schema-offset - $lines-width;
|
||||
border-left: $line-border;
|
||||
}
|
||||
|
||||
.param-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.param-schema:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: $sub-schema-offset;
|
||||
top: ($param-name-height/2) + $cell-padding;
|
||||
bottom: 0;
|
||||
border-left: $line-border;
|
||||
}
|
||||
|
||||
.param-name {
|
||||
font-size: 14px;
|
||||
padding: $cell-padding $cell-spacing $cell-padding 0;
|
||||
font-weight: bold;
|
||||
box-sizing: border-box;
|
||||
line-height: $param-name-height;
|
||||
border-left: $line-border;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.param-info {
|
||||
width: 100%;
|
||||
padding: $cell-padding 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.param {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.param-required {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
line-height: $param-name-height;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.param-type {
|
||||
text-transform: capitalize;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
line-height: $param-name-height;
|
||||
vertical-align: middle;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.param-type-trivial {
|
||||
margin: 10px 10px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* tree */
|
||||
|
||||
// Bullet
|
||||
.param-name > span:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: $bullet-size;
|
||||
height: $bullet-size;
|
||||
background-color: $tree-lines-color;
|
||||
margin: 0 $bullet-margin;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.param-name > span:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-top: $line-border;
|
||||
width: $bullet-margin;
|
||||
left: 0;
|
||||
top: ($param-name-height/2) + $cell-padding;
|
||||
}
|
||||
|
||||
.param-wrap:first-of-type .param-name:before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -$lines-width;
|
||||
top: 0;
|
||||
border-left: $line-border-erase;
|
||||
height: ($param-name-height/2) + $cell-padding;
|
||||
}
|
||||
|
||||
.param-wrap:last-of-type > .param > .param-name:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -$lines-width - 1px;
|
||||
border-left: $line-border-erase;
|
||||
top: ($param-name-height/2) + $cell-padding + $lines-width;
|
||||
background-color: white;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.param-wrap:last-of-type > .param-schema {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.param-schema .param-wrap:first-of-type .param-name:before {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@import 'json-schema-common';
|
||||
|
||||
/* styles for array-schema for array */
|
||||
$array-marker-font-sz: 12px;
|
||||
|
@ -166,6 +41,10 @@ $array-marker-line-height: 1.5;
|
|||
height: ($param-name-height/2) + $cell-padding;
|
||||
}
|
||||
|
||||
.params-wrap > .param > .param-schema.param-array {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.param.discriminator {
|
||||
> div {
|
||||
padding-bottom: 0;
|
||||
|
|
73
lib/components/JsonSchema/json-schema.spec.js
Normal file
73
lib/components/JsonSchema/json-schema.spec.js
Normal file
|
@ -0,0 +1,73 @@
|
|||
'use strict';
|
||||
|
||||
import { getChildDebugElement } from 'tests/helpers';
|
||||
import {Component, View, provide} from 'angular2/core';
|
||||
|
||||
import {
|
||||
TestComponentBuilder,
|
||||
inject,
|
||||
beforeEach,
|
||||
beforeEachProviders,
|
||||
it
|
||||
} from 'angular2/testing';
|
||||
|
||||
import JsonSchema from 'lib/components/JsonSchema/json-schema';
|
||||
import SchemaManager from 'lib/utils/SchemaManager';
|
||||
|
||||
describe('Redoc components', () => {
|
||||
describe('JsonSchema Component', () => {
|
||||
let builder;
|
||||
let component;
|
||||
let schemaMgr = new SchemaManager();
|
||||
let fixture;
|
||||
beforeEachProviders(() => [
|
||||
provide(SchemaManager, {useValue: schemaMgr})
|
||||
]);
|
||||
beforeEach(inject([TestComponentBuilder], (tcb) => {
|
||||
builder = tcb;
|
||||
}));
|
||||
beforeEach((done) => {
|
||||
builder.createAsync(TestApp).then(_fixture => {
|
||||
fixture = _fixture;
|
||||
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema');
|
||||
component = debugEl.componentInstance;
|
||||
done();
|
||||
}, err => done.fail(err));
|
||||
});
|
||||
|
||||
it('should init component', () => {
|
||||
component.pointer = '';
|
||||
schemaMgr._schema = {type: 'object'};
|
||||
fixture.detectChanges();
|
||||
expect(component).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should set isTrivial for non-object/array types', () => {
|
||||
component.pointer = '';
|
||||
schemaMgr._schema = {type: 'string'};
|
||||
fixture.detectChanges();
|
||||
component.isTrivial.should.be.true();
|
||||
});
|
||||
|
||||
it('should use < * > notation for prop without type', () => {
|
||||
component.pointer = '';
|
||||
schemaMgr._schema = {type: 'object', properties: {
|
||||
test: {}
|
||||
}};
|
||||
fixture.detectChanges();
|
||||
component.data.properties[0]._displayType.should.be.equal('< * >');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
/** Test component that contains a Method. */
|
||||
@Component({selector: 'test-app'})
|
||||
@View({
|
||||
directives: [JsonSchema],
|
||||
providers: [SchemaManager],
|
||||
template:
|
||||
`<json-schema></json-schema>`
|
||||
})
|
||||
class TestApp {
|
||||
}
|
|
@ -103,6 +103,7 @@ responses-samples {
|
|||
|
||||
.method-description {
|
||||
padding: 30px 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.http-method {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
.tag-info h1 {
|
||||
color: $headers-color;
|
||||
text-transform: capitalize;
|
||||
font-weight: bold;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.methods {
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
padding: 0.2em 0;
|
||||
margin: 0.5em 0;
|
||||
color: #253137;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@import '../JsonSchema/json-schema.scss';
|
||||
@import '../JsonSchema/json-schema-common';
|
||||
|
||||
|
||||
// paramters can't be multilevel so table representation works for it without javascript
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<div class="redoc-wrap">
|
||||
<sticky-sidebar [scrollParent]="scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||
<api-logo> </api-logo>
|
||||
<side-menu> </side-menu>
|
||||
</sticky-sidebar>
|
||||
<div class="menu-content" sticky-sidebar [scrollParent]="scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||
<api-logo> </api-logo>
|
||||
<side-menu> </side-menu>
|
||||
</div>
|
||||
<div id="api-content">
|
||||
<api-info> </api-info>
|
||||
<methods-list> </methods-list>
|
||||
|
|
|
@ -16,9 +16,11 @@ import {ElementRef} from 'angular2/core';
|
|||
import {BrowserDomAdapter, bootstrap} from 'angular2/platform/browser';
|
||||
import detectScollParent from 'scrollparent';
|
||||
|
||||
import {isFunction} from 'angular2/src/facade/lang';
|
||||
import {isFunction, isString} from 'angular2/src/facade/lang';
|
||||
|
||||
let optionNames = new Set(['scrollYOffset']);
|
||||
let optionNames = new Set(['scrollYOffset', 'disableLazySchemas']);
|
||||
|
||||
let dom = new BrowserDomAdapter();
|
||||
|
||||
@RedocComponent({
|
||||
selector: 'redoc',
|
||||
|
@ -41,6 +43,7 @@ export default class Redoc extends BaseComponent {
|
|||
this.parseOptions();
|
||||
this.options = Object.assign({}, optionsMgr.options, this.options);
|
||||
this.normalizeOptions();
|
||||
optionsMgr.options = this.options;
|
||||
}
|
||||
|
||||
parseOptions() {
|
||||
|
@ -79,12 +82,63 @@ export default class Redoc extends BaseComponent {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isString(this.options.disableLazySchemas)) this.options.disableLazySchemas = true;
|
||||
}
|
||||
|
||||
static showLoadingAnimation() {
|
||||
if (!dom.query('#redoc-loading-style')) {
|
||||
let animStyle = dom.createStyleElement(`
|
||||
redoc.loading {
|
||||
position: relative;
|
||||
display: block;
|
||||
min-height:350px;
|
||||
}
|
||||
|
||||
redoc.loading:before {
|
||||
content: "Loading...";
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
padding-top: 40px;
|
||||
color: #3F5C9C;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: white;
|
||||
z-index: 9999;
|
||||
opacity: 1;
|
||||
transition: all 0.6s ease-out;
|
||||
}
|
||||
|
||||
redoc.loading-remove:before {
|
||||
opacity: 0;
|
||||
}
|
||||
`);
|
||||
animStyle.id = 'redoc-loading-style';
|
||||
dom.appendChild(dom.defaultDoc().head, animStyle);
|
||||
}
|
||||
let elem = dom.query('redoc');
|
||||
dom.addClass(elem, 'loading');
|
||||
}
|
||||
|
||||
static hideLoadingAnimation() {
|
||||
let redocEl = dom.query('redoc');
|
||||
dom.addClass(redocEl, 'loading-remove');
|
||||
setTimeout(() => {
|
||||
dom.removeClass(redocEl, 'loading-remove');
|
||||
dom.removeClass(redocEl, 'loading');
|
||||
}, 400);
|
||||
}
|
||||
|
||||
static init(schemaUrl, options) {
|
||||
if (Redoc.appRef) {
|
||||
Redoc.dispose();
|
||||
}
|
||||
Redoc.showLoadingAnimation();
|
||||
return SchemaManager.instance().load(schemaUrl)
|
||||
.then(() => {
|
||||
(new OptionsManager()).options = options;
|
||||
|
@ -92,6 +146,7 @@ export default class Redoc extends BaseComponent {
|
|||
})
|
||||
.then(
|
||||
(appRef) => {
|
||||
Redoc.hideLoadingAnimation();
|
||||
Redoc.appRef = appRef;
|
||||
redocEvents.bootstrapped.next();
|
||||
console.log('ReDoc bootstrapped!');
|
||||
|
@ -117,6 +172,7 @@ export default class Redoc extends BaseComponent {
|
|||
static dispose() {
|
||||
let dom = new BrowserDomAdapter();
|
||||
let el = dom.query('redoc');
|
||||
let elClone;
|
||||
let parent;
|
||||
let nextSibling;
|
||||
if (el) {
|
||||
|
@ -124,14 +180,15 @@ export default class Redoc extends BaseComponent {
|
|||
nextSibling = el.nextElementSibling;
|
||||
}
|
||||
|
||||
elClone = el.cloneNode(false);
|
||||
|
||||
if (Redoc.appRef) {
|
||||
Redoc.appRef.dispose();
|
||||
Redoc.appRef = null;
|
||||
|
||||
// Redoc dispose removes host element, so need to restore it
|
||||
el = dom.createElement('redoc');
|
||||
el.innerText = 'Loading...';
|
||||
parent && parent.insertBefore(el, nextSibling);
|
||||
elClone.innerHTML = 'Loading...';
|
||||
parent && parent.insertBefore(elClone, nextSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,24 +5,6 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host {
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
background-color: #f2f2f2;
|
||||
padding: 10px;
|
||||
overflow-x: auto;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.redoc-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
@ -49,17 +31,48 @@ api-info {
|
|||
api-logo {
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
sticky-sidebar {
|
||||
[sticky-sidebar] {
|
||||
width: $side-bar-width;
|
||||
background-color: $side-bar-bg-color;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: $side-bar-bg-color;
|
||||
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
bottom: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
#api-content {
|
||||
margin-left: $side-bar-width;
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
padding-top: 3em;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#api-content:after {
|
||||
content: "";
|
||||
position: absolute;;
|
||||
left:0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: black;
|
||||
opacity: 0.5;
|
||||
transition: all 0.3s ease;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#api-content.menu-opened:after {
|
||||
display: block;;
|
||||
}
|
||||
|
||||
footer {
|
||||
|
@ -77,3 +90,104 @@ footer {
|
|||
color: $headers-color;
|
||||
}
|
||||
}
|
||||
|
||||
/* global redoc styles */
|
||||
|
||||
:host p {
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
/* markdown elements */
|
||||
|
||||
:host .redoc-markdown-block {
|
||||
pre {
|
||||
font-family: Courier, monospace;
|
||||
white-space: pre-wrap;
|
||||
background-color: rgba(0,0,0,0.04);
|
||||
padding: 10px;
|
||||
overflow-x: auto;
|
||||
line-height: normal;
|
||||
border-radius: 3px;
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
|
||||
&:before, &:after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: Courier, monospace;
|
||||
background-color: rgba(0,0,0,0.04);
|
||||
padding: 0.2em 0;
|
||||
font-size: 0.85em;
|
||||
border-radius: 3px;
|
||||
|
||||
&:before, &:after {
|
||||
letter-spacing: -0.2em;
|
||||
content: "\\00a0";
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
padding: 0 15px;
|
||||
color: #777;
|
||||
border-left: 4px solid #ddd;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: 2em;
|
||||
margin: 0;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
word-break: normal;
|
||||
word-break: keep-all;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
table tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
&:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
table th, table td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
table th {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,28 +105,39 @@ describe('Redoc components', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Redoc init', () => {
|
||||
it('should return promise', () => {
|
||||
let res = Redoc.init();
|
||||
res.should.be.instanceof(Promise);
|
||||
});
|
||||
|
||||
it('should reject promise for not specifed url', (done) => {
|
||||
let res = Redoc.init();
|
||||
res.then(() => { done.fail('Should not been called'); }, () => {
|
||||
done();
|
||||
describe('Redoc init', () => {
|
||||
let dom = new BrowserDomAdapter();
|
||||
let elem;
|
||||
beforeEach(() => {
|
||||
elem = dom.createElement('redoc');
|
||||
dom.defaultDoc().body.appendChild(elem);
|
||||
});
|
||||
});
|
||||
|
||||
//skip because of PhantomJS crashes on this testcase
|
||||
xit('should init redoc', (done) => {
|
||||
var node = document.createElement('redoc');
|
||||
document.body.appendChild(node);
|
||||
let res = Redoc.init('/tests/schemas/extended-petstore.json');
|
||||
res.then(() => { done(); }, () => {
|
||||
done.fail('Error handler should not been called');
|
||||
afterEach(() => {
|
||||
dom.defaultDoc().body.removeChild(elem);
|
||||
});
|
||||
|
||||
it('should return promise', () => {
|
||||
let res = Redoc.init();
|
||||
res.should.be.instanceof(Promise);
|
||||
});
|
||||
|
||||
it('should reject promise for not specifed url', (done) => {
|
||||
let res = Redoc.init();
|
||||
res.then(() => { done.fail('Should not been called'); }, () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
//skip because of PhantomJS crashes on this testcase
|
||||
xit('should init redoc', (done) => {
|
||||
var node = document.createElement('redoc');
|
||||
document.body.appendChild(node);
|
||||
let res = Redoc.init('/tests/schemas/extended-petstore.json');
|
||||
res.then(() => { done(); }, () => {
|
||||
done.fail('Error handler should not been called');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -217,7 +228,7 @@ describe('Redoc init', () => {
|
|||
@View({
|
||||
directives: [Redoc],
|
||||
template:
|
||||
`<redoc></redoc>`
|
||||
`<redoc disable-lazy-schemas></redoc>`
|
||||
})
|
||||
class TestApp {
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<h2 class="responses-list-header" *ngIf="data.responses.length"> Responses </h2>
|
||||
<zippy *ngFor="#response of data.responses" title="{{response.code}} {{response.description}}"
|
||||
[type]="response.type" [empty]="!response.schema">
|
||||
[type]="response.type" [empty]="response.empty" (open)="lazySchema.load()">
|
||||
<div *ngIf="response.headers" class="response-headers">
|
||||
<header>
|
||||
Headers
|
||||
|
@ -14,6 +14,8 @@
|
|||
<header>
|
||||
Response schema
|
||||
</header>
|
||||
<json-schema *ngIf="response.schema" class="schema type" pointer="{{response.pointer}}/schema">
|
||||
<json-schema *ngIf="response.schema && !enabledLazy" class="schema type" pointer="{{response.pointer}}/schema">
|
||||
</json-schema>
|
||||
<json-schema-lazy #lazySchema pointer="{{response.schema ? response.pointer + '/schema' : null}}">
|
||||
</json-schema-lazy>
|
||||
</zippy>
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
import {RedocComponent, BaseComponent} from '../base';
|
||||
import JsonPointer from '../../utils/JsonPointer';
|
||||
import JsonSchema from '../JsonSchema/json-schema';
|
||||
import JsonSchemaLazy from '../JsonSchema/json-schema-lazy';
|
||||
import Zippy from '../../common/components/Zippy/zippy';
|
||||
import {statusCodeType} from '../../utils/helpers';
|
||||
import OptionsManager from '../../options';
|
||||
|
||||
function isNumeric(n) {
|
||||
return (!isNaN(parseFloat(n)) && isFinite(n));
|
||||
|
@ -14,7 +16,7 @@ function isNumeric(n) {
|
|||
selector: 'responses-list',
|
||||
templateUrl: './lib/components/ResponsesList/responses-list.html',
|
||||
styleUrls: ['./lib/components/ResponsesList/responses-list.css'],
|
||||
directives: [JsonSchema, Zippy]
|
||||
directives: [JsonSchema, Zippy, JsonSchemaLazy]
|
||||
})
|
||||
export default class ResponsesList extends BaseComponent {
|
||||
constructor(schemaMgr) {
|
||||
|
@ -24,6 +26,7 @@ export default class ResponsesList extends BaseComponent {
|
|||
prepareModel() {
|
||||
this.data = {};
|
||||
this.data.responses = [];
|
||||
this.enabledLazy = !OptionsManager.instance().options.disableLazySchemas;
|
||||
|
||||
let responses = this.componentSchema;
|
||||
if (!responses) return;
|
||||
|
@ -40,6 +43,7 @@ export default class ResponsesList extends BaseComponent {
|
|||
resp.pointer = ref;
|
||||
}
|
||||
|
||||
resp.empty = !resp.schema;
|
||||
resp.code = respCode;
|
||||
resp.type = statusCodeType(resp.code);
|
||||
if (resp.headers) {
|
||||
|
@ -48,6 +52,7 @@ export default class ResponsesList extends BaseComponent {
|
|||
respInfo.name = k;
|
||||
return respInfo;
|
||||
});
|
||||
resp.empty = false;
|
||||
}
|
||||
resp.extendable = resp.headers || resp.length;
|
||||
return resp;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
padding: 0.2em 0;
|
||||
margin: 0.5em 0;
|
||||
color: #253137;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.header-name {
|
||||
|
|
|
@ -13,9 +13,10 @@ header {
|
|||
margin: 5px 0;
|
||||
color: $sample-panel-headers-color;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:host tabs li {
|
||||
:host > tabs > ul li {
|
||||
font-size: 13px;
|
||||
margin: 2px 0;
|
||||
padding: 2px 5px;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="snippet">
|
||||
<!-- in case sample is not available for some reason -->
|
||||
<pre *ngIf="data.sample == null"> Sample unavailable </pre>
|
||||
<pre>{{data.sample | json}}</pre>
|
||||
<pre innerHtml="{{data.sample | jsonFormatter}}"></pre>
|
||||
</div>
|
||||
|
|
|
@ -4,19 +4,19 @@ import {RedocComponent, BaseComponent} from '../base';
|
|||
|
||||
import SchemaSampler from 'json-schema-instantiator';
|
||||
|
||||
import {JsonFormatter} from '../../utils/JsonFormatterPipe';
|
||||
import {ElementRef} from 'angular2/core';
|
||||
|
||||
@RedocComponent({
|
||||
selector: 'schema-sample',
|
||||
templateUrl: './lib/components/SchemaSample/schema-sample.html',
|
||||
styles: [`
|
||||
pre {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
`]
|
||||
pipes: [JsonFormatter],
|
||||
styleUrls: ['./lib/components/SchemaSample/schema-sample.css']
|
||||
})
|
||||
export default class SchemaSample extends BaseComponent {
|
||||
constructor(schemaMgr) {
|
||||
constructor(schemaMgr, elementRef) {
|
||||
super(schemaMgr);
|
||||
this.element = elementRef.nativeElement;
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -44,5 +44,19 @@ export default class SchemaSample extends BaseComponent {
|
|||
}
|
||||
|
||||
this.data.sample = sample;
|
||||
|
||||
|
||||
this.element.addEventListener('click', (event) => {
|
||||
var collapsed, target = event.target;
|
||||
if (event.target.className === 'collapser') {
|
||||
collapsed = target.parentNode.getElementsByClassName('collapsible')[0];
|
||||
if (collapsed.parentNode.classList.contains('collapsed')) {
|
||||
collapsed.parentNode.classList.remove('collapsed');
|
||||
} else {
|
||||
collapsed.parentNode.classList.add('collapsed');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
SchemaSample.parameters = SchemaSample.parameters.concat([[ElementRef]]);
|
||||
|
|
112
lib/components/SchemaSample/schema-sample.scss
Normal file
112
lib/components/SchemaSample/schema-sample.scss
Normal file
|
@ -0,0 +1,112 @@
|
|||
pre {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
:host {
|
||||
.property {
|
||||
//font-weight: bold;
|
||||
}
|
||||
|
||||
.type-null {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.type-boolean {
|
||||
color: firebrick;
|
||||
}
|
||||
|
||||
.type-number {
|
||||
color: #4A8BB3;
|
||||
}
|
||||
|
||||
.type-string {
|
||||
color: #66B16E;
|
||||
}
|
||||
|
||||
.callback-function {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.collapser:after {
|
||||
content: "-";
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.collapsed > .collapser:after {
|
||||
content: "+";
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ellipsis:after {
|
||||
content: " … ";
|
||||
}
|
||||
|
||||
.collapsible {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.hoverable {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.hovered {
|
||||
background-color: rgba(235, 238, 249, 1);
|
||||
}
|
||||
|
||||
.collapser {
|
||||
padding-right: 6px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
ul, .redoc-json ul {
|
||||
list-style-type: none;
|
||||
padding: 0px;
|
||||
margin: 0px 0px 0px 26px;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hoverable {
|
||||
transition: background-color .2s ease-out 0s;
|
||||
-webkit-transition: background-color .2s ease-out 0s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.hovered {
|
||||
transition-delay: .2s;
|
||||
-webkit-transition-delay: .2s;
|
||||
}
|
||||
|
||||
.selected {
|
||||
outline-style: solid;
|
||||
outline-width: 1px;
|
||||
outline-style: dotted;
|
||||
}
|
||||
|
||||
.collapsed>.collapsible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapsed>.ellipsis {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.collapser {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: -1.5em;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
}
|
|
@ -1,13 +1,22 @@
|
|||
<h2 class="menu-header"> Api reference </h2>
|
||||
<div *ngFor="var cat of data.menu; #idx = index" class="menu-cat">
|
||||
|
||||
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [ngClass]="{active: cat.active}"> {{cat.name}}</label>
|
||||
<ul class="menu-subitems" [ngClass]="{active: cat.active}">
|
||||
<li *ngFor="var method of cat.methods; var methIdx = index"
|
||||
[ngClass]="{active: method.active}"
|
||||
(click)="activateAndScroll(idx, methIdx)">
|
||||
{{method.summary}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="mobile-nav" (click)="toggleMobileNav()">
|
||||
<span class="menu-header"> API Reference: </span>
|
||||
<span class="selected-item-info">
|
||||
<span class="selected-tag"> {{activeCatCaption}} </span>
|
||||
<span class="selected-endpoint">{{activeItemCaption}}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="resources-nav">
|
||||
<h2 class="menu-header"> API reference </h2>
|
||||
<div *ngFor="var cat of data.menu; #idx = index" class="menu-cat">
|
||||
|
||||
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [ngClass]="{active: cat.active}"> {{cat.name}}</label>
|
||||
<ul class="menu-subitems" [ngClass]="{active: cat.active}">
|
||||
<li *ngFor="var method of cat.methods; var methIdx = index"
|
||||
[ngClass]="{active: method.active}"
|
||||
(click)="activateAndScroll(idx, methIdx)">
|
||||
{{method.summary}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import {RedocComponent, BaseComponent} from '../base';
|
||||
import {redocEvents} from '../../events';
|
||||
|
||||
import {NgZone, ChangeDetectionStrategy} from 'angular2/core';
|
||||
import {NgZone, ChangeDetectionStrategy, ElementRef} from 'angular2/core';
|
||||
import {document} from 'angular2/src/facade/browser';
|
||||
import {BrowserDomAdapter} from 'angular2/platform/browser';
|
||||
import {global} from 'angular2/src/facade/lang';
|
||||
|
@ -27,13 +27,15 @@ const INVIEW_POSITION = {
|
|||
changeDetection: ChangeDetectionStrategy.Default
|
||||
})
|
||||
export default class SideMenu extends BaseComponent {
|
||||
constructor(schemaMgr, adapter, zone, redoc) {
|
||||
constructor(schemaMgr, elementRef, adapter, zone, redoc) {
|
||||
super(schemaMgr);
|
||||
this.zone = zone;
|
||||
this.adapter = adapter;
|
||||
this.redoc = redoc;
|
||||
|
||||
this.scrollParent = this.redoc.scrollParent;
|
||||
this.mobileNav = adapter.querySelector(elementRef.nativeElement, '.mobile-nav');
|
||||
this.resourcesNav = adapter.querySelector(elementRef.nativeElement, '#resources-nav');
|
||||
|
||||
// for some reason constructor is not run inside zone
|
||||
// as workaround running it manually
|
||||
|
@ -45,6 +47,9 @@ export default class SideMenu extends BaseComponent {
|
|||
this.prevOffsetY = null;
|
||||
|
||||
redocEvents.bootstrapped.subscribe(() => this.hashScroll());
|
||||
|
||||
this.activeCatCaption = '';
|
||||
this.activeItemCaption = '';
|
||||
}
|
||||
|
||||
scrollY() {
|
||||
|
@ -65,7 +70,12 @@ export default class SideMenu extends BaseComponent {
|
|||
|
||||
bindEvents() {
|
||||
this.prevOffsetY = this.scrollY();
|
||||
this.scrollYOffset = this.redoc.options.scrollYOffset;
|
||||
|
||||
//decorate option.scrollYOffset to account mobile nav
|
||||
this.scrollYOffset = () => {
|
||||
let mobileNavOffset = this.mobileNav.clientHeight;
|
||||
return this.redoc.options.scrollYOffset() + mobileNavOffset;
|
||||
};
|
||||
this._cancel = {};
|
||||
this._cancel.scroll = this.adapter.onAndCancel(this.scrollParent, 'scroll', () => { this.scrollHandler(); });
|
||||
this._cancel.hash = this.adapter.onAndCancel(global, 'hashchange', evt => this.hashScroll(evt));
|
||||
|
@ -77,6 +87,9 @@ export default class SideMenu extends BaseComponent {
|
|||
}
|
||||
|
||||
activateAndScroll(idx, methodIdx) {
|
||||
if (this.mobileMode()) {
|
||||
this.toggleMobileNav();
|
||||
}
|
||||
this.activate(idx, methodIdx);
|
||||
this.scrollToActive();
|
||||
}
|
||||
|
@ -98,6 +111,10 @@ export default class SideMenu extends BaseComponent {
|
|||
|
||||
activate(catIdx, methodIdx) {
|
||||
let menu = this.data.menu;
|
||||
|
||||
this.activeCatCaption = '';
|
||||
this.activeItemCaption = '';
|
||||
|
||||
menu[this.activeCatIdx].active = false;
|
||||
if (menu[this.activeCatIdx].methods.length) {
|
||||
if (this.activeMethodIdx >= 0) {
|
||||
|
@ -108,11 +125,13 @@ export default class SideMenu extends BaseComponent {
|
|||
this.activeCatIdx = catIdx;
|
||||
this.activeMethodIdx = methodIdx;
|
||||
menu[catIdx].active = true;
|
||||
this.activeCatCaption = menu[catIdx].name;
|
||||
this.activeMethodPtr = null;
|
||||
if (menu[catIdx].methods.length && (methodIdx > -1)) {
|
||||
let currentItem = menu[catIdx].methods[methodIdx];
|
||||
currentItem.active = true;
|
||||
this.activeMethodPtr = currentItem.pointer;
|
||||
this.activeItemCaption = currentItem.summary;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,8 +218,26 @@ export default class SideMenu extends BaseComponent {
|
|||
);
|
||||
}
|
||||
|
||||
mobileMode() {
|
||||
return this.mobileNav.clientHeight > 0;
|
||||
}
|
||||
|
||||
toggleMobileNav() {
|
||||
let dom = this.adapter;
|
||||
let overflowParent = (this.scrollParent === global) ? dom.defaultDoc().body : this.scrollParent;
|
||||
if (dom.hasStyle(this.resourcesNav, 'height')) {
|
||||
dom.removeStyle(this.resourcesNav, 'height');
|
||||
dom.removeStyle(overflowParent, 'overflow-y');
|
||||
} else {
|
||||
let viewportHeight = this.scrollParent.innerHeight || this.scrollParent.clientHeight;
|
||||
let height = viewportHeight - this.mobileNav.getBoundingClientRect().bottom;
|
||||
dom.setStyle(overflowParent, 'overflow-y', 'hidden');
|
||||
dom.setStyle(this.resourcesNav, 'height', height + 'px');
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
this.changeActive(CHANGE.INITIAL);
|
||||
}
|
||||
}
|
||||
SideMenu.parameters = SideMenu.parameters.concat([[BrowserDomAdapter], [NgZone]]);
|
||||
SideMenu.parameters = SideMenu.parameters.concat([[ElementRef], [BrowserDomAdapter], [NgZone]]);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import '../../common/styles/variables';
|
||||
$mobile-menu-compact-breakpoint: 550px;
|
||||
|
||||
.menu-header {
|
||||
text-transform: uppercase;
|
||||
|
@ -9,7 +10,6 @@
|
|||
}
|
||||
|
||||
.menu-cat-header {
|
||||
//font-weight: bold;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
color: $side-menu-item-color;
|
||||
|
@ -24,6 +24,10 @@
|
|||
background-color: $side-menu-even-bg-color;
|
||||
}
|
||||
|
||||
.menu-cat .menu-cat-header.active {
|
||||
background-color: darken($side-menu-active-bg-color, 5%);
|
||||
}
|
||||
|
||||
.menu-subitems {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -61,7 +65,89 @@
|
|||
}
|
||||
}
|
||||
|
||||
.menu-cat-header.active, .menu-subitems li.active {
|
||||
background-color: $side-menu-active-bg-color !important;
|
||||
font-weight: bold;
|
||||
.menu-cat .menu-subitems {
|
||||
li.active, li.active:nth-of-type(even) {
|
||||
background-color: $side-menu-active-bg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: none;
|
||||
height: 3em;
|
||||
line-height: 3em;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #ccc;
|
||||
cursor: pointer;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 100 100" xml:space="preserve"><polygon fill="#010101" points="23.1 34.1 51.5 61.7 80 34.1 81.5 35 51.5 64.1 21.5 35 23.1 34.1 "/></svg>');
|
||||
background-size: 70%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
|
||||
float: right;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.menu-header {
|
||||
padding: 0 10px 0 20px;
|
||||
font-size: 0.95em;
|
||||
|
||||
@media (max-width: $mobile-menu-compact-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $side-menu-mobile-breakpoint) {
|
||||
.mobile-nav {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#resources-nav {
|
||||
height: 0;
|
||||
overflow-y: auto;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#resources-nav .menu-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.menu-subitems {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-tag {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.selected-endpoint:before {
|
||||
content: "/";
|
||||
padding: 0 2px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.selected-endpoint:empty:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selected-item-info {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
max-width: 350px;
|
||||
|
||||
@media (max-width: $mobile-menu-compact-breakpoint) {
|
||||
display: inline-block;
|
||||
padding: 0 20px;
|
||||
max-width: 80%;
|
||||
max-width: calc(100% - 4em);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict';
|
||||
import {Component, View, OnInit, OnDestroy, ChangeDetectionStrategy} from 'angular2/core';
|
||||
import {CORE_DIRECTIVES, JsonPipe} from 'angular2/common';
|
||||
import {CORE_DIRECTIVES, JsonPipe, AsyncPipe} from 'angular2/common';
|
||||
import SchemaManager from '../utils/SchemaManager';
|
||||
import JsonPointer from '../utils/JsonPointer';
|
||||
import {MarkedPipe, JsonPointerEscapePipe} from '../utils/pipes';
|
||||
|
@ -49,7 +49,7 @@ function snapshot(obj) {
|
|||
export function RedocComponent(options) {
|
||||
let inputs = safeConcat(options.inputs, commonInputs);
|
||||
let directives = safeConcat(options.directives, CORE_DIRECTIVES);
|
||||
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe]);
|
||||
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe, AsyncPipe]);
|
||||
|
||||
return function decorator(target) {
|
||||
|
||||
|
@ -104,7 +104,9 @@ export class BaseComponent {
|
|||
let dereferencedCache = {};
|
||||
|
||||
let resolve = (schema) => {
|
||||
let resolvedRef;
|
||||
if (schema && schema.$ref) {
|
||||
resolvedRef = schema.$ref;
|
||||
let resolved = this.schemaMgr.byPointer(schema.$ref);
|
||||
let baseName = JsonPointer.baseName(schema.$ref);
|
||||
if (!dereferencedCache[schema.$ref]) {
|
||||
|
@ -114,11 +116,12 @@ export class BaseComponent {
|
|||
} else {
|
||||
// for circular referenced save only title and type
|
||||
resolved = {
|
||||
title: resolved.title
|
||||
title: resolved.title,
|
||||
type: resolved.type
|
||||
};
|
||||
}
|
||||
|
||||
dereferencedCache[schema.$ref] = true;
|
||||
dereferencedCache[schema.$ref] = dereferencedCache[schema.$ref] ? dereferencedCache[schema.$ref] + 1 : 1;
|
||||
|
||||
resolved.title = resolved.title || baseName;
|
||||
|
||||
|
@ -133,7 +136,6 @@ export class BaseComponent {
|
|||
schema = schema.description ? {
|
||||
description: schema.description
|
||||
} : {};
|
||||
//for (var prop in schema) delete schema[prop];
|
||||
Object.assign(schema, resolved);
|
||||
}
|
||||
|
||||
|
@ -143,10 +145,11 @@ export class BaseComponent {
|
|||
schema[key] = resolve(value);
|
||||
}
|
||||
});
|
||||
if (resolvedRef) dereferencedCache[resolvedRef] = dereferencedCache[resolvedRef] ? dereferencedCache[resolvedRef] - 1 : 0;
|
||||
return schema;
|
||||
};
|
||||
|
||||
this.componentSchema = resolve(schema);
|
||||
this.componentSchema = resolve(schema, 1);
|
||||
}
|
||||
|
||||
joinAllOf(schema = this.componentSchema, opts) {
|
||||
|
|
|
@ -14,12 +14,17 @@ export default class OptionsManager {
|
|||
OptionsManager.prototype._instance = this;
|
||||
|
||||
this._defaults = {
|
||||
scrollYOffset: 0
|
||||
scrollYOffset: 0,
|
||||
disableLazySchemas: false
|
||||
};
|
||||
|
||||
this._options = {};
|
||||
}
|
||||
|
||||
static instance() {
|
||||
return new OptionsManager();
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this._options;
|
||||
}
|
||||
|
|
106
lib/utils/JsonFormatterPipe.js
Normal file
106
lib/utils/JsonFormatterPipe.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
'use strict';
|
||||
import {Pipe} from 'angular2/core';
|
||||
import {isBlank} from 'angular2/src/facade/lang';
|
||||
|
||||
var level = 1;
|
||||
const COLLAPSE_LEVEL = 2;
|
||||
|
||||
@Pipe({ name: 'jsonFormatter' })
|
||||
export class JsonFormatter {
|
||||
transform(value) {
|
||||
if (isBlank(value)) return value;
|
||||
return jsonToHTML(value);
|
||||
}
|
||||
}
|
||||
|
||||
function htmlEncode(t) {
|
||||
return t != null ? t.toString().replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>') : '';
|
||||
}
|
||||
|
||||
function decorateWithSpan(value, className) {
|
||||
return '<span class="' + className + '">' + htmlEncode(value) + '</span>';
|
||||
}
|
||||
|
||||
function valueToHTML(value) {
|
||||
var valueType = typeof value, output = '';
|
||||
if (value == null) {
|
||||
output += decorateWithSpan('null', 'type-null');
|
||||
}
|
||||
else if (value && value.constructor === Array) {
|
||||
level++;
|
||||
output += arrayToHTML(value);
|
||||
level--;
|
||||
}
|
||||
else if (valueType === 'object') {
|
||||
level++;
|
||||
output += objectToHTML(value);
|
||||
level--;
|
||||
}
|
||||
else if (valueType === 'number') {
|
||||
output += decorateWithSpan(value, 'type-number');
|
||||
}
|
||||
else if (valueType === 'string') {
|
||||
if (/^(http|https):\/\/[^\\s]+$/.test(value)) {
|
||||
output += decorateWithSpan('"', 'type-string') + '<a href="' + value + '">' + htmlEncode(value) + '</a>' + decorateWithSpan('"', 'type-string');
|
||||
} else {
|
||||
output += decorateWithSpan('"' + value + '"', 'type-string');
|
||||
}
|
||||
} else if (valueType === 'boolean') {
|
||||
output += decorateWithSpan(value, 'type-boolean');
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function arrayToHTML(json) {
|
||||
var collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : '';
|
||||
var i, length;
|
||||
var output = '<div class="collapser"></div>[<span class="ellipsis"></span><ul class="array collapsible">';
|
||||
var hasContents = false;
|
||||
for (i = 0, length = json.length; i < length; i++) {
|
||||
hasContents = true;
|
||||
output += '<li><div class="hoverable ' + collapsed + '">';
|
||||
output += valueToHTML(json[i]);
|
||||
if (i < length - 1) {
|
||||
output += ',';
|
||||
}
|
||||
output += '</div></li>';
|
||||
}
|
||||
output += '</ul>]';
|
||||
if (!hasContents) {
|
||||
output = '[ ]';
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function objectToHTML(json) {
|
||||
var collapsed = level > COLLAPSE_LEVEL ? 'collapsed' : '';
|
||||
var i, key, length, keys = Object.keys(json);
|
||||
var output = '<div class="collapser"></div>{<span class="ellipsis"></span><ul class="obj collapsible">';
|
||||
var hasContents = false;
|
||||
for (i = 0, length = keys.length; i < length; i++) {
|
||||
key = keys[i];
|
||||
hasContents = true;
|
||||
output += '<li><div class="hoverable ' + collapsed + '">';
|
||||
output += '<span class="property">' + htmlEncode(key) + '</span>: ';
|
||||
output += valueToHTML(json[key]);
|
||||
if (i < length - 1) {
|
||||
output += ',';
|
||||
}
|
||||
output += '</div></li>';
|
||||
}
|
||||
output += '</ul>}';
|
||||
if (!hasContents) {
|
||||
output = '{ }';
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
function jsonToHTML(json) {
|
||||
level = 1;
|
||||
var output = '';
|
||||
output += '<div class="redoc-json">';
|
||||
output += valueToHTML(json);
|
||||
output += '</div>';
|
||||
return output;
|
||||
}
|
|
@ -62,6 +62,6 @@ export class MarkedPipe {
|
|||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
|
||||
}
|
||||
return marked(value);
|
||||
return `<span class="redoc-markdown-block">${marked(value)}</span>`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"description": "Swagger-generated API Reference Documentation",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Rebilly/ReDoc"
|
||||
},
|
||||
"main": "dist/redoc.full.min.js",
|
||||
"main": "dist/redoc.min.js",
|
||||
"scripts": {
|
||||
"test": "gulp lint && ./build/run_tests.sh",
|
||||
"prepublish": "gulp build",
|
||||
|
|
|
@ -65,7 +65,7 @@ function verifyNoBrowserErrors() {
|
|||
function scrollToEl(selector) {
|
||||
let script = `
|
||||
document.querySelector('${selector}').scrollIntoView(true);
|
||||
window.scrollBy(0, 10);
|
||||
window.scrollBy(0, 200);
|
||||
`;
|
||||
|
||||
return browser.driver.executeScript(script);
|
||||
|
|
|
@ -10,12 +10,12 @@
|
|||
</redoc>
|
||||
|
||||
<!-- ReDoc built file with all dependencies included -->
|
||||
<script src="dist/redoc.full.min.js"> </script>
|
||||
<script src="dist/redoc.min.js"> </script>
|
||||
<script>
|
||||
window.redocError = null;
|
||||
/* init redoc */
|
||||
var url = window.location.search.substr(5) || 'swagger.json';
|
||||
Redoc.init(decodeURIComponent(url)).then(function() {}, function(err) {
|
||||
Redoc.init(decodeURIComponent(url), {disableLazySchemas: true}).then(function() {}, function(err) {
|
||||
window.redocError = err;
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -44,7 +44,8 @@ describe('Scroll sync', () => {
|
|||
|
||||
it('should update active menu entries on page scroll', () => {
|
||||
scrollToEl('[tag="store"]').then(function() {
|
||||
expect($('.menu-cat-header.active').getText()).toBe('STORE');
|
||||
expect($('.menu-cat-header.active').getInnerHtml()).toContain('store');
|
||||
expect($('.selected-tag').getInnerHtml()).toContain('store');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,13 @@ export function getChildDebugElement(parent, tagName) {
|
|||
});
|
||||
}
|
||||
|
||||
/** Gets a child DebugElement by Component Type. */
|
||||
export function getChildDebugElementByType(parent, type) {
|
||||
return parent.query(debugEl => {
|
||||
return debugEl.componentInstance instanceof type;
|
||||
});
|
||||
}
|
||||
|
||||
/** Gets a child DebugElements by tag name. */
|
||||
export function getChildDebugElementAll(parent, tagName) {
|
||||
return parent.queryAll(debugEl => {
|
||||
|
|
|
@ -85,12 +85,12 @@ describe('Pipes', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
unmarked = 'test\n';
|
||||
marked = '<p>test</p>\n';
|
||||
marked = '<span class="redoc-markdown-block"><p>test</p>\n</span>';
|
||||
pipe = new MarkedPipe();
|
||||
});
|
||||
|
||||
describe('MarkedPipe transform', () => {
|
||||
it('should escpae pointer', () => {
|
||||
it('should wrap in markdown span', () => {
|
||||
var val = pipe.transform(unmarked);
|
||||
val.should.be.equal(marked);
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user