mirror of
https://github.com/Redocly/redoc.git
synced 2025-06-05 05:23:07 +03:00
Merge commit 'cf6b03c2ae1ff7cb8e2a81b215daae20db6da32a' into releases
This commit is contained in:
commit
d8224f95a4
|
@ -94,6 +94,9 @@ For npm:
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
### Security Definition location
|
||||||
|
You can inject Security Definitions widget into any place of your specification `description`. Check out details [here](docs/security-definitions-injection.md).
|
||||||
|
|
||||||
### Swagger vendor extensions
|
### Swagger vendor extensions
|
||||||
ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions):
|
ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions):
|
||||||
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
||||||
|
@ -132,11 +135,7 @@ Redoc.init('http://petstore.swagger.io/v2/swagger.json', {
|
||||||
`cd ReDoc`
|
`cd ReDoc`
|
||||||
- Install dependencies
|
- Install dependencies
|
||||||
`npm install`
|
`npm install`
|
||||||
- *(Temporary step, will be obsolete after fixing #97)* Compile CSS
|
- _(optional)_ Replace `demo/swagger.yaml` with your own schema
|
||||||
```bash
|
|
||||||
npm run build:sass
|
|
||||||
```
|
|
||||||
- _(optional)_ Replace `demo/swagger.json` with your own schema
|
|
||||||
- Start the server
|
- Start the server
|
||||||
`npm start`
|
`npm start`
|
||||||
- Open `http://localhost:9000`
|
- Open `http://localhost:9000`
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
|
const ForkCheckerPlugin = require('awesome-typescript-loader').ForkCheckerPlugin;
|
||||||
|
const StringReplacePlugin = require("string-replace-webpack-plugin");
|
||||||
|
|
||||||
const root = require('./helpers').root;
|
const root = require('./helpers').root;
|
||||||
const VERSION = JSON.stringify(require('../package.json').version);
|
const VERSION = JSON.stringify(require('../package.json').version);
|
||||||
|
@ -62,6 +63,28 @@ module.exports = {
|
||||||
exclude: [
|
exclude: [
|
||||||
/node_modules/
|
/node_modules/
|
||||||
]
|
]
|
||||||
|
}, {
|
||||||
|
enforce: 'pre',
|
||||||
|
test: /\.ts$/,
|
||||||
|
exclude: [
|
||||||
|
/node_modules/
|
||||||
|
],
|
||||||
|
loader: StringReplacePlugin.replace({
|
||||||
|
replacements: [
|
||||||
|
{
|
||||||
|
pattern: /styleUrls:\s*\[\s*'([\w\.\/-]*)\.css'\s*\][\s,]*$/m,
|
||||||
|
replacement: function (match, p1, offset, string) {
|
||||||
|
return `styleUrls: ['${p1}.scss'],`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /(\.\/components\/Redoc\/redoc-initial-styles\.css)/m,
|
||||||
|
replacement: function (match, p1, offset, string) {
|
||||||
|
return p1.replace('.css', '.scss');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
}, {
|
}, {
|
||||||
test: /\.ts$/,
|
test: /\.ts$/,
|
||||||
loaders: [
|
loaders: [
|
||||||
|
@ -70,13 +93,16 @@ module.exports = {
|
||||||
],
|
],
|
||||||
exclude: [/\.(spec|e2e)\.ts$/]
|
exclude: [/\.(spec|e2e)\.ts$/]
|
||||||
}, {
|
}, {
|
||||||
test: /lib[\\\/].*\.css$/,
|
test: /lib[\\\/].*\.scss$/,
|
||||||
loaders: ['raw-loader'],
|
loaders: ['raw-loader', "sass"],
|
||||||
exclude: [/redoc-initial-styles\.css$/]
|
exclude: [/redoc-initial-styles\.scss$/]
|
||||||
|
}, {
|
||||||
|
test: /\.scss$/,
|
||||||
|
loaders: ['style', 'css?-import', "sass"],
|
||||||
|
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.scss$/]
|
||||||
}, {
|
}, {
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
loaders: ['style', 'css?-import'],
|
loaders: ['style', 'css?-import'],
|
||||||
exclude: [/lib[\\\/](?!.*redoc-initial-styles).*\.css$/]
|
|
||||||
}, {
|
}, {
|
||||||
test: /\.html$/,
|
test: /\.html$/,
|
||||||
loader: 'raw-loader'
|
loader: 'raw-loader'
|
||||||
|
@ -97,6 +123,8 @@ module.exports = {
|
||||||
'AOT': IS_PRODUCTION
|
'AOT': IS_PRODUCTION
|
||||||
}),
|
}),
|
||||||
|
|
||||||
new ForkCheckerPlugin()
|
new ForkCheckerPlugin(),
|
||||||
|
|
||||||
|
new StringReplacePlugin()
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,18 +12,6 @@ const BANNER =
|
||||||
|
|
||||||
const IS_MODULE = process.env.IS_MODULE != null;
|
const IS_MODULE = process.env.IS_MODULE != null;
|
||||||
|
|
||||||
const TS_RULE = {
|
|
||||||
test: /\.ts$/,
|
|
||||||
loader: 'awesome-typescript-loader',
|
|
||||||
exclude: /(node_modules)/,
|
|
||||||
};
|
|
||||||
//
|
|
||||||
// if (IS_MODULE) {
|
|
||||||
// TS_RULE.query = {
|
|
||||||
// noEmitHelpers: false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
context: root(),
|
context: root(),
|
||||||
devtool: 'source-map',
|
devtool: 'source-map',
|
||||||
|
@ -70,7 +58,14 @@ const config = {
|
||||||
exclude: [
|
exclude: [
|
||||||
/node_modules/
|
/node_modules/
|
||||||
]
|
]
|
||||||
}, TS_RULE, {
|
}, {
|
||||||
|
test: /node_modules\/.*\.ngfactory\.ts$/,
|
||||||
|
loader: 'awesome-typescript-loader'
|
||||||
|
}, {
|
||||||
|
test: /\.ts$/,
|
||||||
|
loader: 'awesome-typescript-loader',
|
||||||
|
exclude: /(node_modules)/,
|
||||||
|
}, {
|
||||||
test: /lib[\\\/].*\.css$/,
|
test: /lib[\\\/].*\.css$/,
|
||||||
loaders: ['raw-loader'],
|
loaders: ['raw-loader'],
|
||||||
exclude: [/redoc-initial-styles\.css$/]
|
exclude: [/redoc-initial-styles\.css$/]
|
||||||
|
|
|
@ -26,7 +26,6 @@ info:
|
||||||
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
||||||
And that allows cross-domain communication from the browser.
|
And that allows cross-domain communication from the browser.
|
||||||
All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site.
|
All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site.
|
||||||
|
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
title: Swagger Petstore
|
title: Swagger Petstore
|
||||||
termsOfService: 'http://swagger.io/terms/'
|
termsOfService: 'http://swagger.io/terms/'
|
||||||
|
|
|
@ -49,7 +49,7 @@ info:
|
||||||
|
|
||||||
### Tag Object vendor extensions
|
### Tag Object vendor extensions
|
||||||
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
|
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
|
||||||
#### x-traitTag
|
#### x-traitTag [DEPRECATED]
|
||||||
| Field Name | Type | Description |
|
| Field Name | Type | Description |
|
||||||
| :------------- | :------: | :---------- |
|
| :------------- | :------: | :---------- |
|
||||||
| x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
|
| x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
|
||||||
|
|
19
docs/security-definitions-injection.md
Normal file
19
docs/security-definitions-injection.md
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# Injection security definitions
|
||||||
|
|
||||||
|
You can inject Security Definitions widget into any place of your specification `description`:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
...
|
||||||
|
# Authorization
|
||||||
|
Some description
|
||||||
|
|
||||||
|
<!-- ReDoc-Inject: <security-definitions> -->
|
||||||
|
...
|
||||||
|
```
|
||||||
|
Inject instruction is wrapped into HTML comment so it is **visible only in ReDoc**. It won't be visible e.g. in SwaggerUI.
|
||||||
|
|
||||||
|
# Default behavior
|
||||||
|
If injection tag is not found in the description it will be appended to the end
|
||||||
|
of description under `Authentication` header.
|
||||||
|
|
||||||
|
If `Authentication` header is already present in the description, Security Definitions won't be inserted and rendered at all.
|
13
lib/app.module.ts
Normal file
13
lib/app.module.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { RedocModule } from './redoc.module';
|
||||||
|
import { Redoc } from './components/index';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [ BrowserModule, RedocModule ],
|
||||||
|
bootstrap: [ Redoc ],
|
||||||
|
exports: [ Redoc ]
|
||||||
|
})
|
||||||
|
export class AppModule {
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
import { RedocModule } from './redoc.module';
|
import { AppModule } from './app.module';
|
||||||
|
|
||||||
export function bootstrapRedoc() {
|
export function bootstrapRedoc() {
|
||||||
return platformBrowserDynamic().bootstrapModule(RedocModule);
|
return platformBrowserDynamic().bootstrapModule(AppModule);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { platformBrowser } from '@angular/platform-browser';
|
import { platformBrowser } from '@angular/platform-browser';
|
||||||
import { RedocModuleNgFactory } from './redoc.module.ngfactory';
|
import { AppModuleNgFactory } from './app.module.ngfactory';
|
||||||
|
|
||||||
export function bootstrapRedoc() {
|
export function bootstrapRedoc() {
|
||||||
return platformBrowser().bootstrapModuleFactory(RedocModuleNgFactory);
|
return platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,5 @@
|
||||||
<span *ngIf="!info.license.url"> {{info.license.name}} </span>
|
<span *ngIf="!info.license.url"> {{info.license.name}} </span>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p *ngIf="info.description" class="redoc-markdown-block" [innerHtml]="info['x-redoc-html-description'] | safe"> </p>
|
<dynamic-ng2-viewer [html]="info['x-redoc-html-description']"></dynamic-ng2-viewer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
|
|
||||||
import { ApiInfo } from './api-info';
|
import { ApiInfo } from './api-info';
|
||||||
import { SpecManager } from '../../utils/SpecManager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
|
|
||||||
describe('Redoc components', () => {
|
describe('Redoc components', () => {
|
||||||
describe('ApiInfo Component', () => {
|
describe('ApiInfo Component', () => {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { OptionsService, MenuService } from '../../services/index';
|
||||||
export class ApiInfo extends BaseComponent implements OnInit {
|
export class ApiInfo extends BaseComponent implements OnInit {
|
||||||
info: any = {};
|
info: any = {};
|
||||||
specUrl: String;
|
specUrl: String;
|
||||||
constructor(specMgr:SpecManager, private optionsService:OptionsService, private menuServ: MenuService) {
|
constructor(specMgr: SpecManager, private optionsService: OptionsService) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
|
|
||||||
import { ApiLogo } from './api-logo';
|
import { ApiLogo } from './api-logo';
|
||||||
import { SpecManager } from '../../utils/SpecManager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
|
|
||||||
|
|
||||||
describe('Redoc components', () => {
|
describe('Redoc components', () => {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { Component, ElementRef, ViewContainerRef, OnDestroy, Input,
|
||||||
|
|
||||||
import { JsonSchema } from './json-schema';
|
import { JsonSchema } from './json-schema';
|
||||||
import { OptionsService } from '../../services/options.service';
|
import { OptionsService } from '../../services/options.service';
|
||||||
import { SpecManager } from '../../utils/SpecManager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
|
|
||||||
var cache = {};
|
var cache = {};
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
var componentFactory = this.resolver.resolveComponentFactory(JsonSchema);
|
var componentFactory = this.resolver.resolveComponentFactory(JsonSchema);
|
||||||
let contextInjector = this.location.parentInjector;
|
let contextInjector = this.location.parentInjector;
|
||||||
let compRef = this.location.createComponent(componentFactory, null, contextInjector, null);
|
let compRef = this.location.createComponent(componentFactory, null, contextInjector, null);
|
||||||
this.initComponent(compRef.instance);
|
this.projectComponentInputs(compRef.instance);
|
||||||
this._renderer.setElementAttribute(compRef.location.nativeElement, 'class', this.location.element.nativeElement.className);
|
this._renderer.setElementAttribute(compRef.location.nativeElement, 'class', this.location.element.nativeElement.className);
|
||||||
compRef.changeDetectorRef.detectChanges();
|
compRef.changeDetectorRef.detectChanges();
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
@ -58,24 +58,22 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
this.pointer = this.normalizePointer();
|
this.pointer = this.normalizePointer();
|
||||||
if (cache[this.pointer]) {
|
if (cache[this.pointer]) {
|
||||||
let compRef = cache[this.pointer];
|
let compRef = cache[this.pointer];
|
||||||
setTimeout( ()=> {
|
let $element = compRef.location.nativeElement;
|
||||||
let $element = compRef.location.nativeElement;
|
|
||||||
|
|
||||||
// skip caching view with descendant schemas
|
// skip caching view with descendant schemas
|
||||||
// as it needs attached controller
|
// as it needs attached controller
|
||||||
if (!this.disableLazy && (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas)) {
|
if (!this.disableLazy && (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas)) {
|
||||||
this._loadAfterSelf();
|
this._loadAfterSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
insertAfter($element.cloneNode(true), this.elementRef.nativeElement);
|
insertAfter($element.cloneNode(true), this.elementRef.nativeElement);
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
cache[this.pointer] = this._loadAfterSelf();
|
cache[this.pointer] = this._loadAfterSelf();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initComponent(instance:JsonSchema) {
|
projectComponentInputs(instance:JsonSchema) {
|
||||||
Object.assign(instance, this);
|
Object.assign(instance, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { getChildDebugElement } from '../../../tests/helpers';
|
||||||
|
|
||||||
|
|
||||||
import { JsonSchema } from './json-schema';
|
import { JsonSchema } from './json-schema';
|
||||||
import { SpecManager } from '../../utils/SpecManager';;
|
import { SpecManager } from '../../utils/spec-manager';;
|
||||||
|
|
||||||
describe('Redoc components', () => {
|
describe('Redoc components', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
import { getChildDebugElement } from '../../../tests/helpers';
|
import { getChildDebugElement } from '../../../tests/helpers';
|
||||||
|
|
||||||
import { Method } from './method';
|
import { Method } from './method';
|
||||||
import { SpecManager } from '../../utils/SpecManager';;
|
import { SpecManager } from '../../utils/spec-manager';;
|
||||||
|
|
||||||
describe('Redoc components', () => {
|
describe('Redoc components', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { getChildDebugElement } from '../../../tests/helpers';
|
||||||
|
|
||||||
|
|
||||||
import { MethodsList } from './methods-list';
|
import { MethodsList } from './methods-list';
|
||||||
import { SpecManager } from '../../utils/SpecManager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
|
|
||||||
describe('Redoc components', () => {
|
describe('Redoc components', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -4,12 +4,15 @@
|
||||||
redoc.loading {
|
redoc.loading {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
min-height:350px;
|
min-height: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotate {
|
@keyframes rotate {
|
||||||
0% {transform: rotate(0deg)}
|
0% {
|
||||||
100% {transform: rotate(360deg)}
|
transform: rotate(0deg); }
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redoc.loading:before {
|
redoc.loading:before {
|
||||||
|
@ -33,7 +36,7 @@ redoc.loading:before {
|
||||||
}
|
}
|
||||||
|
|
||||||
redoc.loading:after {
|
redoc.loading:after {
|
||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="512" height="512" viewBox="0 0 512 512"><g></g><path d="M275.682 147.999c0 10.864-8.837 19.661-19.682 19.661v0c-10.875 0-19.681-8.796-19.681-19.661v-96.635c0-10.885 8.806-19.661 19.681-19.661v0c10.844 0 19.682 8.776 19.682 19.661v96.635z" fill="#0033a0"/><path d="M275.682 460.615c0 10.865-8.837 19.682-19.682 19.682v0c-10.875 0-19.681-8.817-19.681-19.682v-96.604c0-10.885 8.806-19.681 19.681-19.681v0c10.844 0 19.682 8.796 19.682 19.682v96.604z" fill="#0033a0"/><path d="M147.978 236.339c10.885 0 19.681 8.755 19.681 19.641v0c0 10.885-8.796 19.702-19.681 19.702h-96.624c-10.864 0-19.661-8.817-19.661-19.702v0c0-10.885 8.796-19.641 19.661-19.641h96.624z" fill="#0033a0"/><path d="M460.615 236.339c10.865 0 19.682 8.755 19.682 19.641v0c0 10.885-8.817 19.702-19.682 19.702h-96.584c-10.885 0-19.722-8.817-19.722-19.702v0c0-10.885 8.837-19.641 19.722-19.641h96.584z" fill="#0033a0"/><path d="M193.546 165.703c7.69 7.66 7.68 20.142 0 27.822v0c-7.701 7.701-20.162 7.701-27.853 0.020l-68.311-68.322c-7.68-7.701-7.68-20.142 0-27.863v0c7.68-7.68 20.121-7.68 27.822 0l68.342 68.342z" fill="#0033a0"/><path d="M414.597 386.775c7.7 7.68 7.7 20.163 0.021 27.863v0c-7.7 7.659-20.142 7.659-27.843-0.062l-68.311-68.26c-7.68-7.7-7.68-20.204 0-27.863v0c7.68-7.7 20.163-7.7 27.842 0l68.291 68.322z" fill="#0033a0"/><path d="M165.694 318.464c7.69-7.7 20.153-7.7 27.853 0v0c7.68 7.659 7.69 20.163 0 27.863l-68.342 68.322c-7.67 7.659-20.142 7.659-27.822-0.062v0c-7.68-7.68-7.68-20.122 0-27.801l68.311-68.322z" fill="#0033a0"/><path d="M386.775 97.362c7.7-7.68 20.142-7.68 27.822 0v0c7.7 7.68 7.7 20.183 0.021 27.863l-68.322 68.311c-7.68 7.68-20.163 7.68-27.843-0.020v0c-7.68-7.68-7.68-20.162 0-27.822l68.322-68.332z" fill="#0033a0"/></svg>');
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="512" height="512" viewBox="0 0 512 512"><g></g><path d="M275.682 147.999c0 10.864-8.837 19.661-19.682 19.661v0c-10.875 0-19.681-8.796-19.681-19.661v-96.635c0-10.885 8.806-19.661 19.681-19.661v0c10.844 0 19.682 8.776 19.682 19.661v96.635z" fill="#0033a0"/><path d="M275.682 460.615c0 10.865-8.837 19.682-19.682 19.682v0c-10.875 0-19.681-8.817-19.681-19.682v-96.604c0-10.885 8.806-19.681 19.681-19.681v0c10.844 0 19.682 8.796 19.682 19.682v96.604z" fill="#0033a0"/><path d="M147.978 236.339c10.885 0 19.681 8.755 19.681 19.641v0c0 10.885-8.796 19.702-19.681 19.702h-96.624c-10.864 0-19.661-8.817-19.661-19.702v0c0-10.885 8.796-19.641 19.661-19.641h96.624z" fill="#0033a0"/><path d="M460.615 236.339c10.865 0 19.682 8.755 19.682 19.641v0c0 10.885-8.817 19.702-19.682 19.702h-96.584c-10.885 0-19.722-8.817-19.722-19.702v0c0-10.885 8.837-19.641 19.722-19.641h96.584z" fill="#0033a0"/><path d="M193.546 165.703c7.69 7.66 7.68 20.142 0 27.822v0c-7.701 7.701-20.162 7.701-27.853 0.020l-68.311-68.322c-7.68-7.701-7.68-20.142 0-27.863v0c7.68-7.68 20.121-7.68 27.822 0l68.342 68.342z" fill="#0033a0"/><path d="M414.597 386.775c7.7 7.68 7.7 20.163 0.021 27.863v0c-7.7 7.659-20.142 7.659-27.843-0.062l-68.311-68.26c-7.68-7.7-7.68-20.204 0-27.863v0c7.68-7.7 20.163-7.7 27.842 0l68.291 68.322z" fill="#0033a0"/><path d="M165.694 318.464c7.69-7.7 20.153-7.7 27.853 0v0c7.68 7.659 7.69 20.163 0 27.863l-68.342 68.322c-7.67 7.659-20.142 7.659-27.822-0.062v0c-7.68-7.68-7.68-20.122 0-27.801l68.311-68.322z" fill="#0033a0"/><path d="M386.775 97.362c7.7-7.68 20.142-7.68 27.822 0v0c7.7 7.68 7.7 20.183 0.021 27.863l-68.322 68.311c-7.68 7.68-20.163 7.68-27.843-0.020v0c-7.68-7.68-7.68-20.162 0-27.822l68.322-68.332z" fill="#0033a0"/></svg>');
|
||||||
animation: 2s rotate linear infinite;
|
animation: 2s rotate linear infinite;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
|
@ -51,20 +54,3 @@ redoc.loading:after {
|
||||||
redoc.loading-remove:before, redoc.loading-remove:after {
|
redoc.loading-remove:before, redoc.loading-remove:after {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redoc-error {
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
color: #cc0000;
|
|
||||||
|
|
||||||
> h2 {
|
|
||||||
color: #cc0000;
|
|
||||||
font-size: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.redoc-error-details {
|
|
||||||
max-width: 750px;
|
|
||||||
margin: 0 auto;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<div class="redoc-wrap" *ngIf="specLoaded">
|
<div class="redoc-error" *ngIf="error">
|
||||||
|
<h1>Oops... ReDoc failed to render this spec</h1>
|
||||||
|
<div class='redoc-error-details'>{{error.message}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="redoc-wrap" *ngIf="specLoaded && !error">
|
||||||
<div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
|
<div class="menu-content" sticky-sidebar [scrollParent]="options.$scrollParent" [scrollYOffset]="options.scrollYOffset">
|
||||||
<api-logo> </api-logo>
|
<api-logo> </api-logo>
|
||||||
<side-menu> </side-menu>
|
<side-menu> </side-menu>
|
||||||
|
|
|
@ -256,3 +256,19 @@ footer {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.redoc-error {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
color: #cc0000;
|
||||||
|
> h2 {
|
||||||
|
color: #cc0000;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.redoc-error-details {
|
||||||
|
max-width: 750px;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { Redoc } from './redoc';
|
import { Redoc } from './redoc';
|
||||||
import { SpecManager } from '../../utils/SpecManager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
import { OptionsService } from '../../services/index';
|
import { OptionsService } from '../../services/index';
|
||||||
|
|
||||||
let optsMgr:OptionsService;
|
let optsMgr:OptionsService;
|
||||||
|
@ -46,123 +46,6 @@ describe('Redoc components', () => {
|
||||||
fixture.destroy();
|
fixture.destroy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// describe('Redoc init', () => {
|
|
||||||
// let dom = new BrowserDomAdapter();
|
|
||||||
// let elem;
|
|
||||||
// beforeEach(() => {
|
|
||||||
// elem = dom.createElement('redoc');
|
|
||||||
// dom.defaultDoc().body.appendChild(elem);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// afterEach(() => {
|
|
||||||
// dom.defaultDoc().body.removeChild(elem);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should return promise', () => {
|
|
||||||
// let res = Redoc.init().catch(() => {/**/});
|
|
||||||
// res.should.be.instanceof(Promise);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should hide loading animation and display message in case of error', async(() => {
|
|
||||||
// spyOn(Redoc, 'hideLoadingAnimation').and.callThrough();
|
|
||||||
// spyOn(Redoc, 'displayError').and.callThrough();
|
|
||||||
// let res = Redoc.init();
|
|
||||||
// return res.catch(() => {
|
|
||||||
// expect(Redoc.hideLoadingAnimation).toHaveBeenCalled();
|
|
||||||
// expect(Redoc.displayError).toHaveBeenCalled();
|
|
||||||
// });
|
|
||||||
// }));
|
|
||||||
//
|
|
||||||
// //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.yml');
|
|
||||||
// res.then(() => { done(); }, () => {
|
|
||||||
// done.fail('Error handler should not been called');
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('Redoc destroy', () => {
|
|
||||||
// let builder;
|
|
||||||
// let fixture;
|
|
||||||
// let element;
|
|
||||||
// let dom;
|
|
||||||
// let destroySpy;
|
|
||||||
//
|
|
||||||
// beforeEach(async(inject([SpecManager, OptionsService, BrowserDomAdapter],
|
|
||||||
// ( specMgr, opts, _dom) => {
|
|
||||||
//
|
|
||||||
// optsMgr = opts;
|
|
||||||
// dom = _dom;
|
|
||||||
// return specMgr.load('/tests/schemas/extended-petstore.yml');
|
|
||||||
// })));
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// fixture = TestBed.createComponent(TestAppComponent);
|
|
||||||
// element = getChildDebugElement(fixture.debugElement, 'methods-list').nativeElement;
|
|
||||||
// destroySpy = jasmine.createSpy('spy');
|
|
||||||
// Redoc.appRef = <ComponentRef<any>>{
|
|
||||||
// destroy: destroySpy
|
|
||||||
// };
|
|
||||||
// fixture.detectChanges();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// afterEach(()=> {
|
|
||||||
// fixture.destroy();
|
|
||||||
// Redoc.appRef = null;
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should call componentRef.destroy', () => {
|
|
||||||
// Redoc.destroy();
|
|
||||||
// expect(destroySpy).toHaveBeenCalled();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should create new host element', () => {
|
|
||||||
// element.parentElement.removeChild(element);
|
|
||||||
// Redoc.destroy();
|
|
||||||
// expect(dom.query('redoc')).not.toBeNull();
|
|
||||||
// dom.query('redoc').should.not.be.equal(element);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should set to null appRef', () => {
|
|
||||||
// Redoc.destroy();
|
|
||||||
// expect(Redoc.appRef).toBeNull();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('Redoc autoInit', () => {
|
|
||||||
// const testURL = 'testurl';
|
|
||||||
// let dom = new BrowserDomAdapter();
|
|
||||||
// //let redocInitSpy;
|
|
||||||
// let elem: HTMLElement;
|
|
||||||
//
|
|
||||||
// beforeEach(() => {
|
|
||||||
// spyOn(Redoc, 'init').and.stub();
|
|
||||||
// elem = dom.createElement('redoc');
|
|
||||||
// dom.defaultDoc().body.appendChild(elem);
|
|
||||||
// dom.setAttribute(elem, 'spec-url', testURL);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should call Redoc.init with url from param spec-url', () => {
|
|
||||||
// Redoc.autoInit();
|
|
||||||
// expect(Redoc.init).toHaveBeenCalled();
|
|
||||||
// expect((<jasmine.Spy>Redoc.init).calls.argsFor(0)).toEqual([testURL]);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// it('should not call Redoc.init when spec-url param is not provided', () => {
|
|
||||||
// dom.removeAttribute(elem, 'spec-url');
|
|
||||||
// Redoc.autoInit();
|
|
||||||
// expect(Redoc.init).not.toHaveBeenCalled();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// afterEach(() => {
|
|
||||||
// (<jasmine.Spy>Redoc.init).and.callThrough();
|
|
||||||
// dom.defaultDoc().body.removeChild(elem);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Test component that contains a Redoc. */
|
/** Test component that contains a Redoc. */
|
||||||
|
|
|
@ -1,15 +1,23 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ElementRef, ComponentRef, ChangeDetectorRef, Input,
|
import { ElementRef,
|
||||||
Component, OnInit, ChangeDetectionStrategy} from '@angular/core';
|
ComponentRef,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Input,
|
||||||
|
Component,
|
||||||
|
OnInit,
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
HostBinding
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter';
|
import { BrowserDomAdapter as DOM } from '../../utils/browser-adapter';
|
||||||
import { BaseComponent } from '../base';
|
import { BaseComponent } from '../base';
|
||||||
|
|
||||||
import * as detectScollParent from 'scrollparent';
|
import * as detectScollParent from 'scrollparent';
|
||||||
|
|
||||||
import { SpecManager } from '../../utils/SpecManager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
import { OptionsService, RedocEventsService } from '../../services/index';
|
import { OptionsService, Hash, MenuService, AppStateService } from '../../services/index';
|
||||||
|
import { CustomErrorHandler } from '../../utils/';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'redoc',
|
selector: 'redoc',
|
||||||
|
@ -18,45 +26,29 @@ import { OptionsService, RedocEventsService } from '../../services/index';
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class Redoc extends BaseComponent implements OnInit {
|
export class Redoc extends BaseComponent implements OnInit {
|
||||||
static appRef: ComponentRef<any>;
|
|
||||||
static _preOptions: any;
|
static _preOptions: any;
|
||||||
specLoaded: boolean;
|
|
||||||
|
|
||||||
public options: any;
|
|
||||||
|
|
||||||
private element: any;
|
private element: any;
|
||||||
|
|
||||||
|
error: any;
|
||||||
|
specLoaded: boolean;
|
||||||
|
options: any;
|
||||||
|
|
||||||
@Input() specUrl: string;
|
@Input() specUrl: string;
|
||||||
|
@HostBinding('class.loading') specLoading: boolean = false;
|
||||||
|
@HostBinding('class.loading-remove') specLoadingRemove: boolean = false;
|
||||||
|
|
||||||
// TODO: refactor in separate component/service
|
constructor(
|
||||||
showLoadingAnimation() {
|
specMgr: SpecManager,
|
||||||
DOM.addClass(this.element, 'loading');
|
optionsMgr: OptionsService,
|
||||||
}
|
elementRef: ElementRef,
|
||||||
|
private changeDetector: ChangeDetectorRef,
|
||||||
hideLoadingAnimation() {
|
private appState: AppStateService
|
||||||
DOM.addClass(this.element, 'loading-remove');
|
) {
|
||||||
setTimeout(() => {
|
|
||||||
DOM.removeClass(this.element, 'loading-remove');
|
|
||||||
DOM.removeClass(this.element, 'loading');
|
|
||||||
}, 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
static displayError(err, elem?) {
|
|
||||||
let redocEl = elem || DOM.query('redoc');
|
|
||||||
if (!redocEl) return;
|
|
||||||
let heading = 'Oops... ReDoc failed to render this spec';
|
|
||||||
let details = err.message;
|
|
||||||
let erroHtml = `<div class="redoc-error">
|
|
||||||
<h1>${heading}</h1>
|
|
||||||
<div class='redoc-error-details'>${details}</div>`;
|
|
||||||
redocEl.innerHTML = erroHtml;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(specMgr: SpecManager, optionsMgr:OptionsService, elementRef:ElementRef,
|
|
||||||
public events:RedocEventsService, private changeDetector: ChangeDetectorRef) {
|
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
// merge options passed before init
|
// merge options passed before init
|
||||||
optionsMgr.options = Redoc._preOptions || {};
|
optionsMgr.options = Redoc._preOptions || {};
|
||||||
|
|
||||||
this.element = elementRef.nativeElement;
|
this.element = elementRef.nativeElement;
|
||||||
//parse options (top level component doesn't support inputs)
|
//parse options (top level component doesn't support inputs)
|
||||||
optionsMgr.parseOptions( this.element );
|
optionsMgr.parseOptions( this.element );
|
||||||
|
@ -66,22 +58,46 @@ export class Redoc extends BaseComponent implements OnInit {
|
||||||
this.options = optionsMgr.options;
|
this.options = optionsMgr.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hideLoadingAnimation() {
|
||||||
|
this.specLoadingRemove = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.specLoadingRemove = true;
|
||||||
|
this.specLoading = false;
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
this.showLoadingAnimation();
|
this.specMgr.load(this.options.specUrl).catch(err => {
|
||||||
SpecManager.instance().load(this.options.specUrl).then(() => {
|
|
||||||
this.specLoaded = true;
|
|
||||||
this.changeDetector.markForCheck();
|
|
||||||
//this.changeDetector.detectChanges();
|
|
||||||
this.events.bootstrapped.next({});
|
|
||||||
this.hideLoadingAnimation();
|
|
||||||
}).catch((err) => {
|
|
||||||
this.hideLoadingAnimation();
|
|
||||||
Redoc.displayError(err, this.element);
|
|
||||||
throw err;
|
throw err;
|
||||||
})
|
});
|
||||||
|
|
||||||
|
this.specMgr.spec.subscribe((spec) => {
|
||||||
|
if (!spec) {
|
||||||
|
this.specLoading = true;
|
||||||
|
this.specLoaded = false;
|
||||||
|
} else {
|
||||||
|
this.specLoaded = true;
|
||||||
|
this.hideLoadingAnimation();
|
||||||
|
this.changeDetector.markForCheck();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.appState.error.subscribe(_err => {
|
||||||
|
if (!_err) return;
|
||||||
|
|
||||||
|
if (this.specLoading) {
|
||||||
|
this.specLoaded = true;
|
||||||
|
this.hideLoadingAnimation();
|
||||||
|
}
|
||||||
|
this.error = _err;
|
||||||
|
this.changeDetector.markForCheck();
|
||||||
|
setTimeout(() => {
|
||||||
|
this.changeDetector.detectChanges()
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
if (this.specUrl) {
|
if (this.specUrl) {
|
||||||
this.options.specUrl = this.specUrl;
|
this.options.specUrl = this.specUrl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { Component, ViewChildren, QueryList, EventEmitter, Input,
|
import { Component, ViewChildren, QueryList, Input,
|
||||||
ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core';
|
ChangeDetectionStrategy, OnInit, HostBinding } from '@angular/core';
|
||||||
|
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
|
||||||
import { BaseComponent, SpecManager } from '../base';
|
import { BaseComponent, SpecManager } from '../base';
|
||||||
import JsonPointer from '../../utils/JsonPointer';
|
import JsonPointer from '../../utils/JsonPointer';
|
||||||
import { Tabs } from '../../shared/components/index';
|
import { Tabs } from '../../shared/components/index';
|
||||||
import { RedocEventsService } from '../../services/index';
|
import { AppStateService } from '../../services/index';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'request-samples',
|
selector: 'request-samples',
|
||||||
|
@ -21,18 +23,17 @@ export class RequestSamples extends BaseComponent implements OnInit {
|
||||||
@HostBinding('attr.hidden') hidden;
|
@HostBinding('attr.hidden') hidden;
|
||||||
|
|
||||||
childTabs: Tabs;
|
childTabs: Tabs;
|
||||||
selectedLang: EventEmitter<any>;
|
selectedLang: Subject<any>;
|
||||||
samples: Array<any>;
|
samples: Array<any>;
|
||||||
|
|
||||||
constructor(specMgr:SpecManager, public events:RedocEventsService) {
|
constructor(specMgr:SpecManager, public appState:AppStateService) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
|
|
||||||
this.selectedLang = this.events.samplesLanguageChanged;
|
this.selectedLang = this.appState.samplesLanguage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
changeLangNotify(lang) {
|
changeLangNotify(lang) {
|
||||||
this.events.samplesLanguageChanged.next(lang);
|
this.selectedLang.next(lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
40
lib/components/SecurityDefinitions/security-definitions.html
Normal file
40
lib/components/SecurityDefinitions/security-definitions.html
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<div class="security-definition" *ngFor="let def of defs">
|
||||||
|
<h2 class="sharable-header" attr.section="section/Authentication/{{def.name}}">
|
||||||
|
<a class="share-link" href="#section/Authentication/{{def.name}}"></a>{{def.name}}</h2>
|
||||||
|
<div [innerHTML]="def.details.description | marked"></div>
|
||||||
|
<div class="redoc-markdown-block"> <!-- apply md styles to table -->
|
||||||
|
<table class="details">
|
||||||
|
<tr>
|
||||||
|
<th> Security scheme type: </th>
|
||||||
|
<td> {{def.details._displayType}} </td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="def.details.type === 'apiKey'">
|
||||||
|
<th> {{def.details.in}} parameter name:</th>
|
||||||
|
<td> {{def.details.name}} </td>
|
||||||
|
</tr>
|
||||||
|
<template [ngIf]="def.details.type === 'oauth2'">
|
||||||
|
<tr>
|
||||||
|
<th> OAuth2 Flow</th>
|
||||||
|
<td> {{def.details.flow}} </td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="def.details.flow === 'implicit' || def.details.flow === 'accessCode'">
|
||||||
|
<th> Authorization URL </th>
|
||||||
|
<td> {{def.details.authorizationUrl}} </td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="def.details.flow !== 'implicit'">
|
||||||
|
<th> Token URL </th>
|
||||||
|
<td> {{def.details.tokenUrl}} </td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
<template [ngIf]="def.details.type === 'oauth2'">
|
||||||
|
<h3> OAuth2 Scopes </h3>
|
||||||
|
<table class="scopes">
|
||||||
|
<tr *ngFor="let scopeName of def.details.scopes | keys">
|
||||||
|
<th> {{scopeName}} </th>
|
||||||
|
<td> {{def.details.scopes[scopeName]}} </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
36
lib/components/SecurityDefinitions/security-definitions.scss
Normal file
36
lib/components/SecurityDefinitions/security-definitions.scss
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
@import '../../shared/styles/variables';
|
||||||
|
|
||||||
|
.security-definition:not(:last-of-type) {
|
||||||
|
border-bottom: 1px solid rgba($text-color, .3);
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
color: $secondary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host h2 {
|
||||||
|
padding-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 1em 0;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host .redoc-markdown-block table {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.details th, table.details td {
|
||||||
|
font-weight: bold;
|
||||||
|
width: 200px;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.details th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 6px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
50
lib/components/SecurityDefinitions/security-definitions.ts
Normal file
50
lib/components/SecurityDefinitions/security-definitions.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
'use strict';
|
||||||
|
import { Component, ChangeDetectionStrategy, OnInit, HostListener } from '@angular/core';
|
||||||
|
import { SpecManager, BaseComponent } from '../base';
|
||||||
|
|
||||||
|
import { ComponentParser } from '../../services/component-parser.service';
|
||||||
|
|
||||||
|
const AUTH_TYPES = {
|
||||||
|
'oauth2': 'OAuth2',
|
||||||
|
'apiKey': 'API Key',
|
||||||
|
'basic': 'Basic Authorization'
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'security-definitions',
|
||||||
|
styleUrls: ['./security-definitions.css'],
|
||||||
|
templateUrl: './security-definitions.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class SecurityDefinitions extends BaseComponent implements OnInit {
|
||||||
|
info: any = {};
|
||||||
|
specUrl: String;
|
||||||
|
defs: any[];
|
||||||
|
|
||||||
|
static insertTagIntoDescription(md:string) {
|
||||||
|
if (ComponentParser.contains(md, 'security-definitions')) return md;
|
||||||
|
if (/^#\s?Authentication\s*$/mi.test(md)) return md;
|
||||||
|
return md + '\n# Authentication \n' + ComponentParser.build('security-definitions');
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(specMgr:SpecManager) {
|
||||||
|
super(specMgr);
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.componentSchema = this.componentSchema.securityDefinitions;
|
||||||
|
this.defs = Object.keys(this.componentSchema).map(name => {
|
||||||
|
let details = this.componentSchema[name];
|
||||||
|
details._displayType = AUTH_TYPES[details.type];
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
details
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.preinit();
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { MethodsList, SideMenu } from '../index';
|
import { MethodsList, SideMenu } from '../index';
|
||||||
|
|
||||||
import { SpecManager } from '../../utils/SpecManager';
|
import { SpecManager } from '../../utils/spec-manager';
|
||||||
|
|
||||||
let testOptions;
|
let testOptions;
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,6 @@ export class SideMenu extends BaseComponent implements OnInit {
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.scrollService.unbind();
|
this.scrollService.unbind();
|
||||||
this.hash.unbind();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
import { BaseComponent } from '../components/base';
|
import { BaseComponent } from '../components/base';
|
||||||
|
|
||||||
describe('Redoc components', () => {
|
describe('Redoc components', () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { OnInit, OnDestroy } from '@angular/core';
|
import { OnInit, OnDestroy } from '@angular/core';
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
|
|
||||||
|
|
||||||
export { SpecManager };
|
export { SpecManager };
|
||||||
|
|
|
@ -13,13 +13,14 @@ import { SideMenu } from './SideMenu/side-menu';
|
||||||
import { MethodsList } from './MethodsList/methods-list';
|
import { MethodsList } from './MethodsList/methods-list';
|
||||||
import { Method } from './Method/method';
|
import { Method } from './Method/method';
|
||||||
import { Warnings } from './Warnings/warnings';
|
import { Warnings } from './Warnings/warnings';
|
||||||
|
import { SecurityDefinitions } from './SecurityDefinitions/security-definitions';
|
||||||
|
|
||||||
import { Redoc } from './Redoc/redoc';
|
import { Redoc } from './Redoc/redoc';
|
||||||
|
|
||||||
export const REDOC_DIRECTIVES = [
|
export const REDOC_DIRECTIVES = [
|
||||||
ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
||||||
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc
|
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions
|
||||||
];
|
];
|
||||||
|
|
||||||
export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
export { ApiInfo, ApiLogo, JsonSchema, JsonSchemaLazy, ParamsList, RequestSamples, ResponsesList,
|
||||||
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc }
|
ResponsesSamples, SchemaSample, SideMenu, MethodsList, Method, Warnings, Redoc, SecurityDefinitions }
|
||||||
|
|
|
@ -3,7 +3,7 @@ import './components/Redoc/redoc-initial-styles.css';
|
||||||
|
|
||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from '@angular/core';
|
||||||
import { Redoc } from './components/index';
|
import { Redoc } from './components/index';
|
||||||
import { SpecManager } from './utils/SpecManager';
|
import { SpecManager } from './utils/spec-manager';
|
||||||
import { BrowserDomAdapter as DOM } from './utils/browser-adapter';
|
import { BrowserDomAdapter as DOM } from './utils/browser-adapter';
|
||||||
import { disableDebugTools } from '@angular/platform-browser';
|
import { disableDebugTools } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export function init(specUrl:string, options:any = {}) {
|
||||||
moduleRef = appRef;
|
moduleRef = appRef;
|
||||||
console.log('ReDoc initialized!');
|
console.log('ReDoc initialized!');
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
Redoc.displayError(err);
|
//Redoc.displayError(err);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,26 +1,40 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule, ErrorHandler } from '@angular/core';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { Redoc, REDOC_DIRECTIVES } from './components/index';
|
import { Redoc, SecurityDefinitions, REDOC_DIRECTIVES } from './components/index';
|
||||||
import { REDOC_COMMON_DIRECTIVES } from './shared/components/index';
|
import { REDOC_COMMON_DIRECTIVES, DynamicNg2Wrapper } from './shared/components/index';
|
||||||
import { REDOC_PIPES } from './utils/pipes';
|
import { REDOC_PIPES, KeysPipe } from './utils/pipes';
|
||||||
|
import { CustomErrorHandler } from './utils/'
|
||||||
|
|
||||||
import { OptionsService, RedocEventsService, MenuService,
|
import {
|
||||||
ScrollService, Hash, WarningsService } from './services/index';
|
OptionsService,
|
||||||
import { SpecManager } from './utils/SpecManager';
|
MenuService,
|
||||||
|
ScrollService,
|
||||||
|
Hash,
|
||||||
|
WarningsService,
|
||||||
|
AppStateService,
|
||||||
|
ComponentParser,
|
||||||
|
ContentProjector,
|
||||||
|
COMPONENT_PARSER_ALLOWED } from './services/';
|
||||||
|
import { SpecManager } from './utils/spec-manager';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [ BrowserModule ],
|
imports: [ CommonModule ],
|
||||||
declarations: [ REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES, REDOC_PIPES],
|
declarations: [ REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES, REDOC_PIPES ],
|
||||||
bootstrap: [ Redoc ],
|
bootstrap: [ Redoc ],
|
||||||
|
entryComponents: [ SecurityDefinitions, DynamicNg2Wrapper ],
|
||||||
providers: [
|
providers: [
|
||||||
SpecManager,
|
SpecManager,
|
||||||
RedocEventsService,
|
|
||||||
ScrollService,
|
ScrollService,
|
||||||
Hash,
|
Hash,
|
||||||
MenuService,
|
MenuService,
|
||||||
WarningsService,
|
WarningsService,
|
||||||
OptionsService
|
OptionsService,
|
||||||
|
AppStateService,
|
||||||
|
ComponentParser,
|
||||||
|
ContentProjector,
|
||||||
|
{ provide: ErrorHandler, useClass: CustomErrorHandler },
|
||||||
|
{ provide: COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': SecurityDefinitions} }
|
||||||
],
|
],
|
||||||
exports: [Redoc]
|
exports: [Redoc]
|
||||||
})
|
})
|
||||||
|
|
11
lib/services/app-state.service.ts
Normal file
11
lib/services/app-state.service.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Subject } from 'rxjs/Subject';
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppStateService {
|
||||||
|
samplesLanguage = new Subject<string>();
|
||||||
|
error = new BehaviorSubject<any>(null);
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var isSupported = document.queryCommandSupported && document.queryCommandSupported('copy');
|
||||||
export class Clipboard {
|
export class Clipboard {
|
||||||
static isSupported():boolean {
|
static isSupported():boolean {
|
||||||
return document.queryCommandSupported && document.queryCommandSupported('copy');
|
return isSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
static selectElement(element:any):void {
|
static selectElement(element:any):void {
|
||||||
|
|
88
lib/services/component-parser.service.ts
Normal file
88
lib/services/component-parser.service.ts
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
Renderer,
|
||||||
|
ComponentRef,
|
||||||
|
Type,
|
||||||
|
Injector,
|
||||||
|
Inject,
|
||||||
|
ComponentFactoryResolver
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
type NodesOrComponents = HTMLElement | ComponentRef<any>;
|
||||||
|
export const COMPONENT_PARSER_ALLOWED = 'COMPONENT_PARSER_ALLOWED';
|
||||||
|
|
||||||
|
const COMPONENT_REGEXP = '^\\s*<!-- ReDoc-Inject:\\s+?{component}\\s+?-->\\s*$';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ComponentParser {
|
||||||
|
private renderer: Renderer;
|
||||||
|
private allowedComponents: any;
|
||||||
|
|
||||||
|
static contains(content: string, componentSelector: string) {
|
||||||
|
let regexp = new RegExp(COMPONENT_REGEXP.replace('{component}', `<${componentSelector}.*>`), 'mi');
|
||||||
|
return regexp.test(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
static build(componentSelector) {
|
||||||
|
return `<!-- ReDoc-Inject: <${componentSelector}> -->`;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private resolver: ComponentFactoryResolver,
|
||||||
|
@Inject(COMPONENT_PARSER_ALLOWED) allowedComponents
|
||||||
|
) {
|
||||||
|
this.allowedComponents = allowedComponents;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRenderer(_renderer: Renderer) {
|
||||||
|
this.renderer = _renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
splitIntoNodesOrComponents(content: string, injector: Injector):NodesOrComponents[] {
|
||||||
|
let componentDefs = [];
|
||||||
|
let match;
|
||||||
|
let anyCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '(.*?)'), 'gmi');
|
||||||
|
while (match = anyCompRegexp.exec(content)) {
|
||||||
|
componentDefs.push(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let splitCompRegexp = new RegExp(COMPONENT_REGEXP.replace('{component}', '.*?'), 'mi');
|
||||||
|
let htmlParts = content.split(splitCompRegexp);
|
||||||
|
let res = [];
|
||||||
|
for (let i = 0; i < htmlParts.length; i++) {
|
||||||
|
let node = this.renderer.createElement(null, 'div');
|
||||||
|
this.renderer.setElementProperty(node, 'innerHTML', htmlParts[i]);
|
||||||
|
if (htmlParts[i]) res.push(node);
|
||||||
|
if (componentDefs[i]) {
|
||||||
|
let componentRef = this.createComponentByHtml(componentDefs[i], injector);
|
||||||
|
res.push(componentRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
createComponentByHtml(htmlTag: string, injector:Injector):ComponentRef<any>| null {
|
||||||
|
let {componentType, options} = this._parseHtml(htmlTag);
|
||||||
|
if (!componentType) return null;
|
||||||
|
|
||||||
|
let factory = this.resolver.resolveComponentFactory(componentType);
|
||||||
|
return factory.create(injector);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _parseHtml(htmlTag: string):{componentType: Type<any> | null, options: any} {
|
||||||
|
// TODO: for now only primitive parsing by tagname
|
||||||
|
let match = /<([\w_-]+).*?>/.exec(htmlTag);
|
||||||
|
if (match.length <= 1) return { componentType: null, options: null };
|
||||||
|
let componentName = match[1];
|
||||||
|
|
||||||
|
let componentType = this.allowedComponents[componentName];
|
||||||
|
// TODO parse options
|
||||||
|
let options = {};
|
||||||
|
return {
|
||||||
|
componentType,
|
||||||
|
options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
39
lib/services/content-projector.service.ts
Normal file
39
lib/services/content-projector.service.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
ComponentFactory,
|
||||||
|
ComponentRef,
|
||||||
|
ViewContainerRef
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ContentProjector {
|
||||||
|
instantiateAndProject<T>(componentFactory: ComponentFactory<T>,
|
||||||
|
parentView:ViewContainerRef, projectedNodesOrComponents: any[]):ComponentRef<T> {
|
||||||
|
let contextInjector = parentView.parentInjector;
|
||||||
|
|
||||||
|
let projectedNodes = [];
|
||||||
|
let componentRefs:ComponentRef<any>[] = [];
|
||||||
|
|
||||||
|
for (let i=0; i < projectedNodesOrComponents.length; i++) {
|
||||||
|
let nodeOrCompRef = projectedNodesOrComponents[i];
|
||||||
|
if (nodeOrCompRef instanceof ComponentRef) {
|
||||||
|
projectedNodes.push(nodeOrCompRef.location.nativeElement);
|
||||||
|
componentRefs.push(nodeOrCompRef);
|
||||||
|
} else {
|
||||||
|
projectedNodes.push(nodeOrCompRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let parentCompRef = parentView.createComponent(componentFactory, null, contextInjector, [projectedNodes]);
|
||||||
|
let appElement = (<any>parentView)._element;
|
||||||
|
appElement.nestedViews = appElement.nestedViews || [];
|
||||||
|
for (let i=0; i < componentRefs.length; i++) {
|
||||||
|
let compRef = componentRefs[i];
|
||||||
|
appElement.nestedViews.push((<any>compRef.hostView).internalView);
|
||||||
|
(<any>compRef.hostView).internalView.addToContentChildren(appElement);
|
||||||
|
}
|
||||||
|
return parentCompRef;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { EventEmitter, Output } from '@angular/core';
|
|
||||||
|
|
||||||
export class RedocEventsService {
|
|
||||||
@Output() bootstrapped = new EventEmitter();
|
|
||||||
@Output() samplesLanguageChanged = new EventEmitter();
|
|
||||||
}
|
|
|
@ -1,25 +1,22 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
import {
|
||||||
|
inject
|
||||||
|
} from '@angular/core/testing';
|
||||||
|
|
||||||
import { RedocEventsService } from './events.service';
|
|
||||||
import { Hash } from './hash.service';
|
import { Hash } from './hash.service';
|
||||||
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
|
|
||||||
describe('Hash Service', () => {
|
describe('Hash Service', () => {
|
||||||
let events = new RedocEventsService();
|
let specMgr = new SpecManager();
|
||||||
let hashService;
|
let hashService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(inject([Hash], (_hash) => hashService = _hash));
|
||||||
hashService = new Hash(events);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
hashService.unbind();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should trigger changed event after ReDoc bootstrapped', (done) => {
|
it('should trigger changed event after ReDoc bootstrapped', (done) => {
|
||||||
spyOn(hashService.changed, 'next').and.callThrough();
|
spyOn(hashService.value, 'next').and.callThrough();
|
||||||
events.bootstrapped.next({});
|
specMgr.spec.next({});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(hashService.changed.next).toHaveBeenCalled();
|
expect(hashService.value.next).toHaveBeenCalled();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,32 +1,31 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Injectable, EventEmitter, Output } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BrowserDomAdapter as DOM } from '../utils/browser-adapter';
|
import { PlatformLocation } from '@angular/common';
|
||||||
import { global } from '@angular/core/src/facade/lang';
|
|
||||||
|
|
||||||
import { RedocEventsService } from './events.service';
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class Hash {
|
export class Hash {
|
||||||
@Output() changed = new EventEmitter();
|
public value = new BehaviorSubject<string>('');
|
||||||
private _cancel: any;
|
constructor(private specMgr: SpecManager, private location: PlatformLocation) {
|
||||||
constructor(private events:RedocEventsService) {
|
|
||||||
this.bind();
|
this.bind();
|
||||||
|
|
||||||
events.bootstrapped.subscribe(() => this.changed.next(this.hash));
|
this.specMgr.spec.subscribe((spec) => {
|
||||||
}
|
if (!spec) return;
|
||||||
|
setTimeout(() => {
|
||||||
get hash() {
|
this.value.next(this.hash);
|
||||||
return DOM.getLocation().hash;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
bind() {
|
|
||||||
this._cancel = DOM.onAndCancel(global, 'hashchange', (evt) => {
|
|
||||||
this.changed.next(this.hash);
|
|
||||||
evt.preventDefault();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
unbind() {
|
get hash() {
|
||||||
this._cancel();
|
return this.location.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind() {
|
||||||
|
this.location.onHashChange(() => {
|
||||||
|
this.value.next(this.hash);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export * from './events.service';
|
export * from './app-state.service';
|
||||||
export * from './options.service';
|
export * from './options.service';
|
||||||
export * from './menu.service';
|
export * from './menu.service';
|
||||||
export * from './scroll.service';
|
export * from './scroll.service';
|
||||||
|
@ -8,3 +8,6 @@ export * from './hash.service';
|
||||||
export * from './schema-normalizer.service';
|
export * from './schema-normalizer.service';
|
||||||
export * from './schema-helper.service';
|
export * from './schema-helper.service';
|
||||||
export * from './warnings.service';
|
export * from './warnings.service';
|
||||||
|
|
||||||
|
export * from './component-parser.service';
|
||||||
|
export * from './content-projector.service';
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import { MenuService } from './menu.service';
|
import { MenuService } from './menu.service';
|
||||||
import { Hash } from './hash.service';
|
import { Hash } from './hash.service';
|
||||||
import { ScrollService } from './scroll.service';
|
import { ScrollService } from './scroll.service';
|
||||||
import { SpecManager } from '../utils/SpecManager';;
|
import { SpecManager } from '../utils/spec-manager';;
|
||||||
|
|
||||||
describe('Menu service', () => {
|
describe('Menu service', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -36,56 +36,59 @@ describe('Menu service', () => {
|
||||||
|
|
||||||
it('should run hashScroll when hash changed', (done) => {
|
it('should run hashScroll when hash changed', (done) => {
|
||||||
spyOn(menu, 'hashScroll').and.callThrough();
|
spyOn(menu, 'hashScroll').and.callThrough();
|
||||||
hashService.changed.subscribe(() => {
|
hashService.value.subscribe((hash) => {
|
||||||
|
if (!hash) return;
|
||||||
expect(menu.hashScroll).toHaveBeenCalled();
|
expect(menu.hashScroll).toHaveBeenCalled();
|
||||||
menu.hashScroll.and.callThrough();
|
menu.hashScroll.and.callThrough();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
hashService.changed.next();
|
hashService.value.next('nonFalsy');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scroll to method when location hash is present [jp]', (done) => {
|
it('should scroll to method when location hash is present [jp]', (done) => {
|
||||||
let hash = '#tag/pet/paths/~1pet~1findByStatus/get';
|
let hash = '#tag/pet/paths/~1pet~1findByStatus/get';
|
||||||
spyOn(menu, 'hashScroll').and.callThrough();
|
spyOn(menu, 'hashScroll').and.callThrough();
|
||||||
spyOn(window, 'scrollTo').and.stub();
|
spyOn(window, 'scrollTo').and.stub();
|
||||||
hashService.changed.subscribe(() => {
|
hashService.value.subscribe((hash) => {
|
||||||
|
if (!hash) return;
|
||||||
expect(menu.hashScroll).toHaveBeenCalled();
|
expect(menu.hashScroll).toHaveBeenCalled();
|
||||||
let scrollY = (<jasmine.Spy>window.scrollTo).calls.argsFor(0)[1];
|
let scrollY = (<jasmine.Spy>window.scrollTo).calls.argsFor(0)[1];
|
||||||
expect(scrollY).toBeGreaterThan(0);
|
expect(scrollY).toBeGreaterThan(0);
|
||||||
(<jasmine.Spy>window.scrollTo).and.callThrough();
|
(<jasmine.Spy>window.scrollTo).and.callThrough();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
hashService.changed.next(hash);
|
hashService.value.next(hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should scroll to method when location hash is present [operation]', (done) => {
|
it('should scroll to method when location hash is present [operation]', (done) => {
|
||||||
let hash = '#operation/getPetById';
|
let hash = '#operation/getPetById';
|
||||||
spyOn(menu, 'hashScroll').and.callThrough();
|
spyOn(menu, 'hashScroll').and.callThrough();
|
||||||
spyOn(window, 'scrollTo').and.stub();
|
spyOn(window, 'scrollTo').and.stub();
|
||||||
hashService.changed.subscribe(() => {
|
hashService.value.subscribe((hash) => {
|
||||||
|
if (!hash) return;
|
||||||
expect(menu.hashScroll).toHaveBeenCalled();
|
expect(menu.hashScroll).toHaveBeenCalled();
|
||||||
let scrollY = (<jasmine.Spy>window.scrollTo).calls.argsFor(0)[1];
|
let scrollY = (<jasmine.Spy>window.scrollTo).calls.argsFor(0)[1];
|
||||||
expect(scrollY).toBeGreaterThan(0);
|
expect(scrollY).toBeGreaterThan(0);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
hashService.changed.next(hash);
|
hashService.value.next(hash);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should select next/prev menu item when scrolled down/up', () => {
|
it('should select next/prev menu item when scrolled down/up', () => {
|
||||||
scroll.$scrollParent = document.querySelector('#parent');
|
scroll.$scrollParent = document.querySelector('#parent');
|
||||||
menu.activeCatIdx.should.be.equal(0);
|
menu.activeCatIdx.should.be.equal(0);
|
||||||
menu.activeMethodIdx.should.be.equal(-1);
|
menu.activeMethodIdx.should.be.equal(-1);
|
||||||
let elTop = menu.getCurrentMethodEl().getBoundingClientRect().bottom;
|
let nextElTop = menu.getRelativeCatOrItem(1).getBoundingClientRect().top;
|
||||||
|
|
||||||
scroll.$scrollParent.scrollTop = elTop + 1;
|
scroll.$scrollParent.scrollTop = nextElTop + 1;
|
||||||
|
|
||||||
//simulate scroll down
|
//simulate scroll down
|
||||||
spyOn(scroll, 'scrollY').and.returnValue(elTop + 2);
|
spyOn(scroll, 'scrollY').and.returnValue(nextElTop + 10);
|
||||||
menu.scrollUpdate(true);
|
menu.scrollUpdate(true);
|
||||||
menu.activeCatIdx.should.be.equal(1);
|
menu.activeCatIdx.should.be.equal(1);
|
||||||
|
|
||||||
scroll.scrollY.and.returnValue(elTop - 2);
|
scroll.scrollY.and.returnValue(nextElTop - 2);
|
||||||
scroll.$scrollParent.scrollTop = elTop - 1;
|
scroll.$scrollParent.scrollTop = nextElTop - 1;
|
||||||
menu.scrollUpdate(false);
|
menu.scrollUpdate(false);
|
||||||
menu.activeCatIdx.should.be.equal(0);
|
menu.activeCatIdx.should.be.equal(0);
|
||||||
});
|
});
|
||||||
|
@ -95,6 +98,7 @@ describe('Menu service', () => {
|
||||||
selector: 'test-app',
|
selector: 'test-app',
|
||||||
template:
|
template:
|
||||||
`<div id='parent' style='height: 500px; overflow:auto'>
|
`<div id='parent' style='height: 500px; overflow:auto'>
|
||||||
|
<api-info></api-info>
|
||||||
<methods-list></methods-list>
|
<methods-list></methods-list>
|
||||||
</div>`
|
</div>`
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { Injectable, EventEmitter } from '@angular/core';
|
import { Injectable, EventEmitter } from '@angular/core';
|
||||||
import { ScrollService, INVIEW_POSITION } from './scroll.service';
|
import { ScrollService, INVIEW_POSITION } from './scroll.service';
|
||||||
import { Hash } from './hash.service';
|
import { Hash } from './hash.service';
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
import { SchemaHelper, MenuCategory } from './schema-helper.service';
|
import { SchemaHelper, MenuCategory } from './schema-helper.service';
|
||||||
|
|
||||||
const CHANGE = {
|
const CHANGE = {
|
||||||
|
@ -30,7 +30,7 @@ export class MenuService {
|
||||||
|
|
||||||
this.changeActive(CHANGE.INITIAL);
|
this.changeActive(CHANGE.INITIAL);
|
||||||
|
|
||||||
this.hash.changed.subscribe((hash) => {
|
this.hash.value.subscribe((hash) => {
|
||||||
this.hashScroll(hash);
|
this.hashScroll(hash);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,14 @@ export class MenuService {
|
||||||
let $activeMethodHost = this.getCurrentMethodEl();
|
let $activeMethodHost = this.getCurrentMethodEl();
|
||||||
if (!$activeMethodHost) return;
|
if (!$activeMethodHost) return;
|
||||||
var elementInViewPos = this.scrollService.getElementPos($activeMethodHost);
|
var elementInViewPos = this.scrollService.getElementPos($activeMethodHost);
|
||||||
if(isScrolledDown && elementInViewPos === INVIEW_POSITION.BELLOW) {
|
if(isScrolledDown) {
|
||||||
stable = this.changeActive(CHANGE.NEXT);
|
//&& elementInViewPos === INVIEW_POSITION.BELLOW
|
||||||
continue;
|
let $nextEl = this.getRelativeCatOrItem(1);
|
||||||
|
let nextInViewPos = this.scrollService.getElementPos($nextEl, true);
|
||||||
|
if (elementInViewPos === INVIEW_POSITION.BELLOW && nextInViewPos === INVIEW_POSITION.ABOVE) {
|
||||||
|
stable = this.changeActive(CHANGE.NEXT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(!isScrolledDown && elementInViewPos === INVIEW_POSITION.ABOVE ) {
|
if(!isScrolledDown && elementInViewPos === INVIEW_POSITION.ABOVE ) {
|
||||||
stable = this.changeActive(CHANGE.BACK);
|
stable = this.changeActive(CHANGE.BACK);
|
||||||
|
@ -53,6 +58,25 @@ export class MenuService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRelativeCatOrItem(offset: number = 0) {
|
||||||
|
let ptr, cat;
|
||||||
|
cat = this.categories[this.activeCatIdx];
|
||||||
|
if (cat.methods.length === 0) {
|
||||||
|
ptr = null;
|
||||||
|
cat = this.categories[this.activeCatIdx + Math.sign(offset)] || cat;
|
||||||
|
} else {
|
||||||
|
let cat = this.categories[this.activeCatIdx];
|
||||||
|
let idx = this.activeMethodIdx + offset;
|
||||||
|
if ((idx >= cat.methods.length - 1) || idx < 0) {
|
||||||
|
cat = this.categories[this.activeCatIdx + Math.sign(offset)] || cat;
|
||||||
|
idx = offset > 0 ? -1 : cat.methods.length - 1;
|
||||||
|
}
|
||||||
|
ptr = cat.methods[idx] && cat.methods[idx].pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getMethodElByPtr(ptr, cat.id);
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentMethodEl() {
|
getCurrentMethodEl() {
|
||||||
return this.getMethodElByPtr(this.activeMethodPtr,
|
return this.getMethodElByPtr(this.activeMethodPtr,
|
||||||
this.categories[this.activeCatIdx].id);
|
this.categories[this.activeCatIdx].id);
|
||||||
|
@ -141,11 +165,13 @@ export class MenuService {
|
||||||
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
|
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
|
||||||
if (namespace === 'operation') {
|
if (namespace === 'operation') {
|
||||||
$el = this.getMethodElByOperId(ptr);
|
$el = this.getMethodElByOperId(ptr);
|
||||||
} else {
|
} else if (namespace === 'tag') {
|
||||||
let sectionId = ptr.split('/')[0];
|
let sectionId = ptr.split('/')[0];
|
||||||
ptr = ptr.substr(sectionId.length) || null;
|
ptr = ptr.substr(sectionId.length) || null;
|
||||||
sectionId = namespace + (sectionId ? '/' + sectionId : '');
|
sectionId = namespace + (sectionId ? '/' + sectionId : '');
|
||||||
$el = this.getMethodElByPtr(ptr, sectionId);
|
$el = this.getMethodElByPtr(ptr, sectionId);
|
||||||
|
} else {
|
||||||
|
$el = this.getMethodElByPtr(null, namespace + '/' + ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($el) this.scrollService.scrollTo($el);
|
if ($el) this.scrollService.scrollTo($el);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { isFunction, isString } from '@angular/core/src/facade/lang';
|
import { isFunction, isString } from '../utils/helpers';
|
||||||
import { BrowserDomAdapter as DOM } from '../utils/browser-adapter';
|
import { BrowserDomAdapter as DOM } from '../utils/browser-adapter';
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { SchemaHelper } from './schema-helper.service';
|
import { SchemaHelper } from './schema-helper.service';
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
|
|
||||||
describe('Spec Helper', () => {
|
describe('Spec Helper', () => {
|
||||||
describe('buildMenuTree method', () => {
|
describe('buildMenuTree method', () => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer } from '../utils/JsonPointer';
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
import {methods as swaggerMethods, keywordTypes} from '../utils/swagger-defs';
|
import { methods as swaggerMethods, keywordTypes } from '../utils/swagger-defs';
|
||||||
import { WarningsService } from './warnings.service';
|
import { WarningsService } from './warnings.service';
|
||||||
import * as slugify from 'slugify';
|
import * as slugify from 'slugify';
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ export interface MenuMethod {
|
||||||
active: boolean;
|
active: boolean;
|
||||||
summary: string;
|
summary: string;
|
||||||
tag: string;
|
tag: string;
|
||||||
|
pointer: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MenuCategory {
|
export interface MenuCategory {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { SchemaNormalizer } from './schema-normalizer.service';
|
import { SchemaNormalizer } from './schema-normalizer.service';
|
||||||
import { SpecManager } from '../utils/SpecManager';;
|
import { SpecManager } from '../utils/spec-manager';;
|
||||||
|
|
||||||
describe('Spec Helper', () => {
|
describe('Spec Helper', () => {
|
||||||
let specMgr:SpecManager = new SpecManager();
|
let specMgr:SpecManager = new SpecManager();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
import { SpecManager } from '../utils/spec-manager';
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer } from '../utils/JsonPointer';
|
||||||
import { defaults } from '../utils/helpers';
|
import { defaults } from '../utils/helpers';
|
||||||
import { WarningsService } from './warnings.service';
|
import { WarningsService } from './warnings.service';
|
||||||
|
|
|
@ -18,7 +18,6 @@ export class ScrollService {
|
||||||
private prevOffsetY: number;
|
private prevOffsetY: number;
|
||||||
private _cancel:any;
|
private _cancel:any;
|
||||||
constructor(optionsService:OptionsService) {
|
constructor(optionsService:OptionsService) {
|
||||||
//events.bootstrapped.subscribe(() => this.hashScroll());
|
|
||||||
this.scrollYOffset = () => optionsService.options.scrollYOffset();
|
this.scrollYOffset = () => optionsService.options.scrollYOffset();
|
||||||
this.$scrollParent = optionsService.options.$scrollParent;
|
this.$scrollParent = optionsService.options.$scrollParent;
|
||||||
this.scroll = new EventEmitter();
|
this.scroll = new EventEmitter();
|
||||||
|
@ -30,12 +29,14 @@ export class ScrollService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* returns 1 if element if above the view, 0 if in view and -1 below the view */
|
/* returns 1 if element if above the view, 0 if in view and -1 below the view */
|
||||||
getElementPos($el) {
|
getElementPos($el, inverted=false) {
|
||||||
if (Math.floor($el.getBoundingClientRect().top) > this.scrollYOffset()) {
|
let scrollYOffset = this.scrollYOffset();
|
||||||
|
let mul = inverted ? -1 : 1;
|
||||||
|
if (mul*Math.floor($el.getBoundingClientRect().top) > mul*scrollYOffset) {
|
||||||
return INVIEW_POSITION.ABOVE;
|
return INVIEW_POSITION.ABOVE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($el.getBoundingClientRect().bottom <= this.scrollYOffset()) {
|
if (mul*$el.getBoundingClientRect().bottom <= mul*scrollYOffset) {
|
||||||
return INVIEW_POSITION.BELLOW;
|
return INVIEW_POSITION.BELLOW;
|
||||||
}
|
}
|
||||||
return INVIEW_POSITION.INVIEW;
|
return INVIEW_POSITION.INVIEW;
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
Output,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
ViewContainerRef,
|
||||||
|
ComponentFactoryResolver,
|
||||||
|
Renderer
|
||||||
|
} from '@angular/core';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ComponentParser,
|
||||||
|
ContentProjector
|
||||||
|
} from '../../../services/';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dynamic-ng2-viewer',
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
export class DynamicNg2Viewer implements OnInit {
|
||||||
|
@Input() html: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private view: ViewContainerRef,
|
||||||
|
private projector: ContentProjector,
|
||||||
|
private parser: ComponentParser,
|
||||||
|
private resolver: ComponentFactoryResolver,
|
||||||
|
private renderer: Renderer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.parser.setRenderer(this.renderer);
|
||||||
|
let nodesOrComponents = this.parser.splitIntoNodesOrComponents(this.html, this.view.injector);
|
||||||
|
let wrapperFactory = this.resolver.resolveComponentFactory(DynamicNg2Wrapper);
|
||||||
|
let ref = this.projector.instantiateAndProject(wrapperFactory, this.view, nodesOrComponents);
|
||||||
|
ref.changeDetectorRef.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'dynamic-ng2-wrapper',
|
||||||
|
template: '<ng-content></ng-content>'
|
||||||
|
})
|
||||||
|
export class DynamicNg2Wrapper {}
|
|
@ -5,9 +5,10 @@ import { Tabs, Tab } from './Tabs/tabs';
|
||||||
import { Zippy } from './Zippy/zippy';
|
import { Zippy } from './Zippy/zippy';
|
||||||
import { CopyButton } from './CopyButton/copy-button.directive';
|
import { CopyButton } from './CopyButton/copy-button.directive';
|
||||||
import { SelectOnClick } from './SelectOnClick/select-on-click.directive';
|
import { SelectOnClick } from './SelectOnClick/select-on-click.directive';
|
||||||
|
import { DynamicNg2Viewer, DynamicNg2Wrapper } from './DynamicNg2Viewer/dynamic-ng2-viewer.component';
|
||||||
|
|
||||||
export const REDOC_COMMON_DIRECTIVES = [
|
export const REDOC_COMMON_DIRECTIVES = [
|
||||||
DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick
|
DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper
|
||||||
];
|
];
|
||||||
|
|
||||||
export { DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick }
|
export { DropDown, StickySidebar, Tabs, Tab, Zippy, CopyButton, SelectOnClick, DynamicNg2Viewer, DynamicNg2Wrapper }
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
//import { isBlank } from '@angular/core/src/facade/lang';
|
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
|
||||||
function isBlank(obj) {
|
function isBlank(obj) {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
/*eslint no-unused-vars: 0*/
|
|
||||||
/*eslint strict: 0*/
|
|
||||||
var $buoop = { vs: {i:9, f:25, o:12.1, s:7}, c:2 };
|
|
||||||
function $buo_f(){
|
|
||||||
var e = document.createElement('script');
|
|
||||||
e.src = '//browser-update.org/update.min.js';
|
|
||||||
document.body.appendChild(e);
|
|
||||||
}
|
|
||||||
try {document.addEventListener('DOMContentLoaded', $buo_f, false);}
|
|
||||||
catch(e){window['attachEvent']('onload', $buo_f);}
|
|
13
lib/utils/custom-error-handler.ts
Normal file
13
lib/utils/custom-error-handler.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { ErrorHandler, Injectable } from '@angular/core';
|
||||||
|
import { AppStateService } from '../services/app-state.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CustomErrorHandler extends ErrorHandler {
|
||||||
|
constructor(private appState: AppStateService) {
|
||||||
|
super(true);
|
||||||
|
}
|
||||||
|
handleError(error) {
|
||||||
|
this.appState.error.next(error && error.rejection || error);
|
||||||
|
super.handleError(error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,103 +0,0 @@
|
||||||
'use strict';
|
|
||||||
var Remarkable = require('remarkable');
|
|
||||||
var md = new Remarkable({
|
|
||||||
html: true,
|
|
||||||
linkify: true,
|
|
||||||
breaks: false,
|
|
||||||
typographer: false,
|
|
||||||
highlight: function (str, lang) {
|
|
||||||
if (lang === 'json')
|
|
||||||
lang = 'js';
|
|
||||||
var grammar = Prism.languages[lang];
|
|
||||||
if (!grammar)
|
|
||||||
return str;
|
|
||||||
return Prism.highlight(str, grammar);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
function renderMd(rawText, headersHandler) {
|
|
||||||
var _origRule;
|
|
||||||
if (headersHandler) {
|
|
||||||
_origRule = {
|
|
||||||
open: md.renderer.rules.heading_open,
|
|
||||||
close: md.renderer.rules.heading_close
|
|
||||||
};
|
|
||||||
md.renderer.rules.heading_open = function (tokens, idx) {
|
|
||||||
if (tokens[idx].hLevel !== 1) {
|
|
||||||
return _origRule.open(tokens, idx);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return headersHandler.open(tokens, idx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
md.renderer.rules.heading_close = function (tokens, idx) {
|
|
||||||
if (tokens[idx].hLevel !== 1) {
|
|
||||||
return _origRule.close(tokens, idx);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return headersHandler.close(tokens, idx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var res = md.render(rawText);
|
|
||||||
if (headersHandler) {
|
|
||||||
md.renderer.rules.heading_open = _origRule.open;
|
|
||||||
md.renderer.rules.heading_close = _origRule.close;
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
exports.renderMd = renderMd;
|
|
||||||
function statusCodeType(statusCode) {
|
|
||||||
if (statusCode < 100 || statusCode > 599) {
|
|
||||||
throw new Error('invalid HTTP code');
|
|
||||||
}
|
|
||||||
var res = 'success';
|
|
||||||
if (statusCode >= 300 && statusCode < 400) {
|
|
||||||
res = 'redirect';
|
|
||||||
}
|
|
||||||
else if (statusCode >= 400) {
|
|
||||||
res = 'error';
|
|
||||||
}
|
|
||||||
else if (statusCode < 200) {
|
|
||||||
res = 'info';
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
exports.statusCodeType = statusCodeType;
|
|
||||||
function defaults(target, src) {
|
|
||||||
var props = Object.keys(src);
|
|
||||||
var index = -1, length = props.length;
|
|
||||||
while (++index < length) {
|
|
||||||
var key = props[index];
|
|
||||||
if (target[key] === undefined) {
|
|
||||||
target[key] = src[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
exports.defaults = defaults;
|
|
||||||
function safePush(obj, prop, val) {
|
|
||||||
if (!obj[prop])
|
|
||||||
obj[prop] = [];
|
|
||||||
obj[prop].push(val);
|
|
||||||
}
|
|
||||||
exports.safePush = safePush;
|
|
||||||
function throttle(fn, threshhold, scope) {
|
|
||||||
threshhold = threshhold || 250;
|
|
||||||
var last, deferTimer;
|
|
||||||
return function () {
|
|
||||||
var context = scope || this;
|
|
||||||
var now = +new Date, args = arguments;
|
|
||||||
if (last && now < last + threshhold) {
|
|
||||||
clearTimeout(deferTimer);
|
|
||||||
deferTimer = setTimeout(function () {
|
|
||||||
last = now;
|
|
||||||
fn.apply(context, args);
|
|
||||||
}, threshhold);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
last = now;
|
|
||||||
fn.apply(context, args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
exports.throttle = throttle;
|
|
|
@ -1,58 +1,19 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import * as Remarkable from 'remarkable';
|
|
||||||
declare var Prism: any;
|
|
||||||
|
|
||||||
const md = new Remarkable({
|
export function stringify(obj:any) {
|
||||||
html: true,
|
return JSON.stringify(obj);
|
||||||
linkify: true,
|
|
||||||
breaks: false,
|
|
||||||
typographer: false,
|
|
||||||
highlight: (str, lang) => {
|
|
||||||
if (lang === 'json') lang = 'js';
|
|
||||||
let grammar = Prism.languages[lang];
|
|
||||||
//fallback to clike
|
|
||||||
if (!grammar) return str;
|
|
||||||
return Prism.highlight(str, grammar);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface HeadersHandler {
|
|
||||||
open: Function;
|
|
||||||
close: Function;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderMd(rawText:string, headersHandler?:HeadersHandler) {
|
export function isString(str:any) {
|
||||||
let _origRule;
|
return typeof str === 'string';
|
||||||
if (headersHandler) {
|
}
|
||||||
_origRule = {
|
|
||||||
open: md.renderer.rules.heading_open,
|
|
||||||
close: md.renderer.rules.heading_close
|
|
||||||
};
|
|
||||||
md.renderer.rules.heading_open = (tokens, idx) => {
|
|
||||||
if (tokens[idx].hLevel !== 1 ) {
|
|
||||||
return _origRule.open(tokens, idx);
|
|
||||||
} else {
|
|
||||||
return headersHandler.open(tokens, idx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
md.renderer.rules.heading_close = (tokens, idx) => {
|
export function isFunction(func: any) {
|
||||||
if (tokens[idx].hLevel !== 1 ) {
|
return typeof func === 'function';
|
||||||
return _origRule.close(tokens, idx);
|
}
|
||||||
} else {
|
|
||||||
return headersHandler.close(tokens, idx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let res = md.render(rawText);
|
export function isBlank(obj: any): boolean {
|
||||||
|
return obj == undefined;
|
||||||
if (headersHandler) {
|
|
||||||
md.renderer.rules.heading_open = _origRule.open;
|
|
||||||
md.renderer.rules.heading_close = _origRule.close;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function statusCodeType(statusCode) {
|
export function statusCodeType(statusCode) {
|
||||||
|
|
3
lib/utils/index.ts
Normal file
3
lib/utils/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './custom-error-handler';
|
||||||
|
export * from './helpers';
|
||||||
|
export * from './md-renderer';
|
90
lib/utils/md-renderer.ts
Normal file
90
lib/utils/md-renderer.ts
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import * as slugify from 'slugify';
|
||||||
|
import * as Remarkable from 'remarkable';
|
||||||
|
|
||||||
|
declare var Prism: any;
|
||||||
|
const md = new Remarkable({
|
||||||
|
html: true,
|
||||||
|
linkify: true,
|
||||||
|
breaks: false,
|
||||||
|
typographer: false,
|
||||||
|
highlight: (str, lang) => {
|
||||||
|
if (lang === 'json') lang = 'js';
|
||||||
|
let grammar = Prism.languages[lang];
|
||||||
|
//fallback to clike
|
||||||
|
if (!grammar) return str;
|
||||||
|
return Prism.highlight(str, grammar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface HeadersHandler {
|
||||||
|
open: Function;
|
||||||
|
close: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MdRenderer {
|
||||||
|
public firstLevelHeadings: string[] = [];
|
||||||
|
|
||||||
|
private _origRules:any = {};
|
||||||
|
private _preProcessors:Function[] = [];
|
||||||
|
|
||||||
|
constructor(private raw: boolean = false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
addPreprocessor(p: Function) {
|
||||||
|
this._preProcessors.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveOrigRules() {
|
||||||
|
this._origRules.open = md.renderer.rules.heading_open;
|
||||||
|
this._origRules.close = md.renderer.rules.heading_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreOrigRules() {
|
||||||
|
md.renderer.rules.heading_open = this._origRules.open;
|
||||||
|
md.renderer.rules.heading_close = this._origRules.close;
|
||||||
|
}
|
||||||
|
|
||||||
|
headingOpenRule(tokens, idx) {
|
||||||
|
if (tokens[idx].hLevel !== 1 ) {
|
||||||
|
return this._origRules.open(tokens, idx);
|
||||||
|
} else {
|
||||||
|
let content = tokens[idx + 1].content;
|
||||||
|
this.firstLevelHeadings.push(content);
|
||||||
|
let contentSlug = slugify(content);
|
||||||
|
return `<h${tokens[idx].hLevel} section="section/${contentSlug}">` +
|
||||||
|
`<a class="share-link" href="#section/${contentSlug}"></a>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headingCloseRule(tokens, idx) {
|
||||||
|
if (tokens[idx].hLevel !== 1 ) {
|
||||||
|
return this._origRules.close(tokens, idx);
|
||||||
|
} else {
|
||||||
|
return `</h${tokens[idx].hLevel}>\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMd(rawText:string) {
|
||||||
|
if (!this.raw) {
|
||||||
|
this.saveOrigRules();
|
||||||
|
md.renderer.rules.heading_open = this.headingOpenRule.bind(this);
|
||||||
|
md.renderer.rules.heading_close = this.headingCloseRule.bind(this);
|
||||||
|
}
|
||||||
|
let text = rawText;
|
||||||
|
|
||||||
|
for (let i=0; i<this._preProcessors.length; i++) {
|
||||||
|
text = this._preProcessors[i](text);
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = md.render(text);
|
||||||
|
|
||||||
|
if (!this.raw) {
|
||||||
|
this.restoreOrigRules();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { isString, stringify, isBlank } from '@angular/core/src/facade/lang';
|
import { isString, stringify, isBlank } from './helpers';
|
||||||
import JsonPointer from './JsonPointer';
|
import JsonPointer from './JsonPointer';
|
||||||
import { renderMd } from './helpers';
|
import { MdRenderer } from './';
|
||||||
import { JsonFormatter } from './JsonFormatterPipe';
|
import { JsonFormatter } from './JsonFormatterPipe';
|
||||||
|
|
||||||
declare var Prism: any;
|
declare var Prism: any;
|
||||||
|
@ -28,23 +28,12 @@ export class KeysPipe implements PipeTransform {
|
||||||
transform(value) {
|
transform(value) {
|
||||||
if (isBlank(value)) return value;
|
if (isBlank(value)) return value;
|
||||||
if (typeof value !== 'object') {
|
if (typeof value !== 'object') {
|
||||||
throw new InvalidPipeArgumentException(ValuesPipe, value);
|
throw new InvalidPipeArgumentException(KeysPipe, value);
|
||||||
}
|
}
|
||||||
return Object.keys(value);
|
return Object.keys(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Pipe({ name: 'values' })
|
|
||||||
export class ValuesPipe implements PipeTransform {
|
|
||||||
transform(value) {
|
|
||||||
if (isBlank(value)) return value;
|
|
||||||
if (typeof value !== 'object') {
|
|
||||||
throw new InvalidPipeArgumentException(ValuesPipe, value);
|
|
||||||
}
|
|
||||||
return Object.keys(value).map(key => value[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pipe({ name: 'jsonPointerEscape' })
|
@Pipe({ name: 'jsonPointerEscape' })
|
||||||
export class JsonPointerEscapePipe implements PipeTransform {
|
export class JsonPointerEscapePipe implements PipeTransform {
|
||||||
transform(value:string) {
|
transform(value:string) {
|
||||||
|
@ -58,7 +47,10 @@ export class JsonPointerEscapePipe implements PipeTransform {
|
||||||
|
|
||||||
@Pipe({ name: 'marked' })
|
@Pipe({ name: 'marked' })
|
||||||
export class MarkedPipe implements PipeTransform {
|
export class MarkedPipe implements PipeTransform {
|
||||||
constructor(private sanitizer: DomSanitizer) {}
|
renderer: MdRenderer;
|
||||||
|
constructor(private sanitizer: DomSanitizer) {
|
||||||
|
this.renderer = new MdRenderer(true);
|
||||||
|
}
|
||||||
transform(value:string) {
|
transform(value:string) {
|
||||||
if (isBlank(value)) return value;
|
if (isBlank(value)) return value;
|
||||||
if (!isString(value)) {
|
if (!isString(value)) {
|
||||||
|
@ -66,7 +58,7 @@ export class MarkedPipe implements PipeTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sanitizer.bypassSecurityTrustHtml(
|
return this.sanitizer.bypassSecurityTrustHtml(
|
||||||
`<span class="redoc-markdown-block">${renderMd(value)}</span>`
|
`<span class="redoc-markdown-block">${this.renderer.renderMd(value)}</span>`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,5 +117,5 @@ export class EncodeURIComponentPipe implements PipeTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const REDOC_PIPES = [
|
export const REDOC_PIPES = [
|
||||||
JsonPointerEscapePipe, MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter
|
JsonPointerEscapePipe, MarkedPipe, SafePipe, PrismPipe, EncodeURIComponentPipe, JsonFormatter, KeysPipe
|
||||||
];
|
];
|
||||||
|
|
|
@ -2,13 +2,16 @@
|
||||||
|
|
||||||
import * as JsonSchemaRefParser from 'json-schema-ref-parser';
|
import * as JsonSchemaRefParser from 'json-schema-ref-parser';
|
||||||
import { JsonPointer } from './JsonPointer';
|
import { JsonPointer } from './JsonPointer';
|
||||||
import { renderMd, safePush } from './helpers';
|
|
||||||
import * as slugify from 'slugify';
|
|
||||||
import { parse as urlParse, resolve as urlResolve } from 'url';
|
import { parse as urlParse, resolve as urlResolve } from 'url';
|
||||||
|
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||||
|
|
||||||
|
import { MdRenderer } from './md-renderer';
|
||||||
|
|
||||||
export class SpecManager {
|
export class SpecManager {
|
||||||
public _schema: any = {};
|
public _schema: any = {};
|
||||||
public apiUrl: string;
|
public apiUrl: string;
|
||||||
|
|
||||||
|
public spec = new BehaviorSubject<any|null>(null);
|
||||||
private _instance: any;
|
private _instance: any;
|
||||||
private _url: string;
|
private _url: string;
|
||||||
|
|
||||||
|
@ -24,17 +27,19 @@ export class SpecManager {
|
||||||
SpecManager.prototype._instance = this;
|
SpecManager.prototype._instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
load(url) {
|
load(urlOrObject: string|Object) {
|
||||||
|
this.schema = null;
|
||||||
let promise = new Promise((resolve, reject) => {
|
let promise = new Promise((resolve, reject) => {
|
||||||
this._schema = {};
|
JsonSchemaRefParser.bundle(urlOrObject, {http: {withCredentials: false}})
|
||||||
|
|
||||||
JsonSchemaRefParser.bundle(url, {http: {withCredentials: false}})
|
|
||||||
.then(schema => {
|
.then(schema => {
|
||||||
|
if (typeof urlOrObject === 'string') {
|
||||||
|
this._url = urlOrObject;
|
||||||
|
}
|
||||||
|
this._schema = schema;
|
||||||
try {
|
try {
|
||||||
this._url = url;
|
|
||||||
this._schema = schema;
|
|
||||||
this.init();
|
this.init();
|
||||||
resolve(this._schema);
|
resolve(this._schema);
|
||||||
|
this.spec.next(this._schema);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
|
@ -70,24 +75,25 @@ export class SpecManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
preprocess() {
|
preprocess() {
|
||||||
this._schema.info['x-redoc-html-description'] = renderMd( this._schema.info.description, {
|
let mdRender = new MdRenderer();
|
||||||
open: (tokens, idx) => {
|
if (!this._schema.info.description) this._schema.info.description = '';
|
||||||
let content = tokens[idx + 1].content;
|
if (this._schema.securityDefinitions) {
|
||||||
safePush(this._schema.info, 'x-redoc-markdown-headers', content);
|
let SecurityDefinitions = require('../components/').SecurityDefinitions;
|
||||||
content = slugify(content);
|
mdRender.addPreprocessor(SecurityDefinitions.insertTagIntoDescription);
|
||||||
return `<h${tokens[idx].hLevel} section="section/${content}">` +
|
}
|
||||||
`<a class="share-link" href="#section/${content}"></a>`;
|
this._schema.info['x-redoc-html-description'] = mdRender.renderMd(this._schema.info.description);
|
||||||
},
|
this._schema.info['x-redoc-markdown-headers'] = mdRender.firstLevelHeadings;
|
||||||
close: (tokens, idx) => {
|
|
||||||
return `</h${tokens[idx].hLevel}>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get schema() {
|
get schema() {
|
||||||
return this._schema;
|
return this._schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set schema(val:any) {
|
||||||
|
this._schema = val;
|
||||||
|
this.spec.next(this._schema);
|
||||||
|
}
|
||||||
|
|
||||||
byPointer(pointer) {
|
byPointer(pointer) {
|
||||||
let res = null;
|
let res = null;
|
||||||
try {
|
try {
|
||||||
|
@ -148,6 +154,9 @@ export class SpecManager {
|
||||||
description: tag.description,
|
description: tag.description,
|
||||||
'x-traitTag': tag['x-traitTag'] || false
|
'x-traitTag': tag['x-traitTag'] || false
|
||||||
};
|
};
|
||||||
|
if (tag['x-traitTag']) {
|
||||||
|
console.warn(`x-traitTag (${tag.name}) is deprecated since v1.5.0 and will be removed in the future`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tagsMap;
|
return tagsMap;
|
42
package.json
42
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"description": "Swagger-generated API Reference Documentation",
|
"description": "Swagger-generated API Reference Documentation",
|
||||||
"version": "1.4.1",
|
"version": "1.5.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/Rebilly/ReDoc"
|
"url": "git://github.com/Rebilly/ReDoc"
|
||||||
|
@ -45,22 +45,22 @@
|
||||||
"author": "Roman Hotsiy",
|
"author": "Roman Hotsiy",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular/common": "^2.1.0",
|
"@angular/common": "^2.1.2",
|
||||||
"@angular/compiler": "^2.1.0",
|
"@angular/compiler": "^2.1.2",
|
||||||
"@angular/compiler-cli": "^2.1.0",
|
"@angular/compiler-cli": "^2.1.2",
|
||||||
"@angular/core": "^2.1.0",
|
"@angular/core": "^2.1.2",
|
||||||
"@angular/platform-browser": "^2.1.0",
|
"@angular/platform-browser": "^2.1.2",
|
||||||
"@angular/platform-browser-dynamic": "^2.1.0",
|
"@angular/platform-browser-dynamic": "^2.1.2",
|
||||||
"@angular/platform-server": "^2.1.0",
|
"@angular/platform-server": "^2.1.2",
|
||||||
"@types/core-js": "^0.9.31",
|
"@types/core-js": "^0.9.31",
|
||||||
"@types/jasmine": "^2.2.32",
|
"@types/jasmine": "^2.2.32",
|
||||||
"@types/requirejs": "^2.1.26",
|
"@types/requirejs": "^2.1.26",
|
||||||
"@types/should": "^8.1.28",
|
"@types/should": "^8.1.28",
|
||||||
"angular2-template-loader": "^0.5.0",
|
"angular2-template-loader": "^0.6.0",
|
||||||
"awesome-typescript-loader": "^2.2.1",
|
"awesome-typescript-loader": "2.2.4",
|
||||||
"branch-release": "^1.0.3",
|
"branch-release": "^1.0.3",
|
||||||
"chalk": "^1.1.3",
|
"chalk": "^1.1.3",
|
||||||
"codelyzer": "^1.0.0-beta.2",
|
"codelyzer": "^1.0.0-beta.3",
|
||||||
"copy-webpack-plugin": "^3.0.1",
|
"copy-webpack-plugin": "^3.0.1",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"coveralls": "^2.11.9",
|
"coveralls": "^2.11.9",
|
||||||
|
@ -82,15 +82,17 @@
|
||||||
"karma-sinon": "^1.0.4",
|
"karma-sinon": "^1.0.4",
|
||||||
"karma-sourcemap-loader": "^0.3.7",
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
"karma-webpack": "^1.8.0",
|
"karma-webpack": "^1.8.0",
|
||||||
"node-sass": "^3.8.0",
|
"node-sass": "^3.10.1",
|
||||||
"phantomjs-prebuilt": "^2.1.7",
|
"phantomjs-prebuilt": "^2.1.7",
|
||||||
"protractor": "^4.0.4",
|
"protractor": "^4.0.10",
|
||||||
"raw-loader": "^0.5.1",
|
"raw-loader": "^0.5.1",
|
||||||
"rxjs": "^5.0.0-beta.12",
|
"rxjs": "5.0.0-beta.12",
|
||||||
|
"sass-loader": "^4.0.2",
|
||||||
"shelljs": "^0.7.0",
|
"shelljs": "^0.7.0",
|
||||||
"should": "^11.1.0",
|
"should": "^11.1.0",
|
||||||
"sinon": "^1.17.2",
|
"sinon": "^1.17.2",
|
||||||
"source-map-loader": "^0.1.5",
|
"source-map-loader": "^0.1.5",
|
||||||
|
"string-replace-webpack-plugin": "0.0.4",
|
||||||
"style-loader": "^0.13.1",
|
"style-loader": "^0.13.1",
|
||||||
"ts-helpers": "^1.1.1",
|
"ts-helpers": "^1.1.1",
|
||||||
"tslint": "^3.15.1",
|
"tslint": "^3.15.1",
|
||||||
|
@ -113,12 +115,12 @@
|
||||||
"stream-http": "^2.3.1"
|
"stream-http": "^2.3.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "^2.1.0",
|
"@angular/common": "^2.1.2",
|
||||||
"@angular/compiler": "^2.1.0",
|
"@angular/compiler": "^2.1.2",
|
||||||
"@angular/core": "^2.1.0",
|
"@angular/core": "^2.1.2",
|
||||||
"@angular/platform-browser": "^2.1.0",
|
"@angular/platform-browser": "^2.1.2",
|
||||||
"@angular/platform-browser-dynamic": "^2.1.0",
|
"@angular/platform-browser-dynamic": "^2.1.2",
|
||||||
"@angular/platform-server": "^2.1.0",
|
"@angular/platform-server": "^2.1.2",
|
||||||
"core-js": "^2.4.1",
|
"core-js": "^2.4.1",
|
||||||
"rxjs": "5.0.0-beta.12",
|
"rxjs": "5.0.0-beta.12",
|
||||||
"zone.js": "^0.6.25"
|
"zone.js": "^0.6.25"
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<script>
|
<script>
|
||||||
window.redocError = null;
|
window.redocError = null;
|
||||||
/* init redoc */
|
/* init redoc */
|
||||||
var url = window.location.search.substr(5) || 'http://rebilly.github.io/SwaggerTemplateRepo/swagger.json';
|
var url = window.location.search.substr(5) || 'http://rebilly.github.io/ReDoc/swagger.yaml';
|
||||||
Redoc.init(decodeURIComponent(url), {disableLazySchemas: true, suppressWarnings: true}).then(function() {}, function(err) {
|
Redoc.init(decodeURIComponent(url), {disableLazySchemas: true, suppressWarnings: true}).then(function() {}, function(err) {
|
||||||
window.redocError = err;
|
window.redocError = err;
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,13 @@ const eachNth = require('./helpers').eachNth;
|
||||||
|
|
||||||
const URL = 'index.html';
|
const URL = 'index.html';
|
||||||
|
|
||||||
|
function waitForInit() {
|
||||||
|
var EC = protractor.ExpectedConditions;
|
||||||
|
var $apiInfo = $('api-info');
|
||||||
|
var $errorMessage = $('.redoc-error')
|
||||||
|
browser.wait(EC.or(EC.visibilityOf($apiInfo), EC.visibilityOf($errorMessage)), 60000);
|
||||||
|
}
|
||||||
|
|
||||||
function basicTests(swaggerUrl, title) {
|
function basicTests(swaggerUrl, title) {
|
||||||
describe(`Basic suite for ${title}`, () => {
|
describe(`Basic suite for ${title}`, () => {
|
||||||
let specUrl = URL;
|
let specUrl = URL;
|
||||||
|
@ -15,6 +22,7 @@ function basicTests(swaggerUrl, title) {
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
browser.get(specUrl);
|
browser.get(specUrl);
|
||||||
|
waitForInit();
|
||||||
fixFFTest(done);
|
fixFFTest(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,11 +30,14 @@ function basicTests(swaggerUrl, title) {
|
||||||
verifyNoBrowserErrors();
|
verifyNoBrowserErrors();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should init redoc without errors', () => {
|
it('should init redoc without errors', (done) => {
|
||||||
let $redoc = $('redoc');
|
let $redoc = $('redoc');
|
||||||
expect($redoc.isPresent()).toBe(true);
|
expect($redoc.isPresent()).toBe(true);
|
||||||
let $methods = $$('method');
|
setTimeout(() => {
|
||||||
expect($methods.count()).toBeGreaterThan(0);
|
let $methods = $$('method');
|
||||||
|
expect($methods.count()).toBeGreaterThan(0);
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -39,6 +50,7 @@ describe('Scroll sync', () => {
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
browser.get(specUrl);
|
browser.get(specUrl);
|
||||||
|
waitForInit();
|
||||||
fixFFTest(done);
|
fixFFTest(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -64,6 +76,7 @@ describe('Language tabs sync', () => {
|
||||||
|
|
||||||
beforeEach((done) => {
|
beforeEach((done) => {
|
||||||
browser.get(specUrl);
|
browser.get(specUrl);
|
||||||
|
waitForInit();
|
||||||
fixFFTest(done);
|
fixFFTest(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
info:
|
info:
|
||||||
description: "This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters."
|
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
title: "Swagger Petstore"
|
title: "Swagger Petstore"
|
||||||
termsOfService: "http://swagger.io/terms/"
|
termsOfService: "http://swagger.io/terms/"
|
||||||
|
@ -12,56 +11,50 @@
|
||||||
license:
|
license:
|
||||||
name: "Apache 2.0"
|
name: "Apache 2.0"
|
||||||
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
|
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||||
|
description: |-
|
||||||
|
# Pagination
|
||||||
|
Sometimes you just can't get enough. For this reason, we've provided a convenient way to access more data in
|
||||||
|
any request for sequential data. Simply call the url in the next_url parameter and we'll respond with the next
|
||||||
|
set of data.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
...
|
||||||
|
"pagination": {
|
||||||
|
"next_url": "https://api.instagram.com/v1/tags/puppy/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&max_id=13872296",
|
||||||
|
"next_max_id": "13872296"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On views where pagination is present, we also support the `count` parameter.
|
||||||
|
Simply set this to the number of items you'd like to receive. Note that the default values
|
||||||
|
should be fine for most applications - but if you decide to increase this number there is a maximum value
|
||||||
|
defined on each endpoint.
|
||||||
|
|
||||||
|
# JSONP
|
||||||
|
This is a sample server Petstore server.
|
||||||
|
You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on
|
||||||
|
[irc.freenode.net, #swagger](http://swagger.io/irc/).
|
||||||
|
For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
https://api.instagram.com/v1/tags/coffee/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&callback=callbackFunction
|
||||||
|
```
|
||||||
|
Would respond with:
|
||||||
|
|
||||||
|
```js
|
||||||
|
callbackFunction({
|
||||||
|
...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
> Example of markdown blockquote
|
||||||
host: "petstore.swagger.io"
|
host: "petstore.swagger.io"
|
||||||
basePath: "/v2"
|
basePath: "/v2"
|
||||||
tags:
|
tags:
|
||||||
-
|
|
||||||
name: "Pagination"
|
|
||||||
x-traitTag: true
|
|
||||||
description: |-
|
|
||||||
Sometimes you just can't get enough. For this reason, we've provided a convenient way to access more data in
|
|
||||||
any request for sequential data. Simply call the url in the next_url parameter and we'll respond with the next
|
|
||||||
set of data.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
...
|
|
||||||
"pagination": {
|
|
||||||
"next_url": "https://api.instagram.com/v1/tags/puppy/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&max_id=13872296",
|
|
||||||
"next_max_id": "13872296"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
On views where pagination is present, we also support the `count` parameter.
|
|
||||||
Simply set this to the number of items you'd like to receive. Note that the default values
|
|
||||||
should be fine for most applications - but if you decide to increase this number there is a maximum value
|
|
||||||
defined on each endpoint.
|
|
||||||
externalDocs:
|
|
||||||
description: "Find out more"
|
|
||||||
url: "http://swagger.io"
|
|
||||||
-
|
|
||||||
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:
|
|
||||||
|
|
||||||
```
|
|
||||||
https://api.instagram.com/v1/tags/coffee/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&callback=callbackFunction
|
|
||||||
```
|
|
||||||
Would respond with:
|
|
||||||
|
|
||||||
```js
|
|
||||||
callbackFunction({
|
|
||||||
...
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
> Example of markdown blockquote
|
|
||||||
externalDocs:
|
|
||||||
description: "Find out more"
|
|
||||||
url: "http://swagger.io"
|
|
||||||
-
|
-
|
||||||
name: "pet"
|
name: "pet"
|
||||||
description: "Everything about your Pets"
|
description: "Everything about your Pets"
|
||||||
|
|
|
@ -19,14 +19,17 @@ require('zone.js/dist/jasmine-patch');
|
||||||
require('../lib/vendor');
|
require('../lib/vendor');
|
||||||
|
|
||||||
var TestBed = require('@angular/core/testing').TestBed;
|
var TestBed = require('@angular/core/testing').TestBed;
|
||||||
|
var ErrorHandler = require('@angular/core').ErrorHandler;
|
||||||
var BrowserDynamicTestingModule = require('@angular/platform-browser-dynamic/testing').BrowserDynamicTestingModule;
|
var BrowserDynamicTestingModule = require('@angular/platform-browser-dynamic/testing').BrowserDynamicTestingModule;
|
||||||
var platformBrowserDynamicTesting = require('@angular/platform-browser-dynamic/testing').platformBrowserDynamicTesting;
|
var platformBrowserDynamicTesting = require('@angular/platform-browser-dynamic/testing').platformBrowserDynamicTesting;
|
||||||
|
|
||||||
|
var SpecManager = require('../lib/utils/spec-manager').SpecManager;
|
||||||
var services = require('../lib/services/index');
|
var services = require('../lib/services/index');
|
||||||
var SpecManager = require('../lib/utils/SpecManager').SpecManager;
|
|
||||||
var BrowserDomAdapter = require('@angular/platform-browser/src/browser/browser_adapter').BrowserDomAdapter;
|
|
||||||
var REDOC_PIPES = require('../lib/utils/pipes').REDOC_PIPES;
|
var REDOC_PIPES = require('../lib/utils/pipes').REDOC_PIPES;
|
||||||
var REDOC_COMMON_DIRECTIVES = require('../lib/shared/components/index').REDOC_COMMON_DIRECTIVES;
|
var sharedComponents = require('../lib/shared/components/');
|
||||||
var REDOC_DIRECTIVES = require('../lib/components/index').REDOC_DIRECTIVES;
|
var REDOC_COMMON_DIRECTIVES = sharedComponents.REDOC_COMMON_DIRECTIVES;
|
||||||
|
var components = require('../lib/components/');
|
||||||
|
var REDOC_DIRECTIVES = components.REDOC_DIRECTIVES;
|
||||||
|
|
||||||
TestBed.initTestEnvironment(
|
TestBed.initTestEnvironment(
|
||||||
BrowserDynamicTestingModule,
|
BrowserDynamicTestingModule,
|
||||||
|
@ -36,18 +39,25 @@ TestBed.initTestEnvironment(
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
BrowserDomAdapter,
|
|
||||||
SpecManager,
|
SpecManager,
|
||||||
BrowserDomAdapter,
|
services.AppStateService,
|
||||||
services.RedocEventsService,
|
|
||||||
services.ScrollService,
|
services.ScrollService,
|
||||||
services.Hash,
|
services.Hash,
|
||||||
services.MenuService,
|
services.MenuService,
|
||||||
services.WarningsService,
|
services.WarningsService,
|
||||||
services.OptionsService
|
services.OptionsService,
|
||||||
|
services.ComponentParser,
|
||||||
|
services.ContentProjector,
|
||||||
|
{ provide: ErrorHandler, useClass: services.CustomErrorHandler },
|
||||||
|
{ provide: services.COMPONENT_PARSER_ALLOWED, useValue: { 'security-definitions': components.SecurityDefinitions }}
|
||||||
],
|
],
|
||||||
declarations: [REDOC_PIPES, REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES]
|
declarations: [REDOC_PIPES, REDOC_DIRECTIVES, REDOC_COMMON_DIRECTIVES]
|
||||||
});
|
});
|
||||||
|
TestBed.overrideModule(BrowserDynamicTestingModule, {
|
||||||
|
set: {
|
||||||
|
entryComponents: [ sharedComponents.DynamicNg2Wrapper, components.SecurityDefinitions ]
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { SpecManager } from '../../lib/utils/SpecManager';
|
import { SpecManager } from '../../lib/utils/spec-manager';
|
||||||
describe('Utils', () => {
|
describe('Utils', () => {
|
||||||
describe('Schema manager', () => {
|
describe('Schema manager', () => {
|
||||||
let specMgr;
|
let specMgr;
|
||||||
|
@ -75,9 +75,9 @@ describe('Utils', () => {
|
||||||
|
|
||||||
describe('byPointer method', () => {
|
describe('byPointer method', () => {
|
||||||
it('should return correct schema part', ()=> {
|
it('should return correct schema part', ()=> {
|
||||||
let part = specMgr.byPointer('/tags/3');
|
let part = specMgr.byPointer('/tags/0');
|
||||||
part.should.be.deepEqual(specMgr.schema.tags[3]);
|
part.should.be.deepEqual(specMgr.schema.tags[0]);
|
||||||
part.should.be.equal(specMgr.schema.tags[3]);
|
part.should.be.equal(specMgr.schema.tags[0]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return null for incorrect pointer', ()=> {
|
it('should return null for incorrect pointer', ()=> {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import {KeysPipe, ValuesPipe, JsonPointerEscapePipe, MarkedPipe} from '../../lib/utils/pipes';
|
import {KeysPipe, JsonPointerEscapePipe, MarkedPipe} from '../../lib/utils/pipes';
|
||||||
|
|
||||||
describe('Pipes', () => {
|
describe('Pipes', () => {
|
||||||
describe('KeysPipe and ValuesPipe', () => {
|
describe('KeysPipe', () => {
|
||||||
let obj;
|
let obj;
|
||||||
var keysPipe, valuesPipe;
|
var keysPipe;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
obj = {
|
obj = {
|
||||||
|
@ -14,7 +14,6 @@ describe('Pipes', () => {
|
||||||
c: 3
|
c: 3
|
||||||
};
|
};
|
||||||
keysPipe = new KeysPipe();
|
keysPipe = new KeysPipe();
|
||||||
valuesPipe = new ValuesPipe();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('KeysPipe transform', () => {
|
describe('KeysPipe transform', () => {
|
||||||
|
@ -28,22 +27,6 @@ describe('Pipes', () => {
|
||||||
(() => keysPipe.transform('45')).should.throw();
|
(() => keysPipe.transform('45')).should.throw();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw on blank input', () => {
|
|
||||||
(() => valuesPipe.transform()).should.not.throw();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('KeysPipe transform', () => {
|
|
||||||
it('should return values', () => {
|
|
||||||
var val = valuesPipe.transform(obj);
|
|
||||||
val.should.be.deepEqual([1, 2, 3]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not support other objects', () => {
|
|
||||||
(() => valuesPipe.transform(45)).should.throw();
|
|
||||||
(() => valuesPipe.transform('45')).should.throw();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not throw on blank input', () => {
|
it('should not throw on blank input', () => {
|
||||||
(() => keysPipe.transform()).should.not.throw();
|
(() => keysPipe.transform()).should.not.throw();
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"should",
|
"should",
|
||||||
"requirejs"
|
"requirejs"
|
||||||
],
|
],
|
||||||
"noEmitHelpers": false
|
"noEmitHelpers": true
|
||||||
},
|
},
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"exclude": [
|
"exclude": [
|
||||||
|
|
11
tslint.json
11
tslint.json
|
@ -38,12 +38,21 @@
|
||||||
|
|
||||||
"directive-selector-type": [true, "attribute"],
|
"directive-selector-type": [true, "attribute"],
|
||||||
"component-selector-type": [true, "element"],
|
"component-selector-type": [true, "element"],
|
||||||
|
"component-selector-name": [true, "kebab-case"],
|
||||||
|
"directive-selector-name": [true, "camelCase"],
|
||||||
"use-input-property-decorator": true,
|
"use-input-property-decorator": true,
|
||||||
"use-output-property-decorator": true,
|
"use-output-property-decorator": true,
|
||||||
"use-host-property-decorator": true,
|
"use-host-property-decorator": true,
|
||||||
"no-input-rename": true,
|
"no-input-rename": true,
|
||||||
"no-output-rename": true,
|
"no-output-rename": true,
|
||||||
|
"pipe-naming": [true, "camelCase"],
|
||||||
|
"import-destructuring-spacing": true,
|
||||||
"use-life-cycle-interface": true,
|
"use-life-cycle-interface": true,
|
||||||
"use-pipe-transform-interface": true
|
"use-pipe-transform-interface": true,
|
||||||
|
"templates-use-public": true,
|
||||||
|
"no-access-missing-member": true,
|
||||||
|
"invoke-injectable": true,
|
||||||
|
"no-forward-ref": true,
|
||||||
|
"no-attribute-parameter-decorator": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user