mirror of
https://github.com/Redocly/redoc.git
synced 2024-11-28 03:23:44 +03:00
Merge commit '5f044b900ff4fa3632144b13c0ea88545dbdf5c5' into releases
This commit is contained in:
commit
639acb35b6
65
README.md
65
README.md
|
@ -1,17 +1,30 @@
|
||||||
# ReDoc
|
# 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)
|
[![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)
|
[![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)
|
[![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
|
## Deployment
|
||||||
|
|
||||||
#### tl;dr
|
### TL;DR
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -21,28 +34,24 @@ Swagger-generated API Reference Documentation
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
ReDoc uses font options from the parent element
|
ReDoc doesn't change outer page styles
|
||||||
So override default browser styles
|
|
||||||
-->
|
-->
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: Verdana, Geneva, sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'>
|
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
|
||||||
</redoc>
|
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
|
||||||
<script src="bower_components/redoc/dist/redoc.min.js"> </script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
```
|
```
|
||||||
|
That's all folks!
|
||||||
|
|
||||||
#### 1. Install redoc
|
### 1. Install ReDoc (skip this step for CDN)
|
||||||
Install using [bower](bower.io):
|
Install using [bower](bower.io):
|
||||||
|
|
||||||
bower install redoc
|
bower install redoc
|
||||||
|
@ -51,12 +60,13 @@ or using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
|
||||||
|
|
||||||
npm install redoc --save
|
npm install redoc --save
|
||||||
|
|
||||||
Alternatively, you can **reference redoc directly** from CDN:
|
### 2. Reference redoc script in HTML
|
||||||
- latest release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
|
For **CDN**:
|
||||||
- particular release, e.g. v0.14.0: https://rebilly.github.io/ReDoc/releases/v0.14.0/redoc.min.js
|
```html
|
||||||
|
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
|
||||||
|
```
|
||||||
|
|
||||||
#### 2. Reference redoc script in HTML
|
For bower:
|
||||||
Then reference [`redoc.min.js`](https://raw.githubusercontent.com/Rebilly/ReDoc/releases/dist/redoc.min.js) in your HTML page:
|
|
||||||
```html
|
```html
|
||||||
<script src="bower_components/redoc/dist/redoc.min.js"> </script>
|
<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>
|
<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
|
```html
|
||||||
<redoc spec-url="<url to your spec>"></redoc>
|
<redoc spec-url="url/to/your/spec"></redoc>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4. Enjoy :smile:
|
### 4. Enjoy :smile:
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
#### 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
|
||||||
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
|
* [`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
|
* [`x-code-samples`](docs/redoc-vendor-extensions.md#x-code-samples) - specify operation code samples
|
||||||
|
|
||||||
#### Options
|
### Options
|
||||||
* `spec-url` - relative or absolute url to your spec file
|
* `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` - 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:
|
`scroll-y-offset` can be specified in various ways:
|
||||||
* **number**: A fixed number of pixels to be used as offset
|
* **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.
|
* **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).
|
* **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
|
## Advanced usage
|
||||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
||||||
|
|
|
@ -4,19 +4,20 @@
|
||||||
<title>ReDoc</title>
|
<title>ReDoc</title>
|
||||||
<link rel="stylesheet" href="main.css">
|
<link rel="stylesheet" href="main.css">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<script src="https://cdn.vaadin.com/vaadin-core-elements/latest/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||||
<style>
|
<link rel="import" href="https://cdn.vaadin.com/vaadin-core-elements/master/vaadin-combo-box/vaadin-combo-box-light.html">
|
||||||
@import url(//fonts.googleapis.com/css?family=Roboto:300,400,700);
|
|
||||||
@import url(//fonts.googleapis.com/css?family=Montserrat:400,700);
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav>
|
||||||
<header> ReDoc </header>
|
<header> ReDoc </header>
|
||||||
<form id="schema-url-form">
|
<template is="dom-bind" id="specs">
|
||||||
<input id="schema-url-input" value='http://rebilly.github.io/SwaggerTemplateRepo/swagger.yaml'>
|
<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>
|
<button type="submit"> Explore </button>
|
||||||
</form>
|
</form>
|
||||||
|
</template>
|
||||||
<iframe src="https://ghbtns.com/github-btn.html?user=Rebilly&repo=ReDoc&type=star&count=true&size=large"
|
<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>
|
frameborder="0" scrolling="0" width="130px" height="30px"></iframe>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -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 {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
|
@ -26,6 +23,7 @@ body {
|
||||||
-webkit-text-size-adjust: 100%;
|
-webkit-text-size-adjust: 100%;
|
||||||
-ms-text-size-adjust: 100%;
|
-ms-text-size-adjust: 100%;
|
||||||
text-size-adjust: 100%;
|
text-size-adjust: 100%;
|
||||||
|
font-family: Monserrat, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav input, nav button {
|
nav input, nav button {
|
||||||
|
@ -49,11 +47,11 @@ nav input {
|
||||||
width: 50%;
|
width: 50%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
color: #555;
|
color: #555;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
|
||||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||||
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;
|
-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;
|
-ms-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nav button:hover {
|
nav button:hover {
|
||||||
|
|
39
demo/main.js
39
demo/main.js
|
@ -2,19 +2,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var schemaUrlForm = document.getElementById('schema-url-form');
|
var schemaUrlForm = document.getElementById('schema-url-form');
|
||||||
var schemaUrlInput = document.getElementById('schema-url-input');
|
var schemaUrlInput;
|
||||||
schemaUrlForm.addEventListener('submit', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value)
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
|
|
||||||
var url = window.location.search.match(/url=([^&]+)/);
|
var url = window.location.search.match(/url=([^&]+)/);
|
||||||
if (url && url.length > 1) {
|
if (url && url.length > 1) {
|
||||||
url = decodeURIComponent(url[1]);
|
url = decodeURIComponent(url[1]);
|
||||||
document.getElementsByTagName('redoc')[0].setAttribute('spec-url', url);
|
document.getElementsByTagName('redoc')[0].setAttribute('spec-url', url);
|
||||||
schemaUrlInput.value = url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateQueryStringParameter(uri, key, value) {
|
function updateQueryStringParameter(uri, key, value) {
|
||||||
|
@ -31,5 +24,35 @@
|
||||||
return uri + separator + key + "=" + value + hash;
|
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;
|
//window.redocDebugMode = true;
|
||||||
})();
|
})();
|
||||||
|
|
BIN
demo/redoc-demo.png
Normal file
BIN
demo/redoc-demo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 198 KiB |
|
@ -1,10 +1,32 @@
|
||||||
swagger: '2.0'
|
swagger: '2.0'
|
||||||
schemes:
|
schemes:
|
||||||
- http
|
- http
|
||||||
|
- https
|
||||||
host: petstore.swagger.io
|
host: petstore.swagger.io
|
||||||
basePath: /v2
|
basePath: /v2
|
||||||
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.'
|
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
|
version: 1.0.0
|
||||||
title: Swagger Petstore
|
title: Swagger Petstore
|
||||||
termsOfService: 'http://swagger.io/terms/'
|
termsOfService: 'http://swagger.io/terms/'
|
||||||
|
@ -16,47 +38,15 @@ info:
|
||||||
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'
|
||||||
externalDocs:
|
externalDocs:
|
||||||
description: Find out more about Swagger
|
description: Find out how to create Github repo for your OpenAPI spec.
|
||||||
url: 'http://swagger.io'
|
url: 'https://github.com/Rebilly/generator-openapi-repo'
|
||||||
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:\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
|
- name: pet
|
||||||
description: Everything about your Pets
|
description: Everything about your Pets
|
||||||
externalDocs:
|
|
||||||
description: Find out more
|
|
||||||
url: 'http://swagger.io'
|
|
||||||
- name: store
|
- name: store
|
||||||
description: Access to Petstore orders
|
description: Access to Petstore orders
|
||||||
- name: user
|
- name: user
|
||||||
description: Operations about user
|
description: Operations about user
|
||||||
externalDocs:
|
|
||||||
description: Find out more about our store
|
|
||||||
url: 'http://swagger.io'
|
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
petstore_auth:
|
petstore_auth:
|
||||||
type: oauth2
|
type: oauth2
|
||||||
|
@ -75,7 +65,7 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
summary: Add a new pet to the store
|
summary: Add a new pet to the store
|
||||||
description: ''
|
description: Add new pet to the store inventory.
|
||||||
operationId: addPet
|
operationId: addPet
|
||||||
consumes:
|
consumes:
|
||||||
- application/json
|
- application/json
|
||||||
|
@ -154,7 +144,6 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
- JSONP
|
|
||||||
summary: Find pet by ID
|
summary: Find pet by ID
|
||||||
description: Returns a single pet
|
description: Returns a single pet
|
||||||
operationId: getPetById
|
operationId: getPetById
|
||||||
|
@ -242,40 +231,6 @@ paths:
|
||||||
- 'write:pets'
|
- 'write:pets'
|
||||||
- 'read:pets'
|
- 'read:pets'
|
||||||
'/pet/{petId}/uploadImage':
|
'/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:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
|
@ -316,8 +271,6 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
- Pagination
|
|
||||||
- JSONP
|
|
||||||
summary: Finds Pets by status
|
summary: Finds Pets by status
|
||||||
description: Multiple status values can be provided with comma seperated strings
|
description: Multiple status values can be provided with comma seperated strings
|
||||||
operationId: findPetsByStatus
|
operationId: findPetsByStatus
|
||||||
|
@ -355,8 +308,6 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- pet
|
- pet
|
||||||
- Pagination
|
|
||||||
- JSONP
|
|
||||||
summary: Finds Pets by tags
|
summary: Finds Pets by tags
|
||||||
description: 'Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.'
|
description: 'Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.'
|
||||||
operationId: findPetsByTags
|
operationId: findPetsByTags
|
||||||
|
@ -389,7 +340,6 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- store
|
- store
|
||||||
- JSONP
|
|
||||||
summary: Returns pet inventories by status
|
summary: Returns pet inventories by status
|
||||||
description: Returns a map of status codes to quantities
|
description: Returns a map of status codes to quantities
|
||||||
operationId: getInventory
|
operationId: getInventory
|
||||||
|
@ -434,7 +384,6 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- store
|
- store
|
||||||
- JSONP
|
|
||||||
summary: Find purchase order by ID
|
summary: Find purchase order by ID
|
||||||
description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions'
|
description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions'
|
||||||
operationId: getOrderById
|
operationId: getOrderById
|
||||||
|
@ -504,7 +453,6 @@ paths:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- user
|
- user
|
||||||
- JSONP
|
|
||||||
summary: Get user by user name
|
summary: Get user by user name
|
||||||
description: ''
|
description: ''
|
||||||
operationId: getUserByName
|
operationId: getUserByName
|
||||||
|
@ -642,6 +590,8 @@ paths:
|
||||||
description: successful operation
|
description: successful operation
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
examples:
|
||||||
|
application/json: OK
|
||||||
headers:
|
headers:
|
||||||
X-Rate-Limit:
|
X-Rate-Limit:
|
||||||
type: integer
|
type: integer
|
||||||
|
@ -699,10 +649,13 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
description: Category ID
|
||||||
format: int64
|
allOf:
|
||||||
|
- $ref: '#/definitions/Id'
|
||||||
name:
|
name:
|
||||||
|
description: Category name
|
||||||
type: string
|
type: string
|
||||||
|
minLength: 1
|
||||||
xml:
|
xml:
|
||||||
name: Category
|
name: Category
|
||||||
Dog:
|
Dog:
|
||||||
|
@ -714,24 +667,44 @@ definitions:
|
||||||
packSize:
|
packSize:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
description: the size of the pack the dog is from
|
description: The size of the pack the dog is from
|
||||||
default: 0
|
default: 1
|
||||||
minimum: 0
|
minimum: 1
|
||||||
required:
|
required:
|
||||||
- packSize
|
- 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:
|
Order:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
description: Order ID
|
||||||
format: int64
|
allOf:
|
||||||
|
- $ref: '#/definitions/Id'
|
||||||
petId:
|
petId:
|
||||||
type: integer
|
description: Pet ID
|
||||||
format: int64
|
allOf:
|
||||||
|
- $ref: '#/definitions/Id'
|
||||||
quantity:
|
quantity:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
|
minimum: 1
|
||||||
|
default: 1
|
||||||
shipDate:
|
shipDate:
|
||||||
|
description: Estimated ship date
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
status:
|
status:
|
||||||
|
@ -742,6 +715,7 @@ definitions:
|
||||||
- approved
|
- approved
|
||||||
- delivered
|
- delivered
|
||||||
complete:
|
complete:
|
||||||
|
description: Indicates whenever order was completed or not
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
xml:
|
xml:
|
||||||
|
@ -754,23 +728,31 @@ definitions:
|
||||||
discriminator: petType
|
discriminator: petType
|
||||||
properties:
|
properties:
|
||||||
petType:
|
petType:
|
||||||
|
description: Type of a pet
|
||||||
type: string
|
type: string
|
||||||
id:
|
id:
|
||||||
type: integer
|
description: Pet ID
|
||||||
format: int64
|
allOf:
|
||||||
|
- $ref: '#/definitions/Id'
|
||||||
category:
|
category:
|
||||||
$ref: '#/definitions/Category'
|
description: Categories this pet belongs to
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/Category'
|
||||||
name:
|
name:
|
||||||
|
description: The name given to a pet
|
||||||
type: string
|
type: string
|
||||||
example: doggie
|
example: Guru
|
||||||
photoUrls:
|
photoUrls:
|
||||||
|
description: The list of URL to a cute photos featuring pet
|
||||||
type: array
|
type: array
|
||||||
xml:
|
xml:
|
||||||
name: photoUrl
|
name: photoUrl
|
||||||
wrapped: true
|
wrapped: true
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
|
format: url
|
||||||
tags:
|
tags:
|
||||||
|
description: Tags attached to the pet
|
||||||
type: array
|
type: array
|
||||||
xml:
|
xml:
|
||||||
name: tag
|
name: tag
|
||||||
|
@ -779,7 +761,7 @@ definitions:
|
||||||
$ref: '#/definitions/Tag'
|
$ref: '#/definitions/Tag'
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
description: pet status in the store
|
description: Pet status in the store
|
||||||
enum:
|
enum:
|
||||||
- available
|
- available
|
||||||
- pending
|
- pending
|
||||||
|
@ -790,33 +772,56 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
description: Tag ID
|
||||||
format: int64
|
allOf:
|
||||||
|
- $ref: '#/definitions/Id'
|
||||||
name:
|
name:
|
||||||
|
description: Tag name
|
||||||
type: string
|
type: string
|
||||||
|
minLength: 1
|
||||||
xml:
|
xml:
|
||||||
name: Tag
|
name: Tag
|
||||||
User:
|
User:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
description: User ID
|
||||||
format: int64
|
$ref: '#/definitions/Id'
|
||||||
username:
|
username:
|
||||||
|
description: User supplied username
|
||||||
type: string
|
type: string
|
||||||
|
minLength: 4
|
||||||
|
example: John78
|
||||||
firstName:
|
firstName:
|
||||||
|
description: User first name
|
||||||
type: string
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
example: John
|
||||||
lastName:
|
lastName:
|
||||||
|
description: User last name
|
||||||
type: string
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
example: Smith
|
||||||
email:
|
email:
|
||||||
|
description: User email address
|
||||||
type: string
|
type: string
|
||||||
|
format: email
|
||||||
|
example: john.smith@example.com
|
||||||
password:
|
password:
|
||||||
type: string
|
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:
|
phone:
|
||||||
|
description: User phone number in international format
|
||||||
type: string
|
type: string
|
||||||
|
pattern: "^\\+(?:[0-9]-?){6,14}[0-9]$"
|
||||||
|
example: +1-202-555-0192
|
||||||
userStatus:
|
userStatus:
|
||||||
|
description: User status
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
description: User Status
|
|
||||||
xml:
|
xml:
|
||||||
name: User
|
name: User
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
# ReDoc vendor extensions
|
# ReDoc 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)
|
||||||
|
|
||||||
### <a name="infoObject"></a>[Info Object](http://swagger.io/specification/#infoObject) vendor extensions
|
### Info Object vendor extensions
|
||||||
|
Extends OpenAPI [Info Object](http://swagger.io/specification/#infoObject)
|
||||||
#### <a name="x-logo"></a> x-logo
|
#### x-logo
|
||||||
|
|
||||||
| Field Name | Type | Description |
|
| Field Name | Type | Description |
|
||||||
| :------------- | :-----------: | :---------- |
|
| :------------- | :-----------: | :---------- |
|
||||||
|
@ -47,9 +47,9 @@ info:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### [Tag object](http://swagger.io/specification/#tagObject) vendor extensions
|
### Tag Object vendor extensions
|
||||||
|
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
|
||||||
#### <a name="x-traitTag"></a> x-traitTag
|
#### x-traitTag
|
||||||
| 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) |
|
||||||
|
@ -74,9 +74,9 @@ description: Pagination description (can use markdown syntax)
|
||||||
x-traitTag: true
|
x-traitTag: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### [Operation Object](http://swagger.io/specification/#operationObject) vendor extensions
|
### Operation Object vendor extensions
|
||||||
|
Extends OpenAPI [Operation Object](http://swagger.io/specification/#operationObject)
|
||||||
#### <a name="x-code-samples"></a> x-code-samples
|
#### x-code-samples
|
||||||
| Field Name | Type | Description |
|
| Field Name | Type | Description |
|
||||||
| :------------- | :------: | :---------- |
|
| :------------- | :------: | :---------- |
|
||||||
| x-code-samples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation |
|
| x-code-samples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation |
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
<div>
|
<div>
|
||||||
<h1 class="api-info-header">{{data.title}} ({{data.version}})</h1>
|
<h1 class="api-info-header">{{info.title}} ({{info.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>
|
|
||||||
<p>
|
<p>
|
||||||
Download OpenAPI (fka Swagger) specification:
|
Download OpenAPI (fka Swagger) specification:
|
||||||
<a class="openapi-button" target="_blank" attr.href='{{specUrl}}'> Download </a>
|
<a class="openapi-button" target="_blank" attr.href='{{specUrl}}'> Download </a>
|
||||||
</p>
|
</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>
|
</div>
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
:host > div {
|
:host > div {
|
||||||
width: 60%;
|
width: 60%;
|
||||||
|
padding: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.openapi-button {
|
a.openapi-button {
|
||||||
|
@ -15,3 +17,8 @@ a.openapi-button {
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host [section] {
|
||||||
|
padding-top: 60px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { getChildDebugElement } from '../../../tests/helpers';
|
import { getChildDebugElement } from '../../../tests/helpers';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
import { OptionsService } from '../../services/index';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
inject,
|
inject,
|
||||||
|
@ -17,8 +18,14 @@ describe('Redoc components', () => {
|
||||||
let builder;
|
let builder;
|
||||||
let component;
|
let component;
|
||||||
let fixture;
|
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;
|
builder = tcb;
|
||||||
return specMgr.load('/tests/schemas/api-info-test.json');
|
return specMgr.load('/tests/schemas/api-info-test.json');
|
||||||
})));
|
})));
|
||||||
|
@ -32,8 +39,8 @@ describe('Redoc components', () => {
|
||||||
|
|
||||||
it('should init component data', () => {
|
it('should init component data', () => {
|
||||||
expect(component).not.toBeNull();
|
expect(component).not.toBeNull();
|
||||||
expect(component.data).not.toBeNull();
|
expect(component.info).not.toBeNull();
|
||||||
component.data.title.should.be.equal('Swagger Petstore');
|
component.info.title.should.be.equal('Swagger Petstore');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render api name and version', () => {
|
it('should render api name and version', () => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { SpecManager, RedocComponent, BaseComponent } from '../base';
|
import { SpecManager, RedocComponent, BaseComponent } from '../base';
|
||||||
import { OptionsService } from '../../services/index';
|
import { OptionsService, MenuService } from '../../services/index';
|
||||||
|
|
||||||
@RedocComponent({
|
@RedocComponent({
|
||||||
selector: 'api-info',
|
selector: 'api-info',
|
||||||
|
@ -9,17 +9,17 @@ import { OptionsService } from '../../services/index';
|
||||||
templateUrl: './api-info.html'
|
templateUrl: './api-info.html'
|
||||||
})
|
})
|
||||||
export class ApiInfo extends BaseComponent {
|
export class ApiInfo extends BaseComponent {
|
||||||
data: any;
|
info: any;
|
||||||
specUrl: String;
|
specUrl: String;
|
||||||
constructor(specMgr:SpecManager, private optionsService:OptionsService) {
|
constructor(specMgr:SpecManager, private optionsService:OptionsService, private menuServ: MenuService) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
this.data = this.componentSchema.info;
|
this.info = this.componentSchema.info;
|
||||||
this.specUrl = this.optionsService.options.specUrl;
|
this.specUrl = this.optionsService.options.specUrl;
|
||||||
if (parseInt(this.data.version.substring(0, 1)) !== NaN) {
|
if (parseInt(this.info.version.substring(0, 1)) !== NaN) {
|
||||||
this.data.version = 'v' + this.data.version;
|
this.info.version = 'v' + this.info.version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}">
|
||||||
|
|
|
@ -35,11 +35,11 @@ describe('Redoc components', () => {
|
||||||
|
|
||||||
it('should init component data', () => {
|
it('should init component data', () => {
|
||||||
expect(component).not.toBeNull();
|
expect(component).not.toBeNull();
|
||||||
expect(component.data).not.toBeNull();
|
expect(component.logo).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not display image when no x-logo', () => {
|
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 nativeElement = getChildDebugElement(fixture.debugElement, 'api-logo').nativeElement;
|
||||||
let imgElement = nativeElement.querySelector('img');
|
let imgElement = nativeElement.querySelector('img');
|
||||||
expect(imgElement).toBeNull();
|
expect(imgElement).toBeNull();
|
||||||
|
@ -49,8 +49,8 @@ describe('Redoc components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load values from spec and use transparent bgColor by default', () => {
|
it('should load values from spec and use transparent bgColor by default', () => {
|
||||||
component.data.imgUrl.should.endWith('petstore-logo.png');
|
component.logo.imgUrl.should.endWith('petstore-logo.png');
|
||||||
component.data.bgColor.should.be.equal('transparent');
|
component.logo.bgColor.should.be.equal('transparent');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,16 +8,16 @@ import {RedocComponent, BaseComponent, SpecManager} from '../base';
|
||||||
templateUrl: './api-logo.html'
|
templateUrl: './api-logo.html'
|
||||||
})
|
})
|
||||||
export class ApiLogo extends BaseComponent {
|
export class ApiLogo extends BaseComponent {
|
||||||
data:any = {};
|
logo:any = {};
|
||||||
|
|
||||||
constructor(specMgr:SpecManager) {
|
constructor(specMgr:SpecManager) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
let logoInfo = this.componentSchema.info['x-logo'];
|
let logoInfo = this.componentSchema.info['x-logo'];
|
||||||
if (!logoInfo) return;
|
if (!logoInfo) return;
|
||||||
this.data.imgUrl = logoInfo.url;
|
this.logo.imgUrl = logoInfo.url;
|
||||||
this.data.bgColor = logoInfo.backgroundColor || 'transparent';
|
this.logo.bgColor = logoInfo.backgroundColor || 'transparent';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,11 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
@Input() nestOdd: boolean;
|
@Input() nestOdd: boolean;
|
||||||
@Input() childFor: string;
|
@Input() childFor: string;
|
||||||
@Input() isArray: boolean;
|
@Input() isArray: boolean;
|
||||||
|
disableLazy: boolean = false;
|
||||||
loaded: boolean = false;
|
loaded: boolean = false;
|
||||||
constructor(private specMgr:SpecManager, private location:ViewContainerRef, private elementRef:ElementRef,
|
constructor(private specMgr:SpecManager, private location:ViewContainerRef, private elementRef:ElementRef,
|
||||||
private resolver:ComponentResolver, private optionsService:OptionsService, private _renderer: Renderer) {
|
private resolver:ComponentResolver, private optionsService:OptionsService, private _renderer: Renderer) {
|
||||||
|
this.disableLazy = this.optionsService.options.disableLazySchemas;
|
||||||
}
|
}
|
||||||
|
|
||||||
normalizePointer() {
|
normalizePointer() {
|
||||||
|
@ -67,7 +69,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
|
|
||||||
// skip caching view with tabs inside (discriminator)
|
// skip caching view with tabs inside (discriminator)
|
||||||
// as it needs attached controller
|
// as it needs attached controller
|
||||||
if (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas) {
|
if (!this.disableLazy && (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas)) {
|
||||||
this._loadAfterSelf();
|
this._loadAfterSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -85,11 +87,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
if (this.optionsService.options.disableLazySchemas) {
|
if (!this.auto && !this.disableLazy) return;
|
||||||
this._loadAfterSelf();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!this.auto) return;
|
|
||||||
this.loadCached();
|
this.loadCached();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ describe('Redoc components', () => {
|
||||||
builder = tcb;
|
builder = tcb;
|
||||||
specMgr = _spec;
|
specMgr = _spec;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = builder.createSync(TestAppComponent);
|
fixture = builder.createSync(TestAppComponent);
|
||||||
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema');
|
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema');
|
||||||
|
@ -37,14 +38,14 @@ describe('Redoc components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set isTrivial for non-object/array types', () => {
|
it('should set isTrivial for non-object/array types', () => {
|
||||||
component.pointer = '';
|
component.pointer = '#';
|
||||||
(<any>specMgr)._schema = {type: 'string'};
|
(<any>specMgr)._schema = {type: 'string'};
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
component.schema.isTrivial.should.be.true();
|
component.schema.isTrivial.should.be.true();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should use < * > notation for prop without type', () => {
|
it('should use < * > notation for prop without type', () => {
|
||||||
component.pointer = '';
|
component.pointer = '#';
|
||||||
(<any>specMgr)._schema = {type: 'object', properties: {
|
(<any>specMgr)._schema = {type: 'object', properties: {
|
||||||
test: {}
|
test: {}
|
||||||
}};
|
}};
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { Zippy } from '../../shared/components/Zippy/zippy';
|
||||||
detect: true
|
detect: true
|
||||||
})
|
})
|
||||||
export class JsonSchema extends BaseComponent {
|
export class JsonSchema extends BaseComponent {
|
||||||
schema: any;
|
schema: any = {};
|
||||||
activeDescendant:any = {};
|
activeDescendant:any = {};
|
||||||
hasDescendants: boolean = false;
|
hasDescendants: boolean = false;
|
||||||
_hasSubSchemas: boolean = false;
|
_hasSubSchemas: boolean = false;
|
||||||
|
@ -68,7 +68,8 @@ export class JsonSchema extends BaseComponent {
|
||||||
this.selectDescendant(0);
|
this.selectDescendant(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
|
if (!this.pointer) return;
|
||||||
if (this.nestOdd) {
|
if (this.nestOdd) {
|
||||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'nestodd', 'true');
|
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'nestodd', 'true');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<div class="method">
|
<div class="method">
|
||||||
<div class="method-content">
|
<div class="method-content">
|
||||||
<h2 class="method-header sharable-header">
|
<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>
|
</h2>
|
||||||
<div class="method-tags" *ngIf="data.methodInfo.tags.length">
|
<div class="method-tags" *ngIf="method.info.tags.length">
|
||||||
<a *ngFor="let tag of data.methodInfo.tags" attr.href="#{{tag}}"> {{tag}} </a>
|
<a *ngFor="let tag of method.info.tags" attr.href="#tag/{{tag}}"> {{tag}} </a>
|
||||||
</div>
|
</div>
|
||||||
<p *ngIf="data.methodInfo.description" class="method-description"
|
<p *ngIf="method.info.description" class="method-description"
|
||||||
[innerHtml]="data.methodInfo.description | marked">
|
[innerHtml]="method.info.description | marked">
|
||||||
</p>
|
</p>
|
||||||
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
||||||
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
||||||
|
@ -16,15 +16,15 @@
|
||||||
<h5>Definition</h5>
|
<h5>Definition</h5>
|
||||||
|
|
||||||
<div class="method-endpoint">
|
<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 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>
|
--></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="data.bodyParam">
|
<div *ngIf="method.bodyParam">
|
||||||
<br>
|
<br>
|
||||||
<request-samples [pointer]="pointer" [schemaPointer]="data.bodyParam._pointer">
|
<request-samples [pointer]="pointer" [schemaPointer]="method.bodyParam._pointer">
|
||||||
</request-samples>
|
</request-samples>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
@import '../../shared/styles/variables';
|
@import '../../shared/styles/variables';
|
||||||
@import '../../shared/styles/share-link';
|
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
padding-bottom: 100px;
|
padding-bottom: 100px;
|
||||||
|
@ -11,6 +11,10 @@
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: $secondary-color;
|
||||||
|
}
|
||||||
|
|
||||||
responses-list, params-list {
|
responses-list, params-list {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,14 +34,14 @@ describe('Redoc components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should init basic component data', () => {
|
it('should init basic component data', () => {
|
||||||
component.data.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
|
component.method.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
|
||||||
component.data.httpMethod.should.be.equal('put');
|
component.method.httpMethod.should.be.equal('put');
|
||||||
component.data.path.should.be.equal('/user/{username}');
|
component.method.path.should.be.equal('/user/{username}');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should main tag', () => {
|
it('should main tag', () => {
|
||||||
component.data.methodInfo.tags.should.be.empty();
|
component.method.info.tags.should.be.empty();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,25 +20,25 @@ import { SchemaHelper } from '../../services/schema-helper.service';
|
||||||
detect: true
|
detect: true
|
||||||
})
|
})
|
||||||
export class Method extends BaseComponent {
|
export class Method extends BaseComponent {
|
||||||
data:any;
|
method:any;
|
||||||
@Input() tag:string;
|
@Input() tag:string;
|
||||||
constructor(specMgr:SpecManager) {
|
constructor(specMgr:SpecManager) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
this.data = {};
|
this.method = {};
|
||||||
this.data.apiUrl = this.specMgr.apiUrl;
|
this.method.apiUrl = this.specMgr.apiUrl;
|
||||||
this.data.httpMethod = JsonPointer.baseName(this.pointer);
|
this.method.httpMethod = JsonPointer.baseName(this.pointer);
|
||||||
this.data.path = JsonPointer.baseName(this.pointer, 2);
|
this.method.path = JsonPointer.baseName(this.pointer, 2);
|
||||||
this.data.methodInfo = this.componentSchema;
|
this.method.info = this.componentSchema;
|
||||||
this.data.methodInfo.tags = this.filterMainTags(this.data.methodInfo.tags);
|
this.method.info.tags = this.filterMainTags(this.method.info.tags);
|
||||||
this.data.bodyParam = this.findBodyParam();
|
this.method.bodyParam = this.findBodyParam();
|
||||||
this.data.summary = SchemaHelper.methodSummary(this.componentSchema);
|
this.method.summary = SchemaHelper.methodSummary(this.componentSchema);
|
||||||
if (this.componentSchema.operationId) {
|
if (this.componentSchema.operationId) {
|
||||||
this.data.methodAnchor = 'operation/' + encodeURIComponent(this.componentSchema.operationId);
|
this.method.anchor = 'operation/' + encodeURIComponent(this.componentSchema.operationId);
|
||||||
} else {
|
} else {
|
||||||
this.data.methodAnchor = 'tag/' + encodeURIComponent(this.tag + this.pointer);
|
this.method.anchor = this.tag + encodeURIComponent(this.pointer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="methods">
|
<div class="methods">
|
||||||
<div class="tag" *ngFor="let tag of data.tags;trackBy:trackByTagName">
|
<div class="tag" *ngFor="let tag of tags;trackBy:trackByTagName">
|
||||||
<div class="tag-info" [attr.tag]="tag.name" *ngIf="!tag.empty">
|
<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>
|
<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>
|
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
||||||
</div>
|
</div>
|
||||||
<method *ngFor="let method of tag.methods;trackBy:trackByPointer" [pointer]="method.pointer" [attr.pointer]="method.pointer"
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
@import '../../shared/styles/variables';
|
@import '../../shared/styles/variables';
|
||||||
@import '../../shared/styles/share-link';
|
|
||||||
|
|
||||||
.tag-info {
|
.tag-info {
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
|
@ -21,6 +20,7 @@
|
||||||
color: $headers-color;
|
color: $headers-color;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.methods {
|
.methods {
|
||||||
|
|
|
@ -35,12 +35,12 @@ describe('Redoc components', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get correct tags list', () => {
|
it('should get correct tags list', () => {
|
||||||
expect(component.data.tags).not.toBeNull();
|
expect(component.tags).not.toBeNull();
|
||||||
component.data.tags.should.have.lengthOf(2);
|
component.tags.should.have.lengthOf(2);
|
||||||
component.data.tags[0].name.should.be.equal('traitTag');
|
component.tags[0].name.should.be.equal('traitTag');
|
||||||
component.data.tags[0].methods.should.be.empty();
|
component.tags[0].methods.should.be.empty();
|
||||||
component.data.tags[1].name.should.be.equal('tag1');
|
component.tags[1].name.should.be.equal('tag1');
|
||||||
component.data.tags[1].methods.should.have.lengthOf(2);
|
component.tags[1].methods.should.have.lengthOf(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,26 +15,21 @@ import { SchemaHelper } from '../../services/index';
|
||||||
detect: true
|
detect: true
|
||||||
})
|
})
|
||||||
export class MethodsList extends BaseComponent {
|
export class MethodsList extends BaseComponent {
|
||||||
data:any;
|
tags:Array<any> = [];
|
||||||
constructor(specMgr:SpecManager) {
|
constructor(specMgr:SpecManager) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
this.data = {};
|
|
||||||
// follow SwaggerUI behavior for cases when one method has more than one tag:
|
|
||||||
// duplicate methods
|
|
||||||
|
|
||||||
let tags = SchemaHelper.buildMenuTree(this.specMgr.schema);
|
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
|
// inject tag name into method info
|
||||||
tagInfo.methods = tagInfo.methods || [];
|
tagInfo.methods = tagInfo.methods || [];
|
||||||
tagInfo.methods.forEach(method => {
|
tagInfo.methods.forEach(method => {
|
||||||
method.tag = tagInfo.name;
|
method.tag = tagInfo.id;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.data.tags = tags;
|
|
||||||
// TODO: check $ref field
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByPointer(idx, el) {
|
trackByPointer(idx, el) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<h5 class="param-list-header" *ngIf="data.params.length"> Parameters </h5>
|
<h5 class="param-list-header" *ngIf="params.length"> Parameters </h5>
|
||||||
<template ngFor [ngForOf]="data.params" let-paramType="$implicit">
|
<template ngFor [ngForOf]="params" let-paramType="$implicit">
|
||||||
<header class="paramType">
|
<header class="paramType">
|
||||||
{{paramType.place}} Parameters
|
{{paramType.place}} Parameters
|
||||||
<span class="hint--top-right hint--large" [attr.data-hint]="paramType.placeHint">?</span>
|
<span class="hint--top-right hint--large" [attr.data-hint]="paramType.placeHint">?</span>
|
||||||
|
@ -14,6 +14,7 @@
|
||||||
<div>
|
<div>
|
||||||
<span class="param-type {{param.type}}" [ngClass]="{'with-hint': param._displayTypeHint}"
|
<span class="param-type {{param.type}}" [ngClass]="{'with-hint': param._displayTypeHint}"
|
||||||
title="{{param._displayTypeHint}}"> {{param._displayType}} {{param._displayFormat}}</span>
|
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>
|
<span *ngIf="param.required" class="param-required">Required</span>
|
||||||
<div class="default" *ngIf="param.default">Default: {{param.default | json}}</div>
|
<div class="default" *ngIf="param.default">Default: {{param.default | json}}</div>
|
||||||
<div *ngIf="param.enum" class="param-enum">
|
<div *ngIf="param.enum" class="param-enum">
|
||||||
|
@ -26,13 +27,13 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div *ngIf="data.bodyParam">
|
<div *ngIf="bodyParam">
|
||||||
<h5 class="param-list-header" *ngIf="data.bodyParam"> Request Body </h5>
|
<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>
|
<div>
|
||||||
<br>
|
<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>
|
</json-schema-lazy>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,14 +18,16 @@ function safePush(obj, prop, item) {
|
||||||
})
|
})
|
||||||
export class ParamsList extends BaseComponent {
|
export class ParamsList extends BaseComponent {
|
||||||
|
|
||||||
data:any;
|
params: Array<any>;
|
||||||
|
empty: boolean;
|
||||||
|
bodyParam: any;
|
||||||
|
|
||||||
constructor(specMgr:SpecManager) {
|
constructor(specMgr:SpecManager) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
this.data = {};
|
this.params = [];
|
||||||
let paramsList = this.specMgr.getMethodParams(this.pointer, true);
|
let paramsList = this.specMgr.getMethodParams(this.pointer, true);
|
||||||
|
|
||||||
paramsList = paramsList.map(paramSchema => {
|
paramsList = paramsList.map(paramSchema => {
|
||||||
|
@ -40,11 +42,11 @@ export class ParamsList extends BaseComponent {
|
||||||
if (paramsMap.body && paramsMap.body.length) {
|
if (paramsMap.body && paramsMap.body.length) {
|
||||||
let bodyParam = paramsMap.body[0];
|
let bodyParam = paramsMap.body[0];
|
||||||
bodyParam.pointer = bodyParam._pointer;
|
bodyParam.pointer = bodyParam._pointer;
|
||||||
this.data.bodyParam = bodyParam;
|
this.bodyParam = bodyParam;
|
||||||
paramsMap.body = undefined;
|
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 paramsPlaces = ['path', 'query', 'formData', 'header', 'body'];
|
||||||
let placeHint = {
|
let placeHint = {
|
||||||
|
@ -64,7 +66,7 @@ export class ParamsList extends BaseComponent {
|
||||||
params.push({place: place, placeHint: placeHint[place], params: paramsMap[place]});
|
params.push({place: place, placeHint: placeHint[place], params: paramsMap[place]});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.data.params = params;
|
this.params = params;
|
||||||
}
|
}
|
||||||
|
|
||||||
orderParams(params):any {
|
orderParams(params):any {
|
||||||
|
|
|
@ -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 {
|
redoc.loading {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<side-menu> </side-menu>
|
<side-menu> </side-menu>
|
||||||
</div>
|
</div>
|
||||||
<div id="api-content">
|
<div id="api-content">
|
||||||
|
<warnings></warnings>
|
||||||
<api-info></api-info>
|
<api-info></api-info>
|
||||||
<methods-list> </methods-list>
|
<methods-list> </methods-list>
|
||||||
<footer>
|
<footer>
|
||||||
|
|
|
@ -51,10 +51,6 @@ api-info, .side-bar {
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
api-info {
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
api-logo {
|
api-logo {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -150,6 +146,8 @@ api-logo {
|
||||||
color: #383838;
|
color: #383838;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@import '../../shared/styles/share-link';
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
|
|
|
@ -12,10 +12,12 @@ import { ApiInfo } from '../ApiInfo/api-info';
|
||||||
import { ApiLogo } from '../ApiLogo/api-logo';
|
import { ApiLogo } from '../ApiLogo/api-logo';
|
||||||
import { MethodsList } from '../MethodsList/methods-list';
|
import { MethodsList } from '../MethodsList/methods-list';
|
||||||
import { SideMenu } from '../SideMenu/side-menu';
|
import { SideMenu } from '../SideMenu/side-menu';
|
||||||
|
import { Warnings } from '../Warnings/warnings';
|
||||||
|
|
||||||
import { StickySidebar } from '../../shared/components/index';
|
import { StickySidebar } from '../../shared/components/index';
|
||||||
import {SpecManager} from '../../utils/SpecManager';
|
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 dom = new BrowserDomAdapter();
|
||||||
var _modeLocked = false;
|
var _modeLocked = false;
|
||||||
|
@ -25,11 +27,15 @@ var _modeLocked = false;
|
||||||
providers: [
|
providers: [
|
||||||
SpecManager,
|
SpecManager,
|
||||||
BrowserDomAdapter,
|
BrowserDomAdapter,
|
||||||
RedocEventsService
|
RedocEventsService,
|
||||||
|
ScrollService,
|
||||||
|
Hash,
|
||||||
|
MenuService,
|
||||||
|
WarningsService
|
||||||
],
|
],
|
||||||
templateUrl: './redoc.html',
|
templateUrl: './redoc.html',
|
||||||
styleUrls: ['./redoc.css'],
|
styleUrls: ['./redoc.css'],
|
||||||
directives: [ ApiInfo, ApiLogo, MethodsList, SideMenu, StickySidebar ],
|
directives: [ ApiInfo, ApiLogo, MethodsList, SideMenu, StickySidebar, Warnings ],
|
||||||
detect: true,
|
detect: true,
|
||||||
onPushOnly: false
|
onPushOnly: false
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<header *ngIf="data.schemaPointer || data.samples.length"> Request samples </header>
|
<header *ngIf="schemaPointer || samples.length"> Request samples </header>
|
||||||
<schema-sample *ngIf="!data.samples.length" [skipReadOnly]="true" [pointer]="data.schemaPointer"> </schema-sample>
|
<schema-sample *ngIf="!samples.length" [skipReadOnly]="true" [pointer]="schemaPointer"> </schema-sample>
|
||||||
<tabs *ngIf="data.samples.length" [selected] = "selectedLang" (change)=changeLangNotify($event)>
|
<tabs *ngIf="samples.length" [selected] = "selectedLang" (change)=changeLangNotify($event)>
|
||||||
<tab tabTitle="JSON">
|
<tab tabTitle="JSON">
|
||||||
<schema-sample [pointer]="data.schemaPointer" [skipReadOnly]="true"> </schema-sample>
|
<schema-sample [pointer]="schemaPointer" [skipReadOnly]="true"> </schema-sample>
|
||||||
</tab>
|
</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="code-sample">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<span copy-button [copyText]="sample.source" class="hint--top-left hint--inversed"><a>Copy</a></span>
|
<span copy-button [copyText]="sample.source" class="hint--top-left hint--inversed"><a>Copy</a></span>
|
||||||
|
|
|
@ -24,7 +24,9 @@ import { CopyButton } from '../../shared/components/CopyButton/copy-button.direc
|
||||||
export class RequestSamples extends BaseComponent {
|
export class RequestSamples extends BaseComponent {
|
||||||
childTabs: Tabs;
|
childTabs: Tabs;
|
||||||
selectedLang: EventEmitter<any>;
|
selectedLang: EventEmitter<any>;
|
||||||
data: any;
|
|
||||||
|
samples: Array<any>;
|
||||||
|
|
||||||
@Input() schemaPointer:string;
|
@Input() schemaPointer:string;
|
||||||
@ViewChildren(Tabs) childQuery:QueryList<Tabs>;
|
@ViewChildren(Tabs) childQuery:QueryList<Tabs>;
|
||||||
constructor(specMgr:SpecManager, public events:RedocEventsService) {
|
constructor(specMgr:SpecManager, public events:RedocEventsService) {
|
||||||
|
@ -38,9 +40,8 @@ export class RequestSamples extends BaseComponent {
|
||||||
this.events.samplesLanguageChanged.next(lang);
|
this.events.samplesLanguageChanged.next(lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
this.data = {};
|
this.schemaPointer = JsonPointer.join(this.schemaPointer, 'schema');;
|
||||||
this.data.schemaPointer = JsonPointer.join(this.schemaPointer, 'schema');
|
this.samples = this.componentSchema['x-code-samples'] || [];
|
||||||
this.data.samples = this.componentSchema['x-code-samples'] || [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<h2 class="responses-list-header" *ngIf="data.responses.length"> Responses </h2>
|
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
|
||||||
<zippy *ngFor="let response of data.responses;trackBy:trackByCode" title="{{response.code}} {{response.description}}"
|
<zippy *ngFor="let response of responses;trackBy:trackByCode" title="{{response.code}} {{response.description}}"
|
||||||
[type]="response.type" [empty]="response.empty" (open)="lazySchema.load()">
|
[type]="response.type" [empty]="response.empty" (open)="lazySchema.load()">
|
||||||
<div *ngIf="response.headers" class="response-headers">
|
<div *ngIf="response.headers" class="response-headers">
|
||||||
<header>
|
<header>
|
||||||
|
@ -7,8 +7,13 @@
|
||||||
</header>
|
</header>
|
||||||
<div class="header" *ngFor="let header of response.headers">
|
<div class="header" *ngFor="let header of response.headers">
|
||||||
<div class="header-name"> {{header.name}} </div>
|
<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.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 class="header-description" [innerHtml]="header.description | marked"> </div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -38,3 +38,20 @@ header {
|
||||||
.header {
|
.header {
|
||||||
margin-bottom: 10px;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { JsonSchemaLazy } from '../JsonSchema/json-schema-lazy';
|
||||||
import { Zippy } from '../../shared/components/index';
|
import { Zippy } from '../../shared/components/index';
|
||||||
import { statusCodeType } from '../../utils/helpers';
|
import { statusCodeType } from '../../utils/helpers';
|
||||||
import { OptionsService } from '../../services/index';
|
import { OptionsService } from '../../services/index';
|
||||||
|
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||||
|
|
||||||
function isNumeric(n) {
|
function isNumeric(n) {
|
||||||
return (!isNaN(parseFloat(n)) && isFinite(n));
|
return (!isNaN(parseFloat(n)) && isFinite(n));
|
||||||
|
@ -20,16 +21,15 @@ function isNumeric(n) {
|
||||||
detect: true
|
detect: true
|
||||||
})
|
})
|
||||||
export class ResponsesList extends BaseComponent {
|
export class ResponsesList extends BaseComponent {
|
||||||
data: any;
|
responses: Array<any>;
|
||||||
options: any;
|
options: any;
|
||||||
constructor(specMgr:SpecManager, optionsMgr:OptionsService) {
|
constructor(specMgr:SpecManager, optionsMgr:OptionsService) {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
this.options = optionsMgr.options;
|
this.options = optionsMgr.options;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
this.data = {};
|
this.responses = [];
|
||||||
this.data.responses = [];
|
|
||||||
|
|
||||||
let responses = this.componentSchema;
|
let responses = this.componentSchema;
|
||||||
if (!responses) return;
|
if (!responses) return;
|
||||||
|
@ -53,14 +53,14 @@ export class ResponsesList extends BaseComponent {
|
||||||
resp.headers = Object.keys(resp.headers).map((k) => {
|
resp.headers = Object.keys(resp.headers).map((k) => {
|
||||||
let respInfo = resp.headers[k];
|
let respInfo = resp.headers[k];
|
||||||
respInfo.name = k;
|
respInfo.name = k;
|
||||||
return respInfo;
|
return SchemaHelper.preprocess(respInfo, this.pointer, this.pointer);
|
||||||
});
|
});
|
||||||
resp.empty = false;
|
resp.empty = false;
|
||||||
}
|
}
|
||||||
resp.extendable = resp.headers || resp.length;
|
resp.extendable = resp.headers || resp.length;
|
||||||
return resp;
|
return resp;
|
||||||
});
|
});
|
||||||
this.data.responses = responses;
|
this.responses = responses;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByCode(idx, el) {
|
trackByCode(idx, el) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ export class ResponsesSamples extends BaseComponent {
|
||||||
super(specMgr);
|
super(specMgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareModel() {
|
init() {
|
||||||
this.data = {};
|
this.data = {};
|
||||||
this.data.responses = [];
|
this.data.responses = [];
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<div class="snippet">
|
<div class="snippet">
|
||||||
<!-- in case sample is not available for some reason -->
|
<!-- 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">
|
<div class="action-buttons">
|
||||||
<span> <a *ngIf="enableButtons" (click)="collapseAll()">Collapse all</a> </span>
|
<span> <a *ngIf="enableButtons" (click)="collapseAll()">Collapse all</a> </span>
|
||||||
<span> <a *ngIf="enableButtons" (click)="expandAll()">Expand 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>
|
</div>
|
||||||
<pre [innerHtml]="data.sample | jsonFormatter"></pre>
|
<pre [innerHtml]="sample | jsonFormatter"></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { CopyButton } from '../../shared/components/CopyButton/copy-button.direc
|
||||||
})
|
})
|
||||||
export class SchemaSample extends BaseComponent {
|
export class SchemaSample extends BaseComponent {
|
||||||
element: any;
|
element: any;
|
||||||
data: any;
|
sample: any;
|
||||||
enableButtons: boolean = false;
|
enableButtons: boolean = false;
|
||||||
@Input() skipReadOnly:boolean;
|
@Input() skipReadOnly:boolean;
|
||||||
|
|
||||||
|
@ -33,7 +33,6 @@ export class SchemaSample extends BaseComponent {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.data = {};
|
|
||||||
|
|
||||||
let base:any = {};
|
let base:any = {};
|
||||||
let sample;
|
let sample;
|
||||||
|
@ -78,12 +77,12 @@ export class SchemaSample extends BaseComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.cache(sample);
|
this.cache(sample);
|
||||||
this.data.sample = sample;
|
this.sample = sample;
|
||||||
this.initButtons();
|
this.initButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
initButtons() {
|
initButtons() {
|
||||||
if (typeof this.data.sample === 'object') {
|
if (typeof this.sample === 'object') {
|
||||||
this.enableButtons = true;
|
this.enableButtons = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,10 +97,10 @@ export class SchemaSample extends BaseComponent {
|
||||||
|
|
||||||
fromCache() {
|
fromCache() {
|
||||||
if (this.skipReadOnly && this.componentSchema['x-redoc-ro-sample']) {
|
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;
|
return true;
|
||||||
} else if (this.componentSchema['x-redoc-rw-sample']) {
|
} 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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
</div>
|
</div>
|
||||||
<div #desktop id="resources-nav">
|
<div #desktop id="resources-nav">
|
||||||
<h5 class="menu-header"> API reference </h5>
|
<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>
|
[ngClass]="{active: cat.active}"> {{cat.name}}</label>
|
||||||
<ul class="menu-subitems" @itemAnimation="cat.active ? 'expanded' : 'collapsed'">
|
<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}"
|
[ngClass]="{active: method.active}"
|
||||||
(click)="activateAndScroll(idx, methIdx)">
|
(click)="activateAndScroll(idx, methIdx)">
|
||||||
{{method.summary}}
|
{{method.summary}}
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('Redoc components', () => {
|
||||||
testOptions = opts;
|
testOptions = opts;
|
||||||
testOptions.options = {
|
testOptions.options = {
|
||||||
scrollYOffset: () => 0,
|
scrollYOffset: () => 0,
|
||||||
scrollParent: window
|
$scrollParent: window
|
||||||
};
|
};
|
||||||
return specMgr.load('/tests/schemas/extended-petstore.yml');
|
return specMgr.load('/tests/schemas/extended-petstore.yml');
|
||||||
})));
|
})));
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ElementRef, ChangeDetectorRef } from '@angular/core';
|
import { ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||||
|
|
||||||
import { BrowserDomAdapter } from '@angular/platform-browser/src/browser/browser_adapter';
|
import { BrowserDomAdapter } from '@angular/platform-browser/src/browser/browser_adapter';
|
||||||
import { global } from '@angular/core/src/facade/lang';
|
import { global } from '@angular/core/src/facade/lang';
|
||||||
import { trigger, state, animate, transition, style } from '@angular/core';
|
import { trigger, state, animate, transition, style } from '@angular/core';
|
||||||
import { RedocComponent, BaseComponent, SpecManager } from '../base';
|
import { RedocComponent, BaseComponent, SpecManager } from '../base';
|
||||||
import { ScrollService, Hash, MenuService, OptionsService } from '../../services/index';
|
import { ScrollService, Hash, MenuService, OptionsService } from '../../services/index';
|
||||||
|
|
||||||
|
import { MenuCategory } from '../../services/schema-helper.service';
|
||||||
|
|
||||||
@RedocComponent({
|
@RedocComponent({
|
||||||
selector: 'side-menu',
|
selector: 'side-menu',
|
||||||
templateUrl: './side-menu.html',
|
templateUrl: './side-menu.html',
|
||||||
providers: [ScrollService, MenuService, Hash],
|
|
||||||
styleUrls: ['./side-menu.css'],
|
styleUrls: ['./side-menu.css'],
|
||||||
detect: true,
|
detect: true,
|
||||||
onPushOnly: false,
|
onPushOnly: false,
|
||||||
|
@ -27,14 +29,16 @@ import { ScrollService, Hash, MenuService, OptionsService } from '../../services
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SideMenu extends BaseComponent {
|
export class SideMenu extends BaseComponent {
|
||||||
$element: any;
|
|
||||||
$mobileNav: any;
|
|
||||||
$resourcesNav: any;
|
|
||||||
$scrollParent: any;
|
|
||||||
activeCatCaption: string;
|
activeCatCaption: string;
|
||||||
activeItemCaption: string;
|
activeItemCaption: string;
|
||||||
options: any;
|
categories: Array<MenuCategory>;
|
||||||
data: any;
|
|
||||||
|
private options: any;
|
||||||
|
private $element: any;
|
||||||
|
private $mobileNav: any;
|
||||||
|
private $resourcesNav: any;
|
||||||
|
private $scrollParent: any;
|
||||||
|
|
||||||
constructor(specMgr:SpecManager, elementRef:ElementRef, private dom:BrowserDomAdapter,
|
constructor(specMgr:SpecManager, elementRef:ElementRef, private dom:BrowserDomAdapter,
|
||||||
private scrollService:ScrollService, private menuService:MenuService, private hash:Hash,
|
private scrollService:ScrollService, private menuService:MenuService, private hash:Hash,
|
||||||
optionsService:OptionsService, private detectorRef:ChangeDetectorRef) {
|
optionsService:OptionsService, private detectorRef:ChangeDetectorRef) {
|
||||||
|
@ -66,6 +70,8 @@ export class SideMenu extends BaseComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
this.categories = this.menuService.categories;
|
||||||
|
|
||||||
this.$mobileNav = this.dom.querySelector(this.$element, '.mobile-nav');
|
this.$mobileNav = this.dom.querySelector(this.$element, '.mobile-nav');
|
||||||
this.$resourcesNav = this.dom.querySelector(this.$element, '#resources-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() {
|
mobileMode() {
|
||||||
return this.$mobileNav.clientHeight > 0;
|
return this.$mobileNav.clientHeight > 0;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,7 @@ export class SideMenu extends BaseComponent {
|
||||||
toggleMobileNav() {
|
toggleMobileNav() {
|
||||||
let dom = this.dom;
|
let dom = this.dom;
|
||||||
let $overflowParent = (this.options.$scrollParent === global) ? dom.defaultDoc().body
|
let $overflowParent = (this.options.$scrollParent === global) ? dom.defaultDoc().body
|
||||||
: this.$scrollParent.$scrollParent;
|
: this.$scrollParent;
|
||||||
if (dom.hasStyle(this.$resourcesNav, 'height')) {
|
if (dom.hasStyle(this.$resourcesNav, 'height')) {
|
||||||
dom.removeStyle(this.$resourcesNav, 'height');
|
dom.removeStyle(this.$resourcesNav, 'height');
|
||||||
dom.removeStyle($overflowParent, 'overflow-y');
|
dom.removeStyle($overflowParent, 'overflow-y');
|
||||||
|
|
4
lib/components/Warnings/warnings.html
Normal file
4
lib/components/Warnings/warnings.html
Normal 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>
|
33
lib/components/Warnings/warnings.scss
Normal file
33
lib/components/Warnings/warnings.scss
Normal 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;
|
||||||
|
}
|
33
lib/components/Warnings/warnings.ts
Normal file
33
lib/components/Warnings/warnings.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,14 +29,11 @@ describe('Redoc components', () => {
|
||||||
component.componentSchema.should.be.deepEqual(specMgr._schema.tags);
|
component.componentSchema.should.be.deepEqual(specMgr._schema.tags);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call prepareModel and init virtual methods after init', () => {
|
it('should call init virtual methods after init', () => {
|
||||||
spyOn(component, 'prepareModel');
|
|
||||||
spyOn(component, 'init');
|
spyOn(component, 'init');
|
||||||
component.ngOnInit();
|
component.ngOnInit();
|
||||||
|
|
||||||
component.prepareModel.calls.count().should.be.equal(1);
|
|
||||||
component.init.calls.count().should.be.equal(1);
|
component.init.calls.count().should.be.equal(1);
|
||||||
component.prepareModel.and.callThrough();
|
|
||||||
component.init.and.callThrough();
|
component.init.and.callThrough();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { CORE_DIRECTIVES, JsonPipe, AsyncPipe } from '@angular/common';
|
import { CORE_DIRECTIVES, JsonPipe, AsyncPipe } from '@angular/common';
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
import { SpecManager } from '../utils/SpecManager';
|
||||||
import { MarkedPipe, JsonPointerEscapePipe } from '../utils/pipes';
|
import { MarkedPipe, JsonPointerEscapePipe, SafePipe } from '../utils/pipes';
|
||||||
|
|
||||||
export { SpecManager };
|
export { SpecManager };
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ function snapshot(obj) {
|
||||||
export function RedocComponent(options) {
|
export function RedocComponent(options) {
|
||||||
let inputs = safeConcat(options.inputs, commonInputs);
|
let inputs = safeConcat(options.inputs, commonInputs);
|
||||||
let directives = safeConcat(options.directives, CORE_DIRECTIVES);
|
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;
|
if (options.onPushOnly === undefined) options.onPushOnly = true;
|
||||||
|
|
||||||
return function decorator(target) {
|
return function decorator(target) {
|
||||||
|
@ -79,7 +79,7 @@ export function RedocComponent(options) {
|
||||||
*/
|
*/
|
||||||
export class BaseComponent implements OnInit, OnDestroy {
|
export class BaseComponent implements OnInit, OnDestroy {
|
||||||
componentSchema: any = null;
|
componentSchema: any = null;
|
||||||
pointer: String;
|
pointer: string;
|
||||||
dereferencedCache = {};
|
dereferencedCache = {};
|
||||||
|
|
||||||
constructor(public specMgr: SpecManager) {
|
constructor(public specMgr: SpecManager) {
|
||||||
|
@ -90,7 +90,6 @@ export class BaseComponent implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.componentSchema = this.specMgr.byPointer(this.pointer || '');
|
this.componentSchema = this.specMgr.byPointer(this.pointer || '');
|
||||||
this.prepareModel();
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,15 +98,7 @@ export class BaseComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to prepare model based on component schema
|
* Used to initialize component
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
prepareModel():any {
|
|
||||||
// emtpy
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to initialize component. Run after prepareModel
|
|
||||||
* @abstract
|
* @abstract
|
||||||
*/
|
*/
|
||||||
init() {
|
init() {
|
||||||
|
|
|
@ -7,3 +7,4 @@ export * from './scroll.service';
|
||||||
export * from './hash.service';
|
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';
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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/SpecManager';
|
||||||
import { SchemaHelper } from './schema-helper.service';
|
import { SchemaHelper, MenuCategory } from './schema-helper.service';
|
||||||
|
|
||||||
const CHANGE = {
|
const CHANGE = {
|
||||||
NEXT : 1,
|
NEXT : 1,
|
||||||
|
@ -14,7 +14,7 @@ const CHANGE = {
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MenuService {
|
export class MenuService {
|
||||||
changed: EventEmitter<any> = new EventEmitter();
|
changed: EventEmitter<any> = new EventEmitter();
|
||||||
categories: any;
|
categories: Array<MenuCategory>;
|
||||||
|
|
||||||
activeCatIdx: number = 0;
|
activeCatIdx: number = 0;
|
||||||
activeMethodIdx: number = -1;
|
activeMethodIdx: number = -1;
|
||||||
|
@ -55,11 +55,11 @@ export class MenuService {
|
||||||
|
|
||||||
getCurrentMethodEl() {
|
getCurrentMethodEl() {
|
||||||
return this.getMethodElByPtr(this.activeMethodPtr,
|
return this.getMethodElByPtr(this.activeMethodPtr,
|
||||||
this.categories[this.activeCatIdx].name);
|
this.categories[this.activeCatIdx].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMethodElByPtr(ptr, tag) {
|
getMethodElByPtr(ptr, section) {
|
||||||
let selector = ptr ? `[pointer="${ptr}"][tag="${tag}"]` : `[tag="${tag}"]`;
|
let selector = ptr ? `[pointer="${ptr}"][section="${section}"]` : `[section="${section}"]`;
|
||||||
return document.querySelector(selector);
|
return document.querySelector(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +95,7 @@ export class MenuService {
|
||||||
_calcActiveIndexes(offset) {
|
_calcActiveIndexes(offset) {
|
||||||
let menu = this.categories;
|
let menu = this.categories;
|
||||||
let catCount = menu.length;
|
let catCount = menu.length;
|
||||||
|
if (!catCount) return [0, -1];
|
||||||
let catLength = menu[this.activeCatIdx].methods.length;
|
let catLength = menu[this.activeCatIdx].methods.length;
|
||||||
|
|
||||||
let resMethodIdx = this.activeMethodIdx + offset;
|
let resMethodIdx = this.activeMethodIdx + offset;
|
||||||
|
@ -140,10 +141,11 @@ 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 if (namespace === 'tag') {
|
} else {
|
||||||
let tag = ptr.split('/')[0];
|
let sectionId = ptr.split('/')[0];
|
||||||
ptr = ptr.substr(tag.length);
|
ptr = ptr.substr(sectionId.length) || null;
|
||||||
$el = this.getMethodElByPtr(ptr, tag);
|
sectionId = namespace + (sectionId ? '/' + sectionId : '');
|
||||||
|
$el = this.getMethodElByPtr(ptr, sectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($el) this.scrollService.scrollTo($el);
|
if ($el) this.scrollService.scrollTo($el);
|
||||||
|
|
|
@ -9,7 +9,7 @@ const defaults = {
|
||||||
debugMode: false//global && global.redocDebugMode
|
debugMode: false//global && global.redocDebugMode
|
||||||
};
|
};
|
||||||
|
|
||||||
const OPTION_NAMES = new Set(['scrollYOffset', 'disableLazySchemas', 'specUrl']);
|
const OPTION_NAMES = new Set(['scrollYOffset', 'disableLazySchemas', 'specUrl', 'suppressWarnings']);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OptionsService {
|
export class OptionsService {
|
||||||
|
@ -70,5 +70,6 @@ export class OptionsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isString(this._options.disableLazySchemas)) this._options.disableLazySchemas = true;
|
if (isString(this._options.disableLazySchemas)) this._options.disableLazySchemas = true;
|
||||||
|
if (isString(this._options.suppressWarnings)) this._options.suppressWarnings = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,9 +59,13 @@ describe('Spec Helper', () => {
|
||||||
info.methods[0].summary.should.be.equal('test post');
|
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', () => {
|
it('should map x-traitTag to empty methods list', () => {
|
||||||
let info = menuTree[0];
|
let info = menuTree[0];
|
||||||
info['x-traitTag'].should.be.true();
|
|
||||||
info.methods.should.be.empty();
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,44 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
import { JsonPointer } from '../utils/JsonPointer';
|
import { JsonPointer } from '../utils/JsonPointer';
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
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 {
|
interface PropertyPreprocessOptions {
|
||||||
childFor: string;
|
childFor: string;
|
||||||
skipReadOnly?: boolean;
|
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 = {
|
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: {
|
general: {
|
||||||
check: () => true,
|
check: () => true,
|
||||||
inject: (injectTo, propertySchema, pointer) => {
|
inject: (injectTo, propertySchema, pointer) => {
|
||||||
|
@ -225,17 +255,36 @@ export class SchemaHelper {
|
||||||
(method.description && method.description.substring(0, 50)) || '<no description>';
|
(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 tag2MethodMapping = {};
|
||||||
|
|
||||||
let definedTags = schema.tags || [];
|
for (let header of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-headers'] || []))) {
|
||||||
// add tags into map to preserve order
|
let id = 'section/' + slugify(header);
|
||||||
for (let tag of definedTags) {
|
tag2MethodMapping[id] = {
|
||||||
tag2MethodMapping[tag.name] = {
|
name: header, id: id, virtual: true, methods: []
|
||||||
'description': tag.description,
|
};
|
||||||
'name': tag.name,
|
}
|
||||||
'x-traitTag': tag['x-traitTag'],
|
|
||||||
'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 methodPointer = JsonPointer.compile(['paths', path, method]);
|
||||||
let methodSummary = SchemaHelper.methodSummary(methodInfo);
|
let methodSummary = SchemaHelper.methodSummary(methodInfo);
|
||||||
for (let tag of tags) {
|
for (let tag of tags) {
|
||||||
let tagDetails = tag2MethodMapping[tag];
|
let id = 'tag/' + slugify(tag);
|
||||||
if (!tag2MethodMapping[tag]) {
|
let tagDetails = tag2MethodMapping[id];
|
||||||
|
if (!tagDetails) {
|
||||||
tagDetails = {
|
tagDetails = {
|
||||||
name: tag,
|
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 = [];
|
if (!tagDetails.methods) tagDetails.methods = [];
|
||||||
tagDetails.methods.push({
|
tagDetails.methods.push({
|
||||||
pointer: methodPointer,
|
pointer: methodPointer,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
|
||||||
import { SpecManager } from '../utils/SpecManager';
|
import { SpecManager } from '../utils/SpecManager';
|
||||||
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';
|
||||||
|
|
||||||
interface Reference {
|
interface Reference {
|
||||||
$ref: string;
|
$ref: string;
|
||||||
|
@ -29,6 +30,7 @@ export class SchemaNormalizer {
|
||||||
let resolved = this._dereferencer.dereference(subSchema, ptr);
|
let resolved = this._dereferencer.dereference(subSchema, ptr);
|
||||||
if (resolved.allOf) {
|
if (resolved.allOf) {
|
||||||
resolved._pointer = resolved._pointer || ptr;
|
resolved._pointer = resolved._pointer || ptr;
|
||||||
|
resolved = Object.assign({}, resolved);
|
||||||
AllOfMerger.merge(resolved, resolved.allOf, {omitParent: opts.omitParent});
|
AllOfMerger.merge(resolved, resolved.allOf, {omitParent: opts.omitParent});
|
||||||
}
|
}
|
||||||
return resolved;
|
return resolved;
|
||||||
|
@ -127,20 +129,22 @@ class AllOfMerger {
|
||||||
private static checkCanMerge(subSchema, into) {
|
private static checkCanMerge(subSchema, into) {
|
||||||
// TODO: add support for merge array schemas
|
// TODO: add support for merge array schemas
|
||||||
if (typeof subSchema !== 'object') {
|
if (typeof subSchema !== 'object') {
|
||||||
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found
|
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found ` +
|
||||||
${subSchema}`;
|
`${subSchema} at "#${into._pointer}"`;
|
||||||
throw new Error(errMessage);
|
throw new Error(errMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (into.type && subSchema.type && into.type !== subSchema.type) {
|
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);
|
throw new Error(errMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (into.type === 'array') {
|
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: 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;
|
let keysCount = Object.keys(schema).length;
|
||||||
if ( keysCount > 2 || (keysCount === 2 && !schema.description) ) {
|
if ( keysCount > 2 || (keysCount === 2 && !schema.description) ) {
|
||||||
console.warn(`other properties defined at the same level as $ref at '${pointer}'.
|
WarningsService.warn(`Other properties are defined at the same level as $ref at "#${pointer}". ` +
|
||||||
They are IGNORRED according to JsonSchema spec`);
|
'They are IGNORRED according to the JsonSchema spec');
|
||||||
resolved.description = resolved.description || schema.description;
|
resolved.description = resolved.description || schema.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
lib/services/warnings.service.ts
Normal file
23
lib/services/warnings.service.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,10 +18,6 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sharable-header {
|
|
||||||
color: $secondary-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sharable-header:hover .share-link:before, .share-link:hover:before {
|
.sharable-header:hover .share-link:before, .share-link:hover:before {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,6 @@ $green : #00aa13;
|
||||||
$yellow : #f1c400;
|
$yellow : #f1c400;
|
||||||
$red : #e53935;
|
$red : #e53935;
|
||||||
|
|
||||||
// Font
|
|
||||||
// ---------------------------
|
|
||||||
$em-size : 14px;
|
$em-size : 14px;
|
||||||
|
|
||||||
// Font weights
|
// Font weights
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
|
|
||||||
import JsonSchemaRefParser from 'json-schema-ref-parser';
|
import JsonSchemaRefParser from 'json-schema-ref-parser';
|
||||||
import JsonPointer from './JsonPointer';
|
import JsonPointer from './JsonPointer';
|
||||||
|
import { renderMd, safePush } from './helpers';
|
||||||
|
import slugify from 'slugify';
|
||||||
|
import { parse as urlParse } from 'url';
|
||||||
|
|
||||||
export class SpecManager {
|
export class SpecManager {
|
||||||
public _schema: any = {};
|
public _schema: any = {};
|
||||||
public apiUrl: string;
|
public apiUrl: string;
|
||||||
private _instance: any;
|
private _instance: any;
|
||||||
|
private _url: string;
|
||||||
|
|
||||||
static instance() {
|
static instance() {
|
||||||
return new SpecManager();
|
return new SpecManager();
|
||||||
|
@ -26,6 +30,7 @@ export class SpecManager {
|
||||||
|
|
||||||
JsonSchemaRefParser.bundle(url, {http: {withCredentials: false}})
|
JsonSchemaRefParser.bundle(url, {http: {withCredentials: false}})
|
||||||
.then(schema => {
|
.then(schema => {
|
||||||
|
this._url = url;
|
||||||
this._schema = schema;
|
this._schema = schema;
|
||||||
resolve(this._schema);
|
resolve(this._schema);
|
||||||
this.init();
|
this.init();
|
||||||
|
@ -37,11 +42,36 @@ export class SpecManager {
|
||||||
|
|
||||||
/* calculate common used values */
|
/* calculate common used values */
|
||||||
init() {
|
init() {
|
||||||
if (!this._schema || !this._schema.schemes) return;
|
let protocol;
|
||||||
this.apiUrl = this._schema.schemes[0] + '://' + this._schema.host + this._schema.basePath;
|
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('/')) {
|
if (this.apiUrl.endsWith('/')) {
|
||||||
this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length - 1);
|
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() {
|
get schema() {
|
||||||
|
|
|
@ -1,4 +1,59 @@
|
||||||
'use strict';
|
'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) {
|
export function statusCodeType(statusCode) {
|
||||||
if (statusCode < 100 || statusCode > 599) {
|
if (statusCode < 100 || statusCode > 599) {
|
||||||
|
@ -29,3 +84,8 @@ export function defaults(target, src) {
|
||||||
}
|
}
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function safePush(obj, prop, val) {
|
||||||
|
if (!obj[prop]) obj[prop] = [];
|
||||||
|
obj[prop].push(val);
|
||||||
|
}
|
||||||
|
|
|
@ -5,24 +5,11 @@ import { DomSanitizationService } from '@angular/platform-browser';
|
||||||
import { isString, stringify, isBlank } from '@angular/core/src/facade/lang';
|
import { isString, stringify, isBlank } from '@angular/core/src/facade/lang';
|
||||||
import { BaseException } from '@angular/core/src/facade/exceptions';
|
import { BaseException } from '@angular/core/src/facade/exceptions';
|
||||||
import JsonPointer from './JsonPointer';
|
import JsonPointer from './JsonPointer';
|
||||||
|
import { renderMd } from './helpers';
|
||||||
|
|
||||||
declare var Prism: any;
|
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 {
|
class InvalidPipeArgumentException extends BaseException {
|
||||||
constructor(type, value) {
|
constructor(type, value) {
|
||||||
|
@ -73,11 +60,24 @@ export class MarkedPipe implements PipeTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.sanitizer.bypassSecurityTrustHtml(
|
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 = {
|
const langMap = {
|
||||||
'c++': 'cpp',
|
'c++': 'cpp',
|
||||||
'c#': 'csharp',
|
'c#': 'csharp',
|
||||||
|
|
|
@ -1,3 +1,26 @@
|
||||||
'use strict';
|
'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'
|
||||||
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "redoc",
|
"name": "redoc",
|
||||||
"description": "Swagger-generated API Reference Documentation",
|
"description": "Swagger-generated API Reference Documentation",
|
||||||
"version": "0.16.1",
|
"version": "1.0.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/Rebilly/ReDoc"
|
"url": "git://github.com/Rebilly/ReDoc"
|
||||||
|
@ -48,7 +48,9 @@
|
||||||
"remarkable": "npm:remarkable@^1.6.2",
|
"remarkable": "npm:remarkable@^1.6.2",
|
||||||
"rxjs": "npm:rxjs@5.0.0-beta.6",
|
"rxjs": "npm:rxjs@5.0.0-beta.6",
|
||||||
"scrollparent": "npm:scrollparent@^0.1.0",
|
"scrollparent": "npm:scrollparent@^0.1.0",
|
||||||
|
"slugify": "npm:slugify@^0.1.1",
|
||||||
"stream-http": "npm:stream-http@^2.3.0",
|
"stream-http": "npm:stream-http@^2.3.0",
|
||||||
|
"url": "github:jspm/nodelibs-url@^0.1.0",
|
||||||
"zone.js": "npm:zone.js@0.6.12"
|
"zone.js": "npm:zone.js@0.6.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -132,6 +134,7 @@
|
||||||
"shelljs": "^0.7.0",
|
"shelljs": "^0.7.0",
|
||||||
"should": "^9.0.2",
|
"should": "^9.0.2",
|
||||||
"sinon": "^1.17.2",
|
"sinon": "^1.17.2",
|
||||||
|
"slugify": "^0.1.1",
|
||||||
"systemjs-builder": "^0.15.16",
|
"systemjs-builder": "^0.15.16",
|
||||||
"tslint": "^3.13.0",
|
"tslint": "^3.13.0",
|
||||||
"tslint-stylish": "^2.1.0-beta",
|
"tslint-stylish": "^2.1.0-beta",
|
||||||
|
|
|
@ -38,8 +38,10 @@ System.config({
|
||||||
"remarkable": "npm:remarkable@1.6.2",
|
"remarkable": "npm:remarkable@1.6.2",
|
||||||
"rxjs": "npm:rxjs@5.0.0-beta.6",
|
"rxjs": "npm:rxjs@5.0.0-beta.6",
|
||||||
"scrollparent": "npm:scrollparent@0.1.0",
|
"scrollparent": "npm:scrollparent@0.1.0",
|
||||||
|
"slugify": "npm:slugify@0.1.1",
|
||||||
"stream-http": "npm:stream-http@2.3.0",
|
"stream-http": "npm:stream-http@2.3.0",
|
||||||
"systemjs/plugin-json": "github:systemjs/plugin-json@0.1.2",
|
"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",
|
"zone.js": "npm:zone.js@0.6.12",
|
||||||
"github:jspm/nodelibs-assert@0.1.0": {
|
"github:jspm/nodelibs-assert@0.1.0": {
|
||||||
"assert": "npm:assert@1.4.1"
|
"assert": "npm:assert@1.4.1"
|
||||||
|
|
|
@ -43,7 +43,7 @@ describe('Scroll sync', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update active menu entries on page scroll forwards', () => {
|
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($('.menu-cat-header.active').getInnerHtml()).toContain('store');
|
||||||
expect($('.selected-tag').getInnerHtml()).toContain('store');
|
expect($('.selected-tag').getInnerHtml()).toContain('store');
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,5 +16,16 @@
|
||||||
"host": "petstore.swagger.io",
|
"host": "petstore.swagger.io",
|
||||||
"basePath": "/v2/",
|
"basePath": "/v2/",
|
||||||
"schemes": ["http"],
|
"schemes": ["http"],
|
||||||
"paths": {}
|
"paths": {
|
||||||
|
"/pet": {
|
||||||
|
"post": {
|
||||||
|
"tags": ["tag1"],
|
||||||
|
"summary": "tag1"
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"tags": ["tag1", "traitTag"],
|
||||||
|
"summary": "two tags",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,12 @@ describe('Utils', () => {
|
||||||
specMgr.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
|
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', () => {
|
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/3');
|
||||||
|
|
2590
typings/globals/node/index.d.ts
vendored
Normal file
2590
typings/globals/node/index.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
8
typings/globals/node/typings.json
Normal file
8
typings/globals/node/typings.json
Normal 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
1
typings/index.d.ts
vendored
|
@ -1,3 +1,4 @@
|
||||||
/// <reference path="globals/jasmine/index.d.ts" />
|
/// <reference path="globals/jasmine/index.d.ts" />
|
||||||
/// <reference path="globals/json-pointer/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" />
|
/// <reference path="globals/should/index.d.ts" />
|
||||||
|
|
4
typings/slugify.d.ts
vendored
Normal file
4
typings/slugify.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
declare module 'slugify' {
|
||||||
|
var x: any;
|
||||||
|
export default x;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user