Merge commit '5f044b900ff4fa3632144b13c0ea88545dbdf5c5' into releases

This commit is contained in:
RedocBot 2016-07-29 10:53:54 +00:00 committed by travis@localhost
commit 639acb35b6
69 changed files with 3365 additions and 390 deletions

View File

@ -1,17 +1,30 @@
# ReDoc
**OpenAPI/Swagger-generated API Reference Documentation**
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![Tested on APIs.guru](http://api.apis.guru/badges/tested_on.svg)](https://APIs.guru) [![Code Climate](https://codeclimate.com/github/Rebilly/ReDoc/badges/gpa.svg)](https://codeclimate.com/github/Rebilly/ReDoc) [![David](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![Stories in Ready](https://badge.waffle.io/Rebilly/ReDoc.png?label=ready&title=Ready)](https://waffle.io/Rebilly/ReDoc)
[![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![Bower](http://img.shields.io/bower/v/redoc.svg)](http://bower.io/) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
[![Browser Compatibility](https://saucelabs.com/browser-matrix/redoc.svg)](https://saucelabs.com/u/redoc)
Swagger-generated API Reference Documentation
![ReDoc demo](demo/redoc-demo.png)
[Live demo](http://rebilly.github.io/ReDoc/)
## [Live demo](http://rebilly.github.io/ReDoc/)
## Roadmap
- [ ] docs pre-rendering (performance and SEO)
- [ ] ability to simple customization
- [ ] built-in API Console
## Releases
We host latest and all the previous ReDoc releases on GitHub Pages-based **CDN**:
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
- particular release, e.g. `v0.16.1`: https://rebilly.github.io/ReDoc/releases/v0.16.0/redoc.min.js
## Deployment
#### tl;dr
### TL;DR
```html
<!DOCTYPE html>
<html>
@ -21,28 +34,24 @@ Swagger-generated API Reference Documentation
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--
ReDoc uses font options from the parent element
So override default browser styles
ReDoc doesn't change outer page styles
-->
<style>
body {
margin: 0;
padding: 0;
font-family: Verdana, Geneva, sans-serif;
font-size: 14px;
color: #333;
}
</style>
</head>
<body>
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'>
</redoc>
<script src="bower_components/redoc/dist/redoc.min.js"> </script>
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
</body>
</html>
```
That's all folks!
#### 1. Install redoc
### 1. Install ReDoc (skip this step for CDN)
Install using [bower](bower.io):
bower install redoc
@ -51,12 +60,13 @@ or using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
npm install redoc --save
Alternatively, you can **reference redoc directly** from CDN:
- latest release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
- particular release, e.g. v0.14.0: https://rebilly.github.io/ReDoc/releases/v0.14.0/redoc.min.js
### 2. Reference redoc script in HTML
For **CDN**:
```html
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
```
#### 2. Reference redoc script in HTML
Then reference [`redoc.min.js`](https://raw.githubusercontent.com/Rebilly/ReDoc/releases/dist/redoc.min.js) in your HTML page:
For bower:
```html
<script src="bower_components/redoc/dist/redoc.min.js"> </script>
```
@ -65,28 +75,29 @@ For npm:
<script src="node_modules/redoc/dist/redoc.min.js"> </script>
```
#### 3. Add `<redoc>` element to your page
### 3. Add `<redoc>` element to your page
```html
<redoc spec-url="<url to your spec>"></redoc>
<redoc spec-url="url/to/your/spec"></redoc>
```
#### 4. Enjoy :smile:
### 4. Enjoy :smile:
## Configuration
#### Swagger vendor extensions
### Swagger vendor extensions
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-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
* [`x-code-samples`](docs/redoc-vendor-extensions.md#x-code-samples) - specify operation code samples
#### Options
* `spec-url` - relative or absolute url to your spec file
* `scroll-y-offset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc.
### Options
* `spec-url` - relative or absolute url to your spec file;
* `scroll-y-offset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc;
`scroll-y-offset` can be specified in various ways:
* **number**: A fixed number of pixels to be used as offset
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset.
* **function**: A getter function. Must return a number representing the offset (in pixels).
* **number**: A fixed number of pixels to be used as offset;
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset;
* **function**: A getter function. Must return a number representing the offset (in pixels);
* `suppress-warnings` - if set, warnings are not rendered at the top of page (they still are logged to the console).
## Advanced usage
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:

View File

@ -4,19 +4,20 @@
<title>ReDoc</title>
<link rel="stylesheet" href="main.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@import url(//fonts.googleapis.com/css?family=Roboto:300,400,700);
@import url(//fonts.googleapis.com/css?family=Montserrat:400,700);
</style>
<script src="https://cdn.vaadin.com/vaadin-core-elements/latest/webcomponentsjs/webcomponents-lite.min.js"></script>
<link rel="import" href="https://cdn.vaadin.com/vaadin-core-elements/master/vaadin-combo-box/vaadin-combo-box-light.html">
</head>
<body>
<nav>
<header> ReDoc </header>
<form id="schema-url-form">
<input id="schema-url-input" value='http://rebilly.github.io/SwaggerTemplateRepo/swagger.yaml'>
<template is="dom-bind" id="specs">
<form id="schema-url-form" is="iron-form">
<vaadin-combo-box-light id="spec-input" items="[[specs]]" attr-for-value="value" allow-custom-value>
<input placeholder="URL to a spec to try" id="schema-url-input" type="text" is="iron-input" value="https://rebilly.github.io/RebillyAPI/swagger.json">
</vaadin-combo-box-light>
<button type="submit"> Explore </button>
</form>
</template>
<iframe src="https://ghbtns.com/github-btn.html?user=Rebilly&repo=ReDoc&type=star&count=true&size=large"
frameborder="0" scrolling="0" width="130px" height="30px"></iframe>
</nav>

View File

@ -1,6 +1,3 @@
/*@import url(http://fonts.googleapis.com/css?family=Roboto:300,400,700);
@import url(http://fonts.googleapis.com/css?family=Montserrat:300,400);*/
body {
margin: 0;
padding-top: 50px;
@ -26,6 +23,7 @@ body {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
text-size-adjust: 100%;
font-family: Monserrat, sans-serif;
}
nav input, nav button {
@ -49,11 +47,11 @@ nav input {
width: 50%;
box-sizing: border-box;
max-width: 500px;
padding: 0 10px;
color: #555;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
@ -80,7 +78,6 @@ nav button {
-ms-user-select: none;
user-select: none;
border: 1px solid #ccc;
border-radius: 4px;
}
nav button:hover {

View File

@ -2,19 +2,12 @@
'use strict';
var schemaUrlForm = document.getElementById('schema-url-form');
var schemaUrlInput = document.getElementById('schema-url-input');
schemaUrlForm.addEventListener('submit', function(event) {
event.preventDefault();
event.stopPropagation();
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value)
return false;
})
var schemaUrlInput;
var url = window.location.search.match(/url=([^&]+)/);
if (url && url.length > 1) {
url = decodeURIComponent(url[1]);
document.getElementsByTagName('redoc')[0].setAttribute('spec-url', url);
schemaUrlInput.value = url;
}
function updateQueryStringParameter(uri, key, value) {
@ -31,5 +24,35 @@
return uri + separator + key + "=" + value + hash;
}
}
var specs = document.querySelector('#specs');
specs.addEventListener('dom-change', function() {
schemaUrlForm = document.getElementById('schema-url-form');
schemaUrlInput = document.getElementById('schema-url-input');
schemaUrlForm.addEventListener('submit', function(event) {
console.log('test')
event.preventDefault();
event.stopPropagation();
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value)
return false;
})
schemaUrlInput.addEventListener('mousedown', function(e) {
e.stopPropagation();
});
schemaUrlInput.value = url;
specs.specs = [
'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml',
'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml',
'https://api.apis.guru/v2/specs/data2crm.com/1/swagger.yaml',
'https://api.apis.guru/v2/specs/graphhopper.com/1.0/swagger.yaml'
];
var $specInput = document.getElementById('spec-input');
$specInput.addEventListener('value-changed', function(e) {
schemaUrlInput.value = e.detail.value;
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
});
});
//window.redocDebugMode = true;
})();

BIN
demo/redoc-demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

View File

@ -1,10 +1,32 @@
swagger: '2.0'
schemes:
- http
- https
host: petstore.swagger.io
basePath: /v2
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.'
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.
# Introduction
This API is documented in **OpenAPI format** and is based on
[Pestore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
# OpenAPI Specification
This API is documented in **OpenAPI format** and is based on
[Pestore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
# Cross-Origin Resource Sharing
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.
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
title: Swagger Petstore
termsOfService: 'http://swagger.io/terms/'
@ -16,47 +38,15 @@ info:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
externalDocs:
description: Find out more about Swagger
url: 'http://swagger.io'
description: Find out how to create Github repo for your OpenAPI spec.
url: 'https://github.com/Rebilly/generator-openapi-repo'
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:\n```\n https://api.instagram.com/v1/tags/coffee/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&callback=callbackFunction\n```\nWould respond with:\n```js\ncallbackFunction({\n ...\n});\n``` \n > Example of markdown blockquote"
externalDocs:
description: Find out more
url: 'http://swagger.io'
- name: pet
description: Everything about your Pets
externalDocs:
description: Find out more
url: 'http://swagger.io'
- name: store
description: Access to Petstore orders
- name: user
description: Operations about user
externalDocs:
description: Find out more about our store
url: 'http://swagger.io'
securityDefinitions:
petstore_auth:
type: oauth2
@ -75,7 +65,7 @@ paths:
tags:
- pet
summary: Add a new pet to the store
description: ''
description: Add new pet to the store inventory.
operationId: addPet
consumes:
- application/json
@ -154,7 +144,6 @@ paths:
get:
tags:
- pet
- JSONP
summary: Find pet by ID
description: Returns a single pet
operationId: getPetById
@ -242,40 +231,6 @@ paths:
- 'write:pets'
- 'read:pets'
'/pet/{petId}/uploadImage':
get:
tags:
- pet
summary: uploads an image
description: ''
operationId: uploadFile
consumes:
- image/jpeg
- image/png
produces:
- image/jpeg
- image/png
parameters:
- name: petId
in: path
description: ID of pet to update
required: true
type: integer
format: int64
- name: file
in: body
description: file to upload
required: false
schema:
type: file
responses:
'200':
description: successful operation
schema:
type: file
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
post:
tags:
- pet
@ -316,8 +271,6 @@ paths:
get:
tags:
- pet
- Pagination
- JSONP
summary: Finds Pets by status
description: Multiple status values can be provided with comma seperated strings
operationId: findPetsByStatus
@ -355,8 +308,6 @@ paths:
get:
tags:
- pet
- Pagination
- JSONP
summary: Finds Pets by tags
description: 'Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.'
operationId: findPetsByTags
@ -389,7 +340,6 @@ paths:
get:
tags:
- store
- JSONP
summary: Returns pet inventories by status
description: Returns a map of status codes to quantities
operationId: getInventory
@ -434,7 +384,6 @@ paths:
get:
tags:
- store
- JSONP
summary: Find purchase order by ID
description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions'
operationId: getOrderById
@ -504,7 +453,6 @@ paths:
get:
tags:
- user
- JSONP
summary: Get user by user name
description: ''
operationId: getUserByName
@ -642,6 +590,8 @@ paths:
description: successful operation
schema:
type: string
examples:
application/json: OK
headers:
X-Rate-Limit:
type: integer
@ -699,10 +649,13 @@ definitions:
type: object
properties:
id:
type: integer
format: int64
description: Category ID
allOf:
- $ref: '#/definitions/Id'
name:
description: Category name
type: string
minLength: 1
xml:
name: Category
Dog:
@ -714,24 +667,44 @@ definitions:
packSize:
type: integer
format: int32
description: the size of the pack the dog is from
default: 0
minimum: 0
description: The size of the pack the dog is from
default: 1
minimum: 1
required:
- packSize
HoneyBee:
description: A representation of a honey bee
allOf:
- $ref: '#/definitions/Pet'
- type: object
properties:
honeyPerDay:
type: number
description: Average amount of honey produced per day in ounces
example: 3.14
required:
- honeyPerDay
Id:
type: integer
format: int64
Order:
type: object
properties:
id:
type: integer
format: int64
description: Order ID
allOf:
- $ref: '#/definitions/Id'
petId:
type: integer
format: int64
description: Pet ID
allOf:
- $ref: '#/definitions/Id'
quantity:
type: integer
format: int32
minimum: 1
default: 1
shipDate:
description: Estimated ship date
type: string
format: date-time
status:
@ -742,6 +715,7 @@ definitions:
- approved
- delivered
complete:
description: Indicates whenever order was completed or not
type: boolean
default: false
xml:
@ -754,23 +728,31 @@ definitions:
discriminator: petType
properties:
petType:
description: Type of a pet
type: string
id:
type: integer
format: int64
description: Pet ID
allOf:
- $ref: '#/definitions/Id'
category:
$ref: '#/definitions/Category'
description: Categories this pet belongs to
allOf:
- $ref: '#/definitions/Category'
name:
description: The name given to a pet
type: string
example: doggie
example: Guru
photoUrls:
description: The list of URL to a cute photos featuring pet
type: array
xml:
name: photoUrl
wrapped: true
items:
type: string
format: url
tags:
description: Tags attached to the pet
type: array
xml:
name: tag
@ -779,7 +761,7 @@ definitions:
$ref: '#/definitions/Tag'
status:
type: string
description: pet status in the store
description: Pet status in the store
enum:
- available
- pending
@ -790,33 +772,56 @@ definitions:
type: object
properties:
id:
type: integer
format: int64
description: Tag ID
allOf:
- $ref: '#/definitions/Id'
name:
description: Tag name
type: string
minLength: 1
xml:
name: Tag
User:
type: object
properties:
id:
type: integer
format: int64
description: User ID
$ref: '#/definitions/Id'
username:
description: User supplied username
type: string
minLength: 4
example: John78
firstName:
description: User first name
type: string
minLength: 1
example: John
lastName:
description: User last name
type: string
minLength: 1
example: Smith
email:
description: User email address
type: string
format: email
example: john.smith@example.com
password:
type: string
description: 'User password, MUST contain a mix of upper and lower case letters, as well as digits'
format: password
minLength: 8
pattern: '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'
example: drowssaP123
phone:
description: User phone number in international format
type: string
pattern: "^\\+(?:[0-9]-?){6,14}[0-9]$"
example: +1-202-555-0192
userStatus:
description: User status
type: integer
format: int32
description: User Status
xml:
name: User

View File

@ -1,9 +1,9 @@
# ReDoc vendor extensions
ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions)
### <a name="infoObject"></a>[Info Object](http://swagger.io/specification/#infoObject) vendor extensions
#### <a name="x-logo"></a> x-logo
### Info Object vendor extensions
Extends OpenAPI [Info Object](http://swagger.io/specification/#infoObject)
#### x-logo
| Field Name | Type | Description |
| :------------- | :-----------: | :---------- |
@ -47,9 +47,9 @@ info:
### [Tag object](http://swagger.io/specification/#tagObject) vendor extensions
#### <a name="x-traitTag"></a> x-traitTag
### Tag Object vendor extensions
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
#### x-traitTag
| 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) |
@ -74,9 +74,9 @@ description: Pagination description (can use markdown syntax)
x-traitTag: true
```
### [Operation Object](http://swagger.io/specification/#operationObject) vendor extensions
#### <a name="x-code-samples"></a> x-code-samples
### Operation Object vendor extensions
Extends OpenAPI [Operation Object](http://swagger.io/specification/#operationObject)
#### x-code-samples
| Field Name | Type | Description |
| :------------- | :------: | :---------- |
| x-code-samples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation |

View File

@ -1,21 +1,21 @@
<div>
<h1 class="api-info-header">{{data.title}} ({{data.version}})</h1>
<p *ngIf="data.description" [innerHtml]="data.description | marked"> </p>
<p>
<!-- TODO: create separate components for contact and license ? -->
<span *ngIf="data.contact"> Contact:
<a *ngIf="data.contact.url" href="{{data.contact.url}}">
{{data.contact.name || data.contact.url}}</a>
<a *ngIf="data.contact.email" href="mailto:{{data.contact.email}}">
{{data.contact.email}}</a>
</span>
<span *ngIf="data.license"> License:
<a *ngIf="data.license.url" href="{{data.license.url}}"> {{data.license.name}} </a>
<span *ngIf="!data.license.url"> {{data.license.name}} </span>
</span>
</p>
<h1 class="api-info-header">{{info.title}} ({{info.version}})</h1>
<p>
Download OpenAPI (fka Swagger) specification:
<a class="openapi-button" target="_blank" attr.href='{{specUrl}}'> Download </a>
</p>
<p *ngIf="info.description" [innerHtml]="info['x-redoc-html-description'] | safe"> </p>
<p>
<!-- TODO: create separate components for contact and license ? -->
<span *ngIf="info.contact"> Contact:
<a *ngIf="info.contact.url" href="{{info.contact.url}}">
{{info.contact.name || info.contact.url}}</a>
<a *ngIf="info.contact.email" href="mailto:{{info.contact.email}}">
{{info.contact.email}}</a>
</span>
<span *ngIf="info.license"> License:
<a *ngIf="info.license.url" href="{{info.license.url}}"> {{info.license.name}} </a>
<span *ngIf="!info.license.url"> {{info.license.name}} </span>
</span>
</p>
</div>

View File

@ -6,6 +6,8 @@
:host > div {
width: 60%;
padding: 40px;
box-sizing: border-box;
}
a.openapi-button {
@ -15,3 +17,8 @@ a.openapi-button {
margin-left: 0.5em;
font-weight: normal;
}
:host [section] {
padding-top: 60px;
margin-top: 20px;
}

View File

@ -2,6 +2,7 @@
import { getChildDebugElement } from '../../../tests/helpers';
import { Component } from '@angular/core';
import { OptionsService } from '../../services/index';
import {
inject,
@ -17,8 +18,14 @@ describe('Redoc components', () => {
let builder;
let component;
let fixture;
let opts;
beforeEach(async(inject([TestComponentBuilder, SpecManager], (tcb, specMgr) => {
beforeEach(async(inject([TestComponentBuilder, SpecManager, OptionsService], (tcb, specMgr, _opts) => {
opts = _opts;
opts.options = {
scrollYOffset: () => 0,
$scrollParent: window
};
builder = tcb;
return specMgr.load('/tests/schemas/api-info-test.json');
})));
@ -32,8 +39,8 @@ describe('Redoc components', () => {
it('should init component data', () => {
expect(component).not.toBeNull();
expect(component.data).not.toBeNull();
component.data.title.should.be.equal('Swagger Petstore');
expect(component.info).not.toBeNull();
component.info.title.should.be.equal('Swagger Petstore');
});
it('should render api name and version', () => {

View File

@ -1,7 +1,7 @@
'use strict';
import { SpecManager, RedocComponent, BaseComponent } from '../base';
import { OptionsService } from '../../services/index';
import { OptionsService, MenuService } from '../../services/index';
@RedocComponent({
selector: 'api-info',
@ -9,17 +9,17 @@ import { OptionsService } from '../../services/index';
templateUrl: './api-info.html'
})
export class ApiInfo extends BaseComponent {
data: any;
info: any;
specUrl: String;
constructor(specMgr:SpecManager, private optionsService:OptionsService) {
constructor(specMgr:SpecManager, private optionsService:OptionsService, private menuServ: MenuService) {
super(specMgr);
}
prepareModel() {
this.data = this.componentSchema.info;
init() {
this.info = this.componentSchema.info;
this.specUrl = this.optionsService.options.specUrl;
if (parseInt(this.data.version.substring(0, 1)) !== NaN) {
this.data.version = 'v' + this.data.version;
if (parseInt(this.info.version.substring(0, 1)) !== NaN) {
this.info.version = 'v' + this.info.version;
}
}
}

View File

@ -1 +1 @@
<img *ngIf="data.imgUrl" [attr.src]="data.imgUrl" [ngStyle]="{'background-color': data.bgColor}">
<img *ngIf="logo.imgUrl" [attr.src]="logo.imgUrl" [ngStyle]="{'background-color': logo.bgColor}">

View File

@ -35,11 +35,11 @@ describe('Redoc components', () => {
it('should init component data', () => {
expect(component).not.toBeNull();
expect(component.data).not.toBeNull();
expect(component.logo).not.toBeNull();
});
it('should not display image when no x-logo', () => {
component.data.should.be.empty();
component.logo.should.be.empty();
let nativeElement = getChildDebugElement(fixture.debugElement, 'api-logo').nativeElement;
let imgElement = nativeElement.querySelector('img');
expect(imgElement).toBeNull();
@ -49,8 +49,8 @@ describe('Redoc components', () => {
});
it('should load values from spec and use transparent bgColor by default', () => {
component.data.imgUrl.should.endWith('petstore-logo.png');
component.data.bgColor.should.be.equal('transparent');
component.logo.imgUrl.should.endWith('petstore-logo.png');
component.logo.bgColor.should.be.equal('transparent');
});
});
});

View File

@ -8,16 +8,16 @@ import {RedocComponent, BaseComponent, SpecManager} from '../base';
templateUrl: './api-logo.html'
})
export class ApiLogo extends BaseComponent {
data:any = {};
logo:any = {};
constructor(specMgr:SpecManager) {
super(specMgr);
}
prepareModel() {
init() {
let logoInfo = this.componentSchema.info['x-logo'];
if (!logoInfo) return;
this.data.imgUrl = logoInfo.url;
this.data.bgColor = logoInfo.backgroundColor || 'transparent';
this.logo.imgUrl = logoInfo.url;
this.logo.bgColor = logoInfo.backgroundColor || 'transparent';
}
}

View File

@ -23,9 +23,11 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
@Input() nestOdd: boolean;
@Input() childFor: string;
@Input() isArray: boolean;
disableLazy: boolean = false;
loaded: boolean = false;
constructor(private specMgr:SpecManager, private location:ViewContainerRef, private elementRef:ElementRef,
private resolver:ComponentResolver, private optionsService:OptionsService, private _renderer: Renderer) {
this.disableLazy = this.optionsService.options.disableLazySchemas;
}
normalizePointer() {
@ -67,7 +69,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
// skip caching view with tabs inside (discriminator)
// as it needs attached controller
if (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas) {
if (!this.disableLazy && (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas)) {
this._loadAfterSelf();
return;
}
@ -85,11 +87,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
}
ngAfterViewInit() {
if (this.optionsService.options.disableLazySchemas) {
this._loadAfterSelf();
return;
}
if (!this.auto) return;
if (!this.auto && !this.disableLazy) return;
this.loadCached();
}

View File

@ -23,6 +23,7 @@ describe('Redoc components', () => {
builder = tcb;
specMgr = _spec;
}));
beforeEach(() => {
fixture = builder.createSync(TestAppComponent);
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema');
@ -37,14 +38,14 @@ describe('Redoc components', () => {
});
it('should set isTrivial for non-object/array types', () => {
component.pointer = '';
component.pointer = '#';
(<any>specMgr)._schema = {type: 'string'};
fixture.detectChanges();
component.schema.isTrivial.should.be.true();
});
it('should use < * > notation for prop without type', () => {
component.pointer = '';
component.pointer = '#';
(<any>specMgr)._schema = {type: 'object', properties: {
test: {}
}};

View File

@ -16,7 +16,7 @@ import { Zippy } from '../../shared/components/Zippy/zippy';
detect: true
})
export class JsonSchema extends BaseComponent {
schema: any;
schema: any = {};
activeDescendant:any = {};
hasDescendants: boolean = false;
_hasSubSchemas: boolean = false;
@ -68,7 +68,8 @@ export class JsonSchema extends BaseComponent {
this.selectDescendant(0);
}
prepareModel() {
init() {
if (!this.pointer) return;
if (this.nestOdd) {
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'nestodd', 'true');
}

View File

@ -1,13 +1,13 @@
<div class="method">
<div class="method-content">
<h2 class="method-header sharable-header">
<a class="share-link" href="#{{data.methodAnchor}}"></a>{{data.summary}}
<a class="share-link" href="#{{method.anchor}}"></a>{{method.summary}}
</h2>
<div class="method-tags" *ngIf="data.methodInfo.tags.length">
<a *ngFor="let tag of data.methodInfo.tags" attr.href="#{{tag}}"> {{tag}} </a>
<div class="method-tags" *ngIf="method.info.tags.length">
<a *ngFor="let tag of method.info.tags" attr.href="#tag/{{tag}}"> {{tag}} </a>
</div>
<p *ngIf="data.methodInfo.description" class="method-description"
[innerHtml]="data.methodInfo.description | marked">
<p *ngIf="method.info.description" class="method-description"
[innerHtml]="method.info.description | marked">
</p>
<params-list pointer="{{pointer}}/parameters"> </params-list>
<responses-list pointer="{{pointer}}/responses"> </responses-list>
@ -16,15 +16,15 @@
<h5>Definition</h5>
<div class="method-endpoint">
<h5 class="http-method" [ngClass]="data.httpMethod">{{data.httpMethod}}</h5>
<h5 class="http-method" [ngClass]="method.httpMethod">{{method.httpMethod}}</h5>
<span select-on-click><!--
--><span class="api-url">{{data.apiUrl}}</span><span class="path">{{data.path}}</span><!--
--><span class="api-url">{{method.apiUrl}}</span><span class="path">{{method.path}}</span><!--
--></span>
</div>
<div *ngIf="data.bodyParam">
<div *ngIf="method.bodyParam">
<br>
<request-samples [pointer]="pointer" [schemaPointer]="data.bodyParam._pointer">
<request-samples [pointer]="pointer" [schemaPointer]="method.bodyParam._pointer">
</request-samples>
</div>
<div>

View File

@ -1,5 +1,5 @@
@import '../../shared/styles/variables';
@import '../../shared/styles/share-link';
:host {
padding-bottom: 100px;
@ -11,6 +11,10 @@
border-bottom: 0;
}
h2 {
color: $secondary-color;
}
responses-list, params-list {
display: block;
}

View File

@ -34,14 +34,14 @@ describe('Redoc components', () => {
});
it('should init basic component data', () => {
component.data.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
component.data.httpMethod.should.be.equal('put');
component.data.path.should.be.equal('/user/{username}');
component.method.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
component.method.httpMethod.should.be.equal('put');
component.method.path.should.be.equal('/user/{username}');
});
it('should main tag', () => {
component.data.methodInfo.tags.should.be.empty();
component.method.info.tags.should.be.empty();
});
});
});

View File

@ -20,25 +20,25 @@ import { SchemaHelper } from '../../services/schema-helper.service';
detect: true
})
export class Method extends BaseComponent {
data:any;
method:any;
@Input() tag:string;
constructor(specMgr:SpecManager) {
super(specMgr);
}
prepareModel() {
this.data = {};
this.data.apiUrl = this.specMgr.apiUrl;
this.data.httpMethod = JsonPointer.baseName(this.pointer);
this.data.path = JsonPointer.baseName(this.pointer, 2);
this.data.methodInfo = this.componentSchema;
this.data.methodInfo.tags = this.filterMainTags(this.data.methodInfo.tags);
this.data.bodyParam = this.findBodyParam();
this.data.summary = SchemaHelper.methodSummary(this.componentSchema);
init() {
this.method = {};
this.method.apiUrl = this.specMgr.apiUrl;
this.method.httpMethod = JsonPointer.baseName(this.pointer);
this.method.path = JsonPointer.baseName(this.pointer, 2);
this.method.info = this.componentSchema;
this.method.info.tags = this.filterMainTags(this.method.info.tags);
this.method.bodyParam = this.findBodyParam();
this.method.summary = SchemaHelper.methodSummary(this.componentSchema);
if (this.componentSchema.operationId) {
this.data.methodAnchor = 'operation/' + encodeURIComponent(this.componentSchema.operationId);
this.method.anchor = 'operation/' + encodeURIComponent(this.componentSchema.operationId);
} else {
this.data.methodAnchor = 'tag/' + encodeURIComponent(this.tag + this.pointer);
this.method.anchor = this.tag + encodeURIComponent(this.pointer);
}
}

View File

@ -1,10 +1,10 @@
<div class="methods">
<div class="tag" *ngFor="let tag of data.tags;trackBy:trackByTagName">
<div class="tag-info" [attr.tag]="tag.name" *ngIf="!tag.empty">
<div class="tag" *ngFor="let tag of tags;trackBy:trackByTagName">
<div class="tag-info" [attr.section]="tag.id" *ngIf="!tag.virtual">
<h1 class="sharable-header"> <a class="share-link" href="#tag/{{tag.name | encodeURIComponent}}"></a>{{tag.name}} </h1>
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
</div>
<method *ngFor="let method of tag.methods;trackBy:trackByPointer" [pointer]="method.pointer" [attr.pointer]="method.pointer"
[attr.tag]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
[attr.section]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
</div>
</div>

View File

@ -1,5 +1,4 @@
@import '../../shared/styles/variables';
@import '../../shared/styles/share-link';
.tag-info {
padding: 40px;
@ -21,6 +20,7 @@
color: $headers-color;
text-transform: capitalize;
font-weight: normal;
margin-top: 0;
}
.methods {

View File

@ -35,12 +35,12 @@ describe('Redoc components', () => {
});
it('should get correct tags list', () => {
expect(component.data.tags).not.toBeNull();
component.data.tags.should.have.lengthOf(2);
component.data.tags[0].name.should.be.equal('traitTag');
component.data.tags[0].methods.should.be.empty();
component.data.tags[1].name.should.be.equal('tag1');
component.data.tags[1].methods.should.have.lengthOf(2);
expect(component.tags).not.toBeNull();
component.tags.should.have.lengthOf(2);
component.tags[0].name.should.be.equal('traitTag');
component.tags[0].methods.should.be.empty();
component.tags[1].name.should.be.equal('tag1');
component.tags[1].methods.should.have.lengthOf(2);
});
});
});

View File

@ -15,26 +15,21 @@ import { SchemaHelper } from '../../services/index';
detect: true
})
export class MethodsList extends BaseComponent {
data:any;
tags:Array<any> = [];
constructor(specMgr:SpecManager) {
super(specMgr);
}
prepareModel() {
this.data = {};
// follow SwaggerUI behavior for cases when one method has more than one tag:
// duplicate methods
init() {
let tags = SchemaHelper.buildMenuTree(this.specMgr.schema);
tags.forEach(tagInfo => {
this.tags = tags.filter(tagInfo => !tagInfo.virtual);
this.tags.forEach(tagInfo => {
// inject tag name into method info
tagInfo.methods = tagInfo.methods || [];
tagInfo.methods.forEach(method => {
method.tag = tagInfo.name;
method.tag = tagInfo.id;
});
});
this.data.tags = tags;
// TODO: check $ref field
}
trackByPointer(idx, el) {

View File

@ -1,5 +1,5 @@
<h5 class="param-list-header" *ngIf="data.params.length"> Parameters </h5>
<template ngFor [ngForOf]="data.params" let-paramType="$implicit">
<h5 class="param-list-header" *ngIf="params.length"> Parameters </h5>
<template ngFor [ngForOf]="params" let-paramType="$implicit">
<header class="paramType">
{{paramType.place}} Parameters
<span class="hint--top-right hint--large" [attr.data-hint]="paramType.placeHint">?</span>
@ -14,6 +14,7 @@
<div>
<span class="param-type {{param.type}}" [ngClass]="{'with-hint': param._displayTypeHint}"
title="{{param._displayTypeHint}}"> {{param._displayType}} {{param._displayFormat}}</span>
<span class="param-range" *ngIf="param._range"> {{param._range}} </span>
<span *ngIf="param.required" class="param-required">Required</span>
<div class="default" *ngIf="param.default">Default: {{param.default | json}}</div>
<div *ngIf="param.enum" class="param-enum">
@ -26,13 +27,13 @@
</div>
</template>
<div *ngIf="data.bodyParam">
<h5 class="param-list-header" *ngIf="data.bodyParam"> Request Body </h5>
<div *ngIf="bodyParam">
<h5 class="param-list-header" *ngIf="bodyParam"> Request Body </h5>
<div class="body-param-description" [innerHtml]="data.bodyParam.description | marked"></div>
<div class="body-param-description" [innerHtml]="bodyParam.description | marked"></div>
<div>
<br>
<json-schema-lazy [isRequestSchema]="true" [auto]="true" pointer="{{data.bodyParam.pointer}}/schema">
<json-schema-lazy [isRequestSchema]="true" [auto]="true" pointer="{{bodyParam.pointer}}/schema">
</json-schema-lazy>
</div>
</div>

View File

@ -18,21 +18,23 @@ function safePush(obj, prop, item) {
})
export class ParamsList extends BaseComponent {
data:any;
params: Array<any>;
empty: boolean;
bodyParam: any;
constructor(specMgr:SpecManager) {
super(specMgr);
}
prepareModel() {
this.data = {};
init() {
this.params = [];
let paramsList = this.specMgr.getMethodParams(this.pointer, true);
paramsList = paramsList.map(paramSchema => {
let propPointer = paramSchema._pointer;
if (paramSchema.in === 'body') return paramSchema;
paramSchema._name = paramSchema.name;
return SchemaHelper.preprocess(paramSchema,propPointer, this.pointer);
return SchemaHelper.preprocess(paramSchema, propPointer, this.pointer);
});
let paramsMap = this.orderParams(paramsList);
@ -40,11 +42,11 @@ export class ParamsList extends BaseComponent {
if (paramsMap.body && paramsMap.body.length) {
let bodyParam = paramsMap.body[0];
bodyParam.pointer = bodyParam._pointer;
this.data.bodyParam = bodyParam;
this.bodyParam = bodyParam;
paramsMap.body = undefined;
}
this.data.noParams = !(Object.keys(paramsMap).length || this.data.bodyParam);
this.empty = !(Object.keys(paramsMap).length || this.bodyParam);
let paramsPlaces = ['path', 'query', 'formData', 'header', 'body'];
let placeHint = {
@ -64,7 +66,7 @@ export class ParamsList extends BaseComponent {
params.push({place: place, placeHint: placeHint[place], params: paramsMap[place]});
}
});
this.data.params = params;
this.params = params;
}
orderParams(params):any {

View File

@ -1,3 +1,6 @@
@import url('//fonts.googleapis.com/css?family=Roboto:300,400,700');
@import url('//fonts.googleapis.com/css?family=Montserrat:400,700');
redoc.loading {
position: relative;
display: block;

View File

@ -4,7 +4,8 @@
<side-menu> </side-menu>
</div>
<div id="api-content">
<api-info> </api-info>
<warnings></warnings>
<api-info></api-info>
<methods-list> </methods-list>
<footer>
<div class="powered-by-badge">

View File

@ -51,10 +51,6 @@ api-info, .side-bar {
padding: 10px 0;
}
api-info {
padding: 40px;
}
api-logo {
display: block;
text-align: center;
@ -150,6 +146,8 @@ api-logo {
color: #383838;
}
}
@import '../../shared/styles/share-link';
}
footer {

View File

@ -12,10 +12,12 @@ import { ApiInfo } from '../ApiInfo/api-info';
import { ApiLogo } from '../ApiLogo/api-logo';
import { MethodsList } from '../MethodsList/methods-list';
import { SideMenu } from '../SideMenu/side-menu';
import { Warnings } from '../Warnings/warnings';
import { StickySidebar } from '../../shared/components/index';
import {SpecManager} from '../../utils/SpecManager';
import { OptionsService, RedocEventsService } from '../../services/index';
import { OptionsService, RedocEventsService, MenuService,
ScrollService, Hash, WarningsService } from '../../services/index';
var dom = new BrowserDomAdapter();
var _modeLocked = false;
@ -25,11 +27,15 @@ var _modeLocked = false;
providers: [
SpecManager,
BrowserDomAdapter,
RedocEventsService
RedocEventsService,
ScrollService,
Hash,
MenuService,
WarningsService
],
templateUrl: './redoc.html',
styleUrls: ['./redoc.css'],
directives: [ ApiInfo, ApiLogo, MethodsList, SideMenu, StickySidebar ],
directives: [ ApiInfo, ApiLogo, MethodsList, SideMenu, StickySidebar, Warnings ],
detect: true,
onPushOnly: false
})

View File

@ -1,10 +1,10 @@
<header *ngIf="data.schemaPointer || data.samples.length"> Request samples </header>
<schema-sample *ngIf="!data.samples.length" [skipReadOnly]="true" [pointer]="data.schemaPointer"> </schema-sample>
<tabs *ngIf="data.samples.length" [selected] = "selectedLang" (change)=changeLangNotify($event)>
<header *ngIf="schemaPointer || samples.length"> Request samples </header>
<schema-sample *ngIf="!samples.length" [skipReadOnly]="true" [pointer]="schemaPointer"> </schema-sample>
<tabs *ngIf="samples.length" [selected] = "selectedLang" (change)=changeLangNotify($event)>
<tab tabTitle="JSON">
<schema-sample [pointer]="data.schemaPointer" [skipReadOnly]="true"> </schema-sample>
<schema-sample [pointer]="schemaPointer" [skipReadOnly]="true"> </schema-sample>
</tab>
<tab *ngFor="let sample of data.samples" [tabTitle]="sample.lang">
<tab *ngFor="let sample of samples" [tabTitle]="sample.lang">
<div class="code-sample">
<div class="action-buttons">
<span copy-button [copyText]="sample.source" class="hint--top-left hint--inversed"><a>Copy</a></span>

View File

@ -24,7 +24,9 @@ import { CopyButton } from '../../shared/components/CopyButton/copy-button.direc
export class RequestSamples extends BaseComponent {
childTabs: Tabs;
selectedLang: EventEmitter<any>;
data: any;
samples: Array<any>;
@Input() schemaPointer:string;
@ViewChildren(Tabs) childQuery:QueryList<Tabs>;
constructor(specMgr:SpecManager, public events:RedocEventsService) {
@ -38,9 +40,8 @@ export class RequestSamples extends BaseComponent {
this.events.samplesLanguageChanged.next(lang);
}
prepareModel() {
this.data = {};
this.data.schemaPointer = JsonPointer.join(this.schemaPointer, 'schema');
this.data.samples = this.componentSchema['x-code-samples'] || [];
init() {
this.schemaPointer = JsonPointer.join(this.schemaPointer, 'schema');;
this.samples = this.componentSchema['x-code-samples'] || [];
}
}

View File

@ -1,5 +1,5 @@
<h2 class="responses-list-header" *ngIf="data.responses.length"> Responses </h2>
<zippy *ngFor="let response of data.responses;trackBy:trackByCode" title="{{response.code}} {{response.description}}"
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
<zippy *ngFor="let response of responses;trackBy:trackByCode" title="{{response.code}} {{response.description}}"
[type]="response.type" [empty]="response.empty" (open)="lazySchema.load()">
<div *ngIf="response.headers" class="response-headers">
<header>
@ -7,8 +7,13 @@
</header>
<div class="header" *ngFor="let header of response.headers">
<div class="header-name"> {{header.name}} </div>
<div class="header-type"> {{header.type}} </div>
<div class="header-type {{header.type}}"> {{header._displayType}} {{header._displayFormat}}
<span class="header-range" *ngIf="header._range"> {{header._range}} </span>
</div>
<div *ngIf="header.default" class="header-default"> Default: {{header.default}} </div>
<div *ngIf="header.enum" class="header-enum">
<span *ngFor="let enumItem of header.enum" class="enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
</div>
<div class="header-description" [innerHtml]="header.description | marked"> </div>
</div>
</div>

View File

@ -38,3 +38,20 @@ header {
.header {
margin-bottom: 10px;
}
.header-range {
position: relative;
top: 1px;
margin-right: 6px;
margin-left: 6px;
border-radius: $border-radius;
background-color: rgba($primary-color, .1);
padding: 0 4px;
color: rgba($primary-color, 0.7);
}
.header-type.array:before {
content: "Array of ";
color: $black;
font-weight: $base-font-weight;
}

View File

@ -7,6 +7,7 @@ import { JsonSchemaLazy } from '../JsonSchema/json-schema-lazy';
import { Zippy } from '../../shared/components/index';
import { statusCodeType } from '../../utils/helpers';
import { OptionsService } from '../../services/index';
import { SchemaHelper } from '../../services/schema-helper.service';
function isNumeric(n) {
return (!isNaN(parseFloat(n)) && isFinite(n));
@ -20,16 +21,15 @@ function isNumeric(n) {
detect: true
})
export class ResponsesList extends BaseComponent {
data: any;
responses: Array<any>;
options: any;
constructor(specMgr:SpecManager, optionsMgr:OptionsService) {
super(specMgr);
this.options = optionsMgr.options;
}
prepareModel() {
this.data = {};
this.data.responses = [];
init() {
this.responses = [];
let responses = this.componentSchema;
if (!responses) return;
@ -53,14 +53,14 @@ export class ResponsesList extends BaseComponent {
resp.headers = Object.keys(resp.headers).map((k) => {
let respInfo = resp.headers[k];
respInfo.name = k;
return respInfo;
return SchemaHelper.preprocess(respInfo, this.pointer, this.pointer);
});
resp.empty = false;
}
resp.extendable = resp.headers || resp.length;
return resp;
});
this.data.responses = responses;
this.responses = responses;
}
trackByCode(idx, el) {

View File

@ -29,7 +29,7 @@ export class ResponsesSamples extends BaseComponent {
super(specMgr);
}
prepareModel() {
init() {
this.data = {};
this.data.responses = [];

View File

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

View File

@ -19,7 +19,7 @@ import { CopyButton } from '../../shared/components/CopyButton/copy-button.direc
})
export class SchemaSample extends BaseComponent {
element: any;
data: any;
sample: any;
enableButtons: boolean = false;
@Input() skipReadOnly:boolean;
@ -33,7 +33,6 @@ export class SchemaSample extends BaseComponent {
init() {
this.bindEvents();
this.data = {};
let base:any = {};
let sample;
@ -78,12 +77,12 @@ export class SchemaSample extends BaseComponent {
}
}
this.cache(sample);
this.data.sample = sample;
this.sample = sample;
this.initButtons();
}
initButtons() {
if (typeof this.data.sample === 'object') {
if (typeof this.sample === 'object') {
this.enableButtons = true;
}
}
@ -98,10 +97,10 @@ export class SchemaSample extends BaseComponent {
fromCache() {
if (this.skipReadOnly && this.componentSchema['x-redoc-ro-sample']) {
this.data.sample = this.componentSchema['x-redoc-ro-sample'];
this.sample = this.componentSchema['x-redoc-ro-sample'];
return true;
} else if (this.componentSchema['x-redoc-rw-sample']) {
this.data.sample = this.componentSchema['x-redoc-rw-sample'];
this.sample = this.componentSchema['x-redoc-rw-sample'];
return true;
}
return false;

View File

@ -7,12 +7,12 @@
</div>
<div #desktop id="resources-nav">
<h5 class="menu-header"> API reference </h5>
<div *ngFor="let cat of data.menu; let idx = index" class="menu-cat">
<div *ngFor="let cat of categories; let idx = index" class="menu-cat">
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [hidden]="cat.empty"
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [hidden]="cat.headless"
[ngClass]="{active: cat.active}"> {{cat.name}}</label>
<ul class="menu-subitems" @itemAnimation="cat.active ? 'expanded' : 'collapsed'">
<li *ngFor="let method of cat.methods; let methIdx = index"
<li *ngFor="let method of cat.methods; trackBy:summary; let methIdx = index"
[ngClass]="{active: method.active}"
(click)="activateAndScroll(idx, methIdx)">
{{method.summary}}

View File

@ -29,7 +29,7 @@ describe('Redoc components', () => {
testOptions = opts;
testOptions.options = {
scrollYOffset: () => 0,
scrollParent: window
$scrollParent: window
};
return specMgr.load('/tests/schemas/extended-petstore.yml');
})));

View File

@ -1,16 +1,18 @@
'use strict';
import { ElementRef, ChangeDetectorRef } from '@angular/core';
import { BrowserDomAdapter } from '@angular/platform-browser/src/browser/browser_adapter';
import { global } from '@angular/core/src/facade/lang';
import { trigger, state, animate, transition, style } from '@angular/core';
import { RedocComponent, BaseComponent, SpecManager } from '../base';
import { ScrollService, Hash, MenuService, OptionsService } from '../../services/index';
import { MenuCategory } from '../../services/schema-helper.service';
@RedocComponent({
selector: 'side-menu',
templateUrl: './side-menu.html',
providers: [ScrollService, MenuService, Hash],
styleUrls: ['./side-menu.css'],
detect: true,
onPushOnly: false,
@ -27,14 +29,16 @@ import { ScrollService, Hash, MenuService, OptionsService } from '../../services
],
})
export class SideMenu extends BaseComponent {
$element: any;
$mobileNav: any;
$resourcesNav: any;
$scrollParent: any;
activeCatCaption: string;
activeItemCaption: string;
options: any;
data: any;
categories: Array<MenuCategory>;
private options: any;
private $element: any;
private $mobileNav: any;
private $resourcesNav: any;
private $scrollParent: any;
constructor(specMgr:SpecManager, elementRef:ElementRef, private dom:BrowserDomAdapter,
private scrollService:ScrollService, private menuService:MenuService, private hash:Hash,
optionsService:OptionsService, private detectorRef:ChangeDetectorRef) {
@ -66,6 +70,8 @@ export class SideMenu extends BaseComponent {
}
init() {
this.categories = this.menuService.categories;
this.$mobileNav = this.dom.querySelector(this.$element, '.mobile-nav');
this.$resourcesNav = this.dom.querySelector(this.$element, '#resources-nav');
@ -76,11 +82,6 @@ export class SideMenu extends BaseComponent {
};
}
prepareModel() {
this.data = {};
this.data.menu = this.menuService.categories;
}
mobileMode() {
return this.$mobileNav.clientHeight > 0;
}
@ -88,7 +89,7 @@ export class SideMenu extends BaseComponent {
toggleMobileNav() {
let dom = this.dom;
let $overflowParent = (this.options.$scrollParent === global) ? dom.defaultDoc().body
: this.$scrollParent.$scrollParent;
: this.$scrollParent;
if (dom.hasStyle(this.$resourcesNav, 'height')) {
dom.removeStyle(this.$resourcesNav, 'height');
dom.removeStyle($overflowParent, 'overflow-y');

View File

@ -0,0 +1,4 @@
<div *ngIf="shown">
<a class="warnings-close" (click)="close()">×</a>
<div class="message" *ngFor="let message of warnings">{{message}}</div>
</div>

View File

@ -0,0 +1,33 @@
:host {
width: 60%;
display: block;
}
.message {
padding: 5px 40px;
background-color: #fcf8e3;
color: #8a6d3b;
&:before {
content: "Warning: ";
font-weight: bold;
}
}
.warnings-close {
font-size: 150%;
color: black;
opacity: 0.4;
float: right;
margin: 5px 20px 0 0;
font-weight: bold;
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
p {
display: inline;
}

View File

@ -0,0 +1,33 @@
'use strict';
import { SpecManager, RedocComponent, BaseComponent } from '../base';
import { WarningsService, OptionsService } from '../../services/index';
@RedocComponent({
selector: 'warnings',
styleUrls: ['./warnings.css'],
templateUrl: './warnings.html',
detect: true,
onPushOnly: false
})
export class Warnings extends BaseComponent {
warnings: Array<string> = [];
shown: boolean = false;
suppressWarnings: boolean;
constructor(specMgr:SpecManager, optionsMgr: OptionsService) {
super(specMgr);
this.suppressWarnings = optionsMgr.options.suppressWarnings;
}
init() {
this.shown = !this.suppressWarnings && !!this.warnings.length;
WarningsService.warnings.subscribe((warns) => {
this.warnings = warns;
this.shown = !this.suppressWarnings && !!warns.length;
});
}
close() {
this.shown = false;
}
}

View File

@ -29,14 +29,11 @@ describe('Redoc components', () => {
component.componentSchema.should.be.deepEqual(specMgr._schema.tags);
});
it('should call prepareModel and init virtual methods after init', () => {
spyOn(component, 'prepareModel');
it('should call init virtual methods after init', () => {
spyOn(component, 'init');
component.ngOnInit();
component.prepareModel.calls.count().should.be.equal(1);
component.init.calls.count().should.be.equal(1);
component.prepareModel.and.callThrough();
component.init.and.callThrough();
});
});

View File

@ -2,7 +2,7 @@
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
import { CORE_DIRECTIVES, JsonPipe, AsyncPipe } from '@angular/common';
import { SpecManager } from '../utils/SpecManager';
import { MarkedPipe, JsonPointerEscapePipe } from '../utils/pipes';
import { MarkedPipe, JsonPointerEscapePipe, SafePipe } from '../utils/pipes';
export { SpecManager };
@ -50,7 +50,7 @@ function snapshot(obj) {
export function RedocComponent(options) {
let inputs = safeConcat(options.inputs, commonInputs);
let directives = safeConcat(options.directives, CORE_DIRECTIVES);
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe, AsyncPipe]);
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe, AsyncPipe, SafePipe]);
if (options.onPushOnly === undefined) options.onPushOnly = true;
return function decorator(target) {
@ -79,7 +79,7 @@ export function RedocComponent(options) {
*/
export class BaseComponent implements OnInit, OnDestroy {
componentSchema: any = null;
pointer: String;
pointer: string;
dereferencedCache = {};
constructor(public specMgr: SpecManager) {
@ -90,7 +90,6 @@ export class BaseComponent implements OnInit, OnDestroy {
*/
ngOnInit() {
this.componentSchema = this.specMgr.byPointer(this.pointer || '');
this.prepareModel();
this.init();
}
@ -99,15 +98,7 @@ export class BaseComponent implements OnInit, OnDestroy {
}
/**
* Used to prepare model based on component schema
* @abstract
*/
prepareModel():any {
// emtpy
}
/**
* Used to initialize component. Run after prepareModel
* Used to initialize component
* @abstract
*/
init() {

View File

@ -7,3 +7,4 @@ export * from './scroll.service';
export * from './hash.service';
export * from './schema-normalizer.service';
export * from './schema-helper.service';
export * from './warnings.service';

View File

@ -3,7 +3,7 @@ import { Injectable, EventEmitter } from '@angular/core';
import { ScrollService, INVIEW_POSITION } from './scroll.service';
import { Hash } from './hash.service';
import { SpecManager } from '../utils/SpecManager';
import { SchemaHelper } from './schema-helper.service';
import { SchemaHelper, MenuCategory } from './schema-helper.service';
const CHANGE = {
NEXT : 1,
@ -14,7 +14,7 @@ const CHANGE = {
@Injectable()
export class MenuService {
changed: EventEmitter<any> = new EventEmitter();
categories: any;
categories: Array<MenuCategory>;
activeCatIdx: number = 0;
activeMethodIdx: number = -1;
@ -55,11 +55,11 @@ export class MenuService {
getCurrentMethodEl() {
return this.getMethodElByPtr(this.activeMethodPtr,
this.categories[this.activeCatIdx].name);
this.categories[this.activeCatIdx].id);
}
getMethodElByPtr(ptr, tag) {
let selector = ptr ? `[pointer="${ptr}"][tag="${tag}"]` : `[tag="${tag}"]`;
getMethodElByPtr(ptr, section) {
let selector = ptr ? `[pointer="${ptr}"][section="${section}"]` : `[section="${section}"]`;
return document.querySelector(selector);
}
@ -95,6 +95,7 @@ export class MenuService {
_calcActiveIndexes(offset) {
let menu = this.categories;
let catCount = menu.length;
if (!catCount) return [0, -1];
let catLength = menu[this.activeCatIdx].methods.length;
let resMethodIdx = this.activeMethodIdx + offset;
@ -140,10 +141,11 @@ export class MenuService {
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
if (namespace === 'operation') {
$el = this.getMethodElByOperId(ptr);
} else if (namespace === 'tag') {
let tag = ptr.split('/')[0];
ptr = ptr.substr(tag.length);
$el = this.getMethodElByPtr(ptr, tag);
} else {
let sectionId = ptr.split('/')[0];
ptr = ptr.substr(sectionId.length) || null;
sectionId = namespace + (sectionId ? '/' + sectionId : '');
$el = this.getMethodElByPtr(ptr, sectionId);
}
if ($el) this.scrollService.scrollTo($el);

View File

@ -9,7 +9,7 @@ const defaults = {
debugMode: false//global && global.redocDebugMode
};
const OPTION_NAMES = new Set(['scrollYOffset', 'disableLazySchemas', 'specUrl']);
const OPTION_NAMES = new Set(['scrollYOffset', 'disableLazySchemas', 'specUrl', 'suppressWarnings']);
@Injectable()
export class OptionsService {
@ -70,5 +70,6 @@ export class OptionsService {
}
if (isString(this._options.disableLazySchemas)) this._options.disableLazySchemas = true;
if (isString(this._options.suppressWarnings)) this._options.suppressWarnings = true;
}
}

View File

@ -59,9 +59,13 @@ describe('Spec Helper', () => {
info.methods[0].summary.should.be.equal('test post');
});
it('should map x-traitTag to empty section', () => {
let info = menuTree[0];
info.empty.should.be.true();
});
it('should map x-traitTag to empty methods list', () => {
let info = menuTree[0];
info['x-traitTag'].should.be.true();
info.methods.should.be.empty();
});
@ -81,4 +85,19 @@ describe('Spec Helper', () => {
}
});
});
describe('injectors', () => {
it('should autodetect type if not-specified', () => {
spyOn(console, 'warn').and.stub();
let schema = {
type: undefined,
properties: {}
};
SchemaHelper.runInjectors(schema, schema, '#/');
schema.type.should.be.equal('object');
expect(console.warn).toHaveBeenCalled();
(<jasmine.Spy>console.warn).and.callThrough();
});
});
});

View File

@ -1,14 +1,44 @@
'use strict';
import { JsonPointer } from '../utils/JsonPointer';
import { SpecManager } from '../utils/SpecManager';
import {methods as swaggerMethods} from '../utils/swagger-defs';
import {methods as swaggerMethods, keywordTypes} from '../utils/swagger-defs';
import { WarningsService } from './warnings.service';
import slugify from 'slugify';
interface PropertyPreprocessOptions {
childFor: string;
skipReadOnly?: boolean;
}
export interface MenuMethod {
active: boolean;
summary: string;
tag: string;
}
export interface MenuCategory {
name: string;
id: string;
active?: boolean;
methods?: Array<MenuMethod>;
description?: string;
empty?: string;
virtual?: boolean;
}
const injectors = {
notype: {
check: (propertySchema) => !propertySchema.type,
inject: (injectTo, propertySchema, pointer) => {
injectTo.type = SchemaHelper.detectType(propertySchema);
propertySchema.type = injectTo.type;
if (injectTo.type) {
let message = `No "type" specified at "${pointer}". Automatically detected: "${injectTo.type}"`;
WarningsService.warn(message);
}
}
},
general: {
check: () => true,
inject: (injectTo, propertySchema, pointer) => {
@ -225,17 +255,36 @@ export class SchemaHelper {
(method.description && method.description.substring(0, 50)) || '<no description>';
}
static buildMenuTree(schema) {
static detectType(schema) {
let keywords = Object.keys(keywordTypes);
for (var i=0; i < keywords.length; i++) {
let keyword = keywords[i];
let type = keywordTypes[keyword];
if (schema[keyword]) {
return type;
}
}
}
static buildMenuTree(schema):Array<MenuCategory> {
let tag2MethodMapping = {};
let definedTags = schema.tags || [];
// add tags into map to preserve order
for (let tag of definedTags) {
tag2MethodMapping[tag.name] = {
'description': tag.description,
'name': tag.name,
'x-traitTag': tag['x-traitTag'],
'methods': []
for (let header of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-headers'] || []))) {
let id = 'section/' + slugify(header);
tag2MethodMapping[id] = {
name: header, id: id, virtual: true, methods: []
};
}
for (let tag of schema.tags || []) {
let id = 'tag/' + slugify(tag.name);
tag2MethodMapping[id] = {
name: tag.name,
id: id,
description: tag.description,
headless: tag.name === '',
empty: !!tag['x-traitTag'],
methods: [],
};
}
@ -252,15 +301,17 @@ export class SchemaHelper {
let methodPointer = JsonPointer.compile(['paths', path, method]);
let methodSummary = SchemaHelper.methodSummary(methodInfo);
for (let tag of tags) {
let tagDetails = tag2MethodMapping[tag];
if (!tag2MethodMapping[tag]) {
let id = 'tag/' + slugify(tag);
let tagDetails = tag2MethodMapping[id];
if (!tagDetails) {
tagDetails = {
name: tag,
empty: tag === ''
id: id,
headless: tag === ''
};
tag2MethodMapping[tag] = tagDetails;
tag2MethodMapping[id] = tagDetails;
}
if (tagDetails['x-traitTag']) continue;
if (tagDetails.empty) continue;
if (!tagDetails.methods) tagDetails.methods = [];
tagDetails.methods.push({
pointer: methodPointer,

View File

@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
import { SpecManager } from '../utils/SpecManager';
import { JsonPointer } from '../utils/JsonPointer';
import { defaults } from '../utils/helpers';
import { WarningsService } from './warnings.service';
interface Reference {
$ref: string;
@ -29,6 +30,7 @@ export class SchemaNormalizer {
let resolved = this._dereferencer.dereference(subSchema, ptr);
if (resolved.allOf) {
resolved._pointer = resolved._pointer || ptr;
resolved = Object.assign({}, resolved);
AllOfMerger.merge(resolved, resolved.allOf, {omitParent: opts.omitParent});
}
return resolved;
@ -127,20 +129,22 @@ class AllOfMerger {
private static checkCanMerge(subSchema, into) {
// TODO: add support for merge array schemas
if (typeof subSchema !== 'object') {
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found
${subSchema}`;
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found ` +
`${subSchema} at "#${into._pointer}"`;
throw new Error(errMessage);
}
if (into.type && subSchema.type && into.type !== subSchema.type) {
let errMessage = `allOf merging error: schemas with different types can't be merged`;
let errMessage = `allOf merging error: schemas with different types can't be merged: ` +
`"${into.type}" and "${subSchema.type}" at "#${into._pointer}"`;
throw new Error(errMessage);
}
if (into.type === 'array') {
console.warn('allOf: subschemas with type array are not supported yet');
WarningsService.warn('allOf: subschemas with type "array" are not supported yet');
}
// TODO: add check if can be merged correctly (no different properties with the same name)
// TODO: merge properties
}
}
@ -191,8 +195,8 @@ class SchemaDereferencer {
let keysCount = Object.keys(schema).length;
if ( keysCount > 2 || (keysCount === 2 && !schema.description) ) {
console.warn(`other properties defined at the same level as $ref at '${pointer}'.
They are IGNORRED according to JsonSchema spec`);
WarningsService.warn(`Other properties are defined at the same level as $ref at "#${pointer}". ` +
'They are IGNORRED according to the JsonSchema spec');
resolved.description = resolved.description || schema.description;
}

View File

@ -0,0 +1,23 @@
'use strict';
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable()
export class WarningsService {
private static _warnings: Array<string> = [];
private static _warningsObs = new Subject<Array<string>>();
static get warnings() {
return WarningsService._warningsObs;
}
static hasWarnings() {
return !!WarningsService._warnings.length;
}
static warn(message:string) {
WarningsService._warnings.push(message);
WarningsService._warningsObs.next(WarningsService._warnings);
console.warn(message);
}
}

View File

@ -18,10 +18,6 @@
vertical-align: middle;
}
.sharable-header {
color: $secondary-color;
}
.sharable-header:hover .share-link:before, .share-link:hover:before {
visibility: visible;
}

View File

@ -7,8 +7,6 @@ $green : #00aa13;
$yellow : #f1c400;
$red : #e53935;
// Font
// ---------------------------
$em-size : 14px;
// Font weights

View File

@ -2,11 +2,15 @@
import JsonSchemaRefParser from 'json-schema-ref-parser';
import JsonPointer from './JsonPointer';
import { renderMd, safePush } from './helpers';
import slugify from 'slugify';
import { parse as urlParse } from 'url';
export class SpecManager {
public _schema:any = {};
public _schema: any = {};
public apiUrl: string;
private _instance:any;
private _instance: any;
private _url: string;
static instance() {
return new SpecManager();
@ -26,6 +30,7 @@ export class SpecManager {
JsonSchemaRefParser.bundle(url, {http: {withCredentials: false}})
.then(schema => {
this._url = url;
this._schema = schema;
resolve(this._schema);
this.init();
@ -37,11 +42,36 @@ export class SpecManager {
/* calculate common used values */
init() {
if (!this._schema || !this._schema.schemes) return;
this.apiUrl = this._schema.schemes[0] + '://' + this._schema.host + this._schema.basePath;
let protocol;
if (!this._schema.schemes || !this._schema.schemes.length) {
protocol = this._url ? urlParse(this._url).protocol : 'http';
} else {
protocol = this._schema.schemes[0];
if (protocol === 'http' && this._schema.schemes.indexOf('https') >= 0) {
protocol = 'https';
}
}
this.apiUrl = protocol + '://' + this._schema.host + this._schema.basePath;
if (this.apiUrl.endsWith('/')) {
this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length - 1);
}
this.preprocess();
}
preprocess() {
this._schema.info['x-redoc-html-description'] = renderMd( this._schema.info.description, {
open: (tokens, idx) => {
let content = tokens[idx + 1].content;
safePush(this._schema.info, 'x-redoc-markdown-headers', content);
content = slugify(content);
return `<h${tokens[idx].hLevel} section="section/${content}">` +
`<a class="share-link" href="#section/${content}"></a>`;
},
close: (tokens, idx) => {
return `</h${tokens[idx].hLevel}>`;
}
});
}
get schema() {

View File

@ -1,4 +1,59 @@
'use strict';
import 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;
}
export function renderMd(rawText:string, headersHandler?:HeadersHandler) {
let _origRule;
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) => {
if (tokens[idx].hLevel !== 1 ) {
return _origRule.close(tokens, idx);
} else {
return headersHandler.close(tokens, idx);
}
};
}
let res = md.render(rawText);
if (headersHandler) {
md.renderer.rules.heading_open = _origRule.open;
md.renderer.rules.heading_close = _origRule.close;
}
return res;
}
export function statusCodeType(statusCode) {
if (statusCode < 100 || statusCode > 599) {
@ -29,3 +84,8 @@ export function defaults(target, src) {
}
return target;
}
export function safePush(obj, prop, val) {
if (!obj[prop]) obj[prop] = [];
obj[prop].push(val);
}

View File

@ -5,24 +5,11 @@ import { DomSanitizationService } from '@angular/platform-browser';
import { isString, stringify, isBlank } from '@angular/core/src/facade/lang';
import { BaseException } from '@angular/core/src/facade/exceptions';
import JsonPointer from './JsonPointer';
import { renderMd } from './helpers';
declare var Prism: any;
import Remarkable from 'remarkable';
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);
}
});
class InvalidPipeArgumentException extends BaseException {
constructor(type, value) {
@ -73,11 +60,24 @@ export class MarkedPipe implements PipeTransform {
}
return this.sanitizer.bypassSecurityTrustHtml(
`<span class="redoc-markdown-block">${md.render(value)}</span>`
`<span class="redoc-markdown-block">${renderMd(value)}</span>`
);
}
}
@Pipe({ name: 'safe' })
export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizationService) {}
transform(value) {
if (isBlank(value)) return value;
if (!isString(value)) {
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
}
return this.sanitizer.bypassSecurityTrustHtml(value);
}
}
const langMap = {
'c++': 'cpp',
'c#': 'csharp',

View File

@ -1,3 +1,26 @@
'use strict';
export var methods = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch']);
export const methods = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch']);
export const keywordTypes = {
multipleOf: 'number',
maximum: 'number',
exclusiveMaximum: 'number',
minimum: 'number',
exclusiveMinimum: 'number',
maxLength: 'string',
minLength: 'string',
pattern: 'string',
items: 'array',
maxItems: 'array',
minItems: 'array',
uniqueItems: 'array',
maxProperties: 'object',
minProperties: 'object',
required: 'object',
additionalProperties: 'object',
properties: 'object'
};

View File

@ -1,7 +1,7 @@
{
"name": "redoc",
"description": "Swagger-generated API Reference Documentation",
"version": "0.16.1",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "git://github.com/Rebilly/ReDoc"
@ -48,7 +48,9 @@
"remarkable": "npm:remarkable@^1.6.2",
"rxjs": "npm:rxjs@5.0.0-beta.6",
"scrollparent": "npm:scrollparent@^0.1.0",
"slugify": "npm:slugify@^0.1.1",
"stream-http": "npm:stream-http@^2.3.0",
"url": "github:jspm/nodelibs-url@^0.1.0",
"zone.js": "npm:zone.js@0.6.12"
},
"devDependencies": {
@ -132,6 +134,7 @@
"shelljs": "^0.7.0",
"should": "^9.0.2",
"sinon": "^1.17.2",
"slugify": "^0.1.1",
"systemjs-builder": "^0.15.16",
"tslint": "^3.13.0",
"tslint-stylish": "^2.1.0-beta",

View File

@ -38,8 +38,10 @@ System.config({
"remarkable": "npm:remarkable@1.6.2",
"rxjs": "npm:rxjs@5.0.0-beta.6",
"scrollparent": "npm:scrollparent@0.1.0",
"slugify": "npm:slugify@0.1.1",
"stream-http": "npm:stream-http@2.3.0",
"systemjs/plugin-json": "github:systemjs/plugin-json@0.1.2",
"url": "github:jspm/nodelibs-url@0.1.0",
"zone.js": "npm:zone.js@0.6.12",
"github:jspm/nodelibs-assert@0.1.0": {
"assert": "npm:assert@1.4.1"

View File

@ -43,7 +43,7 @@ describe('Scroll sync', () => {
});
it('should update active menu entries on page scroll forwards', () => {
scrollToEl('[tag="store"]').then(() => {
scrollToEl('[section="tag/store"]').then(() => {
expect($('.menu-cat-header.active').getInnerHtml()).toContain('store');
expect($('.selected-tag').getInnerHtml()).toContain('store');
});

View File

@ -16,5 +16,16 @@
"host": "petstore.swagger.io",
"basePath": "/v2/",
"schemes": ["http"],
"paths": {}
"paths": {
"/pet": {
"post": {
"tags": ["tag1"],
"summary": "tag1"
},
"put": {
"tags": ["tag1", "traitTag"],
"summary": "two tags",
}
}
}
}

View File

@ -53,6 +53,12 @@ describe('Utils', () => {
specMgr.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
});
it('should correctly init api url if both http and https', ()=> {
specMgr._schema.schemes.push('https');
specMgr.init();
specMgr.apiUrl.should.be.equal('https://petstore.swagger.io/v2');
});
describe('byPointer method', () => {
it('should return correct schema part', ()=> {
let part = specMgr.byPointer('/tags/3');

2590
typings/globals/node/index.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
{
"resolution": "main",
"tree": {
"src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/91d45c49a3b5cd6a0abbf5f319c1406fd4f2b1e7/node/node.d.ts",
"raw": "registry:dt/node#6.0.0+20160720070758",
"typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/91d45c49a3b5cd6a0abbf5f319c1406fd4f2b1e7/node/node.d.ts"
}
}

1
typings/index.d.ts vendored
View File

@ -1,3 +1,4 @@
/// <reference path="globals/jasmine/index.d.ts" />
/// <reference path="globals/json-pointer/index.d.ts" />
/// <reference path="globals/node/index.d.ts" />
/// <reference path="globals/should/index.d.ts" />

4
typings/slugify.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module 'slugify' {
var x: any;
export default x;
}