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
|
||||
**OpenAPI/Swagger-generated API Reference Documentation**
|
||||
|
||||
[![Build Status](https://travis-ci.org/Rebilly/ReDoc.svg?branch=master)](https://travis-ci.org/Rebilly/ReDoc) [![Coverage Status](https://coveralls.io/repos/Rebilly/ReDoc/badge.svg?branch=master&service=github)](https://coveralls.io/github/Rebilly/ReDoc?branch=master) [![Tested on APIs.guru](http://api.apis.guru/badges/tested_on.svg)](https://APIs.guru) [![Code Climate](https://codeclimate.com/github/Rebilly/ReDoc/badges/gpa.svg)](https://codeclimate.com/github/Rebilly/ReDoc) [![David](https://david-dm.org/Rebilly/ReDoc/dev-status.svg)](https://david-dm.org/Rebilly/ReDoc#info=devDependencies) [![Stories in Ready](https://badge.waffle.io/Rebilly/ReDoc.png?label=ready&title=Ready)](https://waffle.io/Rebilly/ReDoc)
|
||||
|
||||
[![npm](http://img.shields.io/npm/v/redoc.svg)](https://www.npmjs.com/package/redoc) [![Bower](http://img.shields.io/bower/v/redoc.svg)](http://bower.io/) [![License](https://img.shields.io/npm/l/redoc.svg)](https://github.com/Rebilly/ReDoc/blob/master/LICENSE)
|
||||
|
||||
[![Browser Compatibility](https://saucelabs.com/browser-matrix/redoc.svg)](https://saucelabs.com/u/redoc)
|
||||
|
||||
Swagger-generated API Reference Documentation
|
||||
![ReDoc demo](demo/redoc-demo.png)
|
||||
|
||||
[Live demo](http://rebilly.github.io/ReDoc/)
|
||||
## [Live demo](http://rebilly.github.io/ReDoc/)
|
||||
|
||||
## Roadmap
|
||||
- [ ] docs pre-rendering (performance and SEO)
|
||||
- [ ] ability to simple customization
|
||||
- [ ] built-in API Console
|
||||
|
||||
## Releases
|
||||
We host latest and all the previous ReDoc releases on GitHub Pages-based **CDN**:
|
||||
- `latest` release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
|
||||
- particular release, e.g. `v0.16.1`: https://rebilly.github.io/ReDoc/releases/v0.16.0/redoc.min.js
|
||||
|
||||
## Deployment
|
||||
|
||||
#### tl;dr
|
||||
### TL;DR
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
@ -21,28 +34,24 @@ Swagger-generated API Reference Documentation
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!--
|
||||
ReDoc uses font options from the parent element
|
||||
So override default browser styles
|
||||
ReDoc doesn't change outer page styles
|
||||
-->
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'>
|
||||
</redoc>
|
||||
<script src="bower_components/redoc/dist/redoc.min.js"> </script>
|
||||
<redoc spec-url='http://petstore.swagger.io/v2/swagger.json'></redoc>
|
||||
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
That's all folks!
|
||||
|
||||
#### 1. Install redoc
|
||||
### 1. Install ReDoc (skip this step for CDN)
|
||||
Install using [bower](bower.io):
|
||||
|
||||
bower install redoc
|
||||
|
@ -51,12 +60,13 @@ or using [npm](https://docs.npmjs.com/getting-started/what-is-npm):
|
|||
|
||||
npm install redoc --save
|
||||
|
||||
Alternatively, you can **reference redoc directly** from CDN:
|
||||
- latest release: https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js
|
||||
- particular release, e.g. v0.14.0: https://rebilly.github.io/ReDoc/releases/v0.14.0/redoc.min.js
|
||||
### 2. Reference redoc script in HTML
|
||||
For **CDN**:
|
||||
```html
|
||||
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
|
||||
```
|
||||
|
||||
#### 2. Reference redoc script in HTML
|
||||
Then reference [`redoc.min.js`](https://raw.githubusercontent.com/Rebilly/ReDoc/releases/dist/redoc.min.js) in your HTML page:
|
||||
For bower:
|
||||
```html
|
||||
<script src="bower_components/redoc/dist/redoc.min.js"> </script>
|
||||
```
|
||||
|
@ -65,28 +75,29 @@ For npm:
|
|||
<script src="node_modules/redoc/dist/redoc.min.js"> </script>
|
||||
```
|
||||
|
||||
#### 3. Add `<redoc>` element to your page
|
||||
### 3. Add `<redoc>` element to your page
|
||||
```html
|
||||
<redoc spec-url="<url to your spec>"></redoc>
|
||||
<redoc spec-url="url/to/your/spec"></redoc>
|
||||
```
|
||||
|
||||
#### 4. Enjoy :smile:
|
||||
### 4. Enjoy :smile:
|
||||
|
||||
## Configuration
|
||||
|
||||
#### Swagger vendor extensions
|
||||
### Swagger vendor extensions
|
||||
ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions):
|
||||
* [`x-logo`](docs/redoc-vendor-extensions.md#x-logo) - is used to specify API logo
|
||||
* [`x-traitTag`](docs/redoc-vendor-extensions.md#x-traitTag) - useful for handling out common things like Pagination, Rate-Limits, etc
|
||||
* [`x-code-samples`](docs/redoc-vendor-extensions.md#x-code-samples) - specify operation code samples
|
||||
|
||||
#### Options
|
||||
* `spec-url` - relative or absolute url to your spec file
|
||||
* `scroll-y-offset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc.
|
||||
### Options
|
||||
* `spec-url` - relative or absolute url to your spec file;
|
||||
* `scroll-y-offset` - If set, specifies a vertical scroll-offset. This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc;
|
||||
`scroll-y-offset` can be specified in various ways:
|
||||
* **number**: A fixed number of pixels to be used as offset
|
||||
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset.
|
||||
* **function**: A getter function. Must return a number representing the offset (in pixels).
|
||||
* **number**: A fixed number of pixels to be used as offset;
|
||||
* **selector**: selector of the element to be used for specifying the offset. The distance from the top of the page to the element's bottom will be used as offset;
|
||||
* **function**: A getter function. Must return a number representing the offset (in pixels);
|
||||
* `suppress-warnings` - if set, warnings are not rendered at the top of page (they still are logged to the console).
|
||||
|
||||
## Advanced usage
|
||||
Instead of adding `spec-url` attribute to the `<redoc>` element you can initialize ReDoc via globally exposed `Redoc` object:
|
||||
|
|
|
@ -4,19 +4,20 @@
|
|||
<title>ReDoc</title>
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<style>
|
||||
@import url(//fonts.googleapis.com/css?family=Roboto:300,400,700);
|
||||
@import url(//fonts.googleapis.com/css?family=Montserrat:400,700);
|
||||
</style>
|
||||
<script src="https://cdn.vaadin.com/vaadin-core-elements/latest/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<link rel="import" href="https://cdn.vaadin.com/vaadin-core-elements/master/vaadin-combo-box/vaadin-combo-box-light.html">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<header> ReDoc </header>
|
||||
<form id="schema-url-form">
|
||||
<input id="schema-url-input" value='http://rebilly.github.io/SwaggerTemplateRepo/swagger.yaml'>
|
||||
<template is="dom-bind" id="specs">
|
||||
<form id="schema-url-form" is="iron-form">
|
||||
<vaadin-combo-box-light id="spec-input" items="[[specs]]" attr-for-value="value" allow-custom-value>
|
||||
<input placeholder="URL to a spec to try" id="schema-url-input" type="text" is="iron-input" value="https://rebilly.github.io/RebillyAPI/swagger.json">
|
||||
</vaadin-combo-box-light>
|
||||
<button type="submit"> Explore </button>
|
||||
</form>
|
||||
</template>
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=Rebilly&repo=ReDoc&type=star&count=true&size=large"
|
||||
frameborder="0" scrolling="0" width="130px" height="30px"></iframe>
|
||||
</nav>
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/*@import url(http://fonts.googleapis.com/css?family=Roboto:300,400,700);
|
||||
@import url(http://fonts.googleapis.com/css?family=Montserrat:300,400);*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding-top: 50px;
|
||||
|
@ -26,6 +23,7 @@ body {
|
|||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
font-family: Monserrat, sans-serif;
|
||||
}
|
||||
|
||||
nav input, nav button {
|
||||
|
@ -49,11 +47,11 @@ nav input {
|
|||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
max-width: 500px;
|
||||
padding: 0 10px;
|
||||
|
||||
color: #555;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
|
||||
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
||||
|
@ -80,7 +78,6 @@ nav button {
|
|||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
nav button:hover {
|
||||
|
|
39
demo/main.js
39
demo/main.js
|
@ -2,19 +2,12 @@
|
|||
'use strict';
|
||||
|
||||
var schemaUrlForm = document.getElementById('schema-url-form');
|
||||
var schemaUrlInput = document.getElementById('schema-url-input');
|
||||
schemaUrlForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value)
|
||||
return false;
|
||||
})
|
||||
var schemaUrlInput;
|
||||
|
||||
var url = window.location.search.match(/url=([^&]+)/);
|
||||
if (url && url.length > 1) {
|
||||
url = decodeURIComponent(url[1]);
|
||||
document.getElementsByTagName('redoc')[0].setAttribute('spec-url', url);
|
||||
schemaUrlInput.value = url;
|
||||
}
|
||||
|
||||
function updateQueryStringParameter(uri, key, value) {
|
||||
|
@ -31,5 +24,35 @@
|
|||
return uri + separator + key + "=" + value + hash;
|
||||
}
|
||||
}
|
||||
|
||||
var specs = document.querySelector('#specs');
|
||||
specs.addEventListener('dom-change', function() {
|
||||
schemaUrlForm = document.getElementById('schema-url-form');
|
||||
schemaUrlInput = document.getElementById('schema-url-input');
|
||||
|
||||
schemaUrlForm.addEventListener('submit', function(event) {
|
||||
console.log('test')
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value)
|
||||
return false;
|
||||
})
|
||||
|
||||
schemaUrlInput.addEventListener('mousedown', function(e) {
|
||||
e.stopPropagation();
|
||||
});
|
||||
schemaUrlInput.value = url;
|
||||
specs.specs = [
|
||||
'https://api.apis.guru/v2/specs/instagram.com/1.0.0/swagger.yaml',
|
||||
'https://api.apis.guru/v2/specs/googleapis.com/calendar/v3/swagger.yaml',
|
||||
'https://api.apis.guru/v2/specs/data2crm.com/1/swagger.yaml',
|
||||
'https://api.apis.guru/v2/specs/graphhopper.com/1.0/swagger.yaml'
|
||||
];
|
||||
var $specInput = document.getElementById('spec-input');
|
||||
$specInput.addEventListener('value-changed', function(e) {
|
||||
schemaUrlInput.value = e.detail.value;
|
||||
location.search = updateQueryStringParameter(location.search, 'url', schemaUrlInput.value);
|
||||
});
|
||||
});
|
||||
//window.redocDebugMode = true;
|
||||
})();
|
||||
|
|
BIN
demo/redoc-demo.png
Normal file
BIN
demo/redoc-demo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 198 KiB |
|
@ -1,10 +1,32 @@
|
|||
swagger: '2.0'
|
||||
schemes:
|
||||
- http
|
||||
- https
|
||||
host: petstore.swagger.io
|
||||
basePath: /v2
|
||||
info:
|
||||
description: 'This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). For this sample, you can use the api key `special-key` to test the authorization filters.'
|
||||
description: |
|
||||
This is a sample server Petstore server.
|
||||
You can find out more about Swagger at
|
||||
[http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/).
|
||||
For this sample, you can use the api key `special-key` to test the authorization filters.
|
||||
# Introduction
|
||||
This API is documented in **OpenAPI format** and is based on
|
||||
[Pestore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
||||
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
# OpenAPI Specification
|
||||
This API is documented in **OpenAPI format** and is based on
|
||||
[Pestore sample](http://petstore.swagger.io/) provided by [swagger.io](http://swagger.io) team.
|
||||
It was **extended** to illustrate features of [generator-openapi-repo](https://github.com/Rebilly/generator-openapi-repo)
|
||||
tool and [ReDoc](https://github.com/Rebilly/ReDoc) documentation. In addition to standard
|
||||
OpenAPI syntax we use a few [vendor extensions](https://github.com/Rebilly/ReDoc/blob/master/docs/redoc-vendor-extensions.md).
|
||||
# Cross-Origin Resource Sharing
|
||||
This API features Cross-Origin Resource Sharing (CORS) implemented in compliance with [W3C spec](https://www.w3.org/TR/cors/).
|
||||
And that allows cross-domain communication from the browser.
|
||||
All responses have a wildcard same-origin which makes them completely public and accessible to everyone, including any code on any site.
|
||||
|
||||
version: 1.0.0
|
||||
title: Swagger Petstore
|
||||
termsOfService: 'http://swagger.io/terms/'
|
||||
|
@ -16,47 +38,15 @@ info:
|
|||
name: Apache 2.0
|
||||
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
|
||||
externalDocs:
|
||||
description: Find out more about Swagger
|
||||
url: 'http://swagger.io'
|
||||
description: Find out how to create Github repo for your OpenAPI spec.
|
||||
url: 'https://github.com/Rebilly/generator-openapi-repo'
|
||||
tags:
|
||||
- name: Pagination
|
||||
x-traitTag: true
|
||||
description: |-
|
||||
Sometimes you just can't get enough. For this reason, we've provided a convenient way to access more data in any request for sequential data. Simply call the url in the next_url parameter and we'll respond with the next set of data.
|
||||
```json
|
||||
{
|
||||
...
|
||||
"pagination": {
|
||||
"next_url":
|
||||
"https://api.instagram.com/v1/tags/puppy/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&max_id=13872296",
|
||||
"next_max_id": "13872296"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
On views where pagination is present, we also support the `count`
|
||||
parameter. Simply set this to the number of items you'd like to receive. Note that the default values should be fine for most applications - but if you decide to increase this number there is a maximum value defined on each endpoint.
|
||||
externalDocs:
|
||||
description: Find out more
|
||||
url: 'http://swagger.io'
|
||||
- name: JSONP
|
||||
x-traitTag: true
|
||||
description: "If you're writing an AJAX application, and you'd like to wrap our response with a callback, all you have to do is specify a callback parameter with any API call:\n```\n https://api.instagram.com/v1/tags/coffee/media/recent?access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&callback=callbackFunction\n```\nWould respond with:\n```js\ncallbackFunction({\n ...\n});\n``` \n > Example of markdown blockquote"
|
||||
externalDocs:
|
||||
description: Find out more
|
||||
url: 'http://swagger.io'
|
||||
- name: pet
|
||||
description: Everything about your Pets
|
||||
externalDocs:
|
||||
description: Find out more
|
||||
url: 'http://swagger.io'
|
||||
- name: store
|
||||
description: Access to Petstore orders
|
||||
- name: user
|
||||
description: Operations about user
|
||||
externalDocs:
|
||||
description: Find out more about our store
|
||||
url: 'http://swagger.io'
|
||||
securityDefinitions:
|
||||
petstore_auth:
|
||||
type: oauth2
|
||||
|
@ -75,7 +65,7 @@ paths:
|
|||
tags:
|
||||
- pet
|
||||
summary: Add a new pet to the store
|
||||
description: ''
|
||||
description: Add new pet to the store inventory.
|
||||
operationId: addPet
|
||||
consumes:
|
||||
- application/json
|
||||
|
@ -154,7 +144,6 @@ paths:
|
|||
get:
|
||||
tags:
|
||||
- pet
|
||||
- JSONP
|
||||
summary: Find pet by ID
|
||||
description: Returns a single pet
|
||||
operationId: getPetById
|
||||
|
@ -242,40 +231,6 @@ paths:
|
|||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
'/pet/{petId}/uploadImage':
|
||||
get:
|
||||
tags:
|
||||
- pet
|
||||
summary: uploads an image
|
||||
description: ''
|
||||
operationId: uploadFile
|
||||
consumes:
|
||||
- image/jpeg
|
||||
- image/png
|
||||
produces:
|
||||
- image/jpeg
|
||||
- image/png
|
||||
parameters:
|
||||
- name: petId
|
||||
in: path
|
||||
description: ID of pet to update
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
- name: file
|
||||
in: body
|
||||
description: file to upload
|
||||
required: false
|
||||
schema:
|
||||
type: file
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
schema:
|
||||
type: file
|
||||
security:
|
||||
- petstore_auth:
|
||||
- 'write:pets'
|
||||
- 'read:pets'
|
||||
post:
|
||||
tags:
|
||||
- pet
|
||||
|
@ -316,8 +271,6 @@ paths:
|
|||
get:
|
||||
tags:
|
||||
- pet
|
||||
- Pagination
|
||||
- JSONP
|
||||
summary: Finds Pets by status
|
||||
description: Multiple status values can be provided with comma seperated strings
|
||||
operationId: findPetsByStatus
|
||||
|
@ -355,8 +308,6 @@ paths:
|
|||
get:
|
||||
tags:
|
||||
- pet
|
||||
- Pagination
|
||||
- JSONP
|
||||
summary: Finds Pets by tags
|
||||
description: 'Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.'
|
||||
operationId: findPetsByTags
|
||||
|
@ -389,7 +340,6 @@ paths:
|
|||
get:
|
||||
tags:
|
||||
- store
|
||||
- JSONP
|
||||
summary: Returns pet inventories by status
|
||||
description: Returns a map of status codes to quantities
|
||||
operationId: getInventory
|
||||
|
@ -434,7 +384,6 @@ paths:
|
|||
get:
|
||||
tags:
|
||||
- store
|
||||
- JSONP
|
||||
summary: Find purchase order by ID
|
||||
description: 'For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions'
|
||||
operationId: getOrderById
|
||||
|
@ -504,7 +453,6 @@ paths:
|
|||
get:
|
||||
tags:
|
||||
- user
|
||||
- JSONP
|
||||
summary: Get user by user name
|
||||
description: ''
|
||||
operationId: getUserByName
|
||||
|
@ -642,6 +590,8 @@ paths:
|
|||
description: successful operation
|
||||
schema:
|
||||
type: string
|
||||
examples:
|
||||
application/json: OK
|
||||
headers:
|
||||
X-Rate-Limit:
|
||||
type: integer
|
||||
|
@ -699,10 +649,13 @@ definitions:
|
|||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Category ID
|
||||
allOf:
|
||||
- $ref: '#/definitions/Id'
|
||||
name:
|
||||
description: Category name
|
||||
type: string
|
||||
minLength: 1
|
||||
xml:
|
||||
name: Category
|
||||
Dog:
|
||||
|
@ -714,24 +667,44 @@ definitions:
|
|||
packSize:
|
||||
type: integer
|
||||
format: int32
|
||||
description: the size of the pack the dog is from
|
||||
default: 0
|
||||
minimum: 0
|
||||
description: The size of the pack the dog is from
|
||||
default: 1
|
||||
minimum: 1
|
||||
required:
|
||||
- packSize
|
||||
HoneyBee:
|
||||
description: A representation of a honey bee
|
||||
allOf:
|
||||
- $ref: '#/definitions/Pet'
|
||||
- type: object
|
||||
properties:
|
||||
honeyPerDay:
|
||||
type: number
|
||||
description: Average amount of honey produced per day in ounces
|
||||
example: 3.14
|
||||
required:
|
||||
- honeyPerDay
|
||||
Id:
|
||||
type: integer
|
||||
format: int64
|
||||
Order:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Order ID
|
||||
allOf:
|
||||
- $ref: '#/definitions/Id'
|
||||
petId:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Pet ID
|
||||
allOf:
|
||||
- $ref: '#/definitions/Id'
|
||||
quantity:
|
||||
type: integer
|
||||
format: int32
|
||||
minimum: 1
|
||||
default: 1
|
||||
shipDate:
|
||||
description: Estimated ship date
|
||||
type: string
|
||||
format: date-time
|
||||
status:
|
||||
|
@ -742,6 +715,7 @@ definitions:
|
|||
- approved
|
||||
- delivered
|
||||
complete:
|
||||
description: Indicates whenever order was completed or not
|
||||
type: boolean
|
||||
default: false
|
||||
xml:
|
||||
|
@ -754,23 +728,31 @@ definitions:
|
|||
discriminator: petType
|
||||
properties:
|
||||
petType:
|
||||
description: Type of a pet
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Pet ID
|
||||
allOf:
|
||||
- $ref: '#/definitions/Id'
|
||||
category:
|
||||
$ref: '#/definitions/Category'
|
||||
description: Categories this pet belongs to
|
||||
allOf:
|
||||
- $ref: '#/definitions/Category'
|
||||
name:
|
||||
description: The name given to a pet
|
||||
type: string
|
||||
example: doggie
|
||||
example: Guru
|
||||
photoUrls:
|
||||
description: The list of URL to a cute photos featuring pet
|
||||
type: array
|
||||
xml:
|
||||
name: photoUrl
|
||||
wrapped: true
|
||||
items:
|
||||
type: string
|
||||
format: url
|
||||
tags:
|
||||
description: Tags attached to the pet
|
||||
type: array
|
||||
xml:
|
||||
name: tag
|
||||
|
@ -779,7 +761,7 @@ definitions:
|
|||
$ref: '#/definitions/Tag'
|
||||
status:
|
||||
type: string
|
||||
description: pet status in the store
|
||||
description: Pet status in the store
|
||||
enum:
|
||||
- available
|
||||
- pending
|
||||
|
@ -790,33 +772,56 @@ definitions:
|
|||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Tag ID
|
||||
allOf:
|
||||
- $ref: '#/definitions/Id'
|
||||
name:
|
||||
description: Tag name
|
||||
type: string
|
||||
minLength: 1
|
||||
xml:
|
||||
name: Tag
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: User ID
|
||||
$ref: '#/definitions/Id'
|
||||
username:
|
||||
description: User supplied username
|
||||
type: string
|
||||
minLength: 4
|
||||
example: John78
|
||||
firstName:
|
||||
description: User first name
|
||||
type: string
|
||||
minLength: 1
|
||||
example: John
|
||||
lastName:
|
||||
description: User last name
|
||||
type: string
|
||||
minLength: 1
|
||||
example: Smith
|
||||
email:
|
||||
description: User email address
|
||||
type: string
|
||||
format: email
|
||||
example: john.smith@example.com
|
||||
password:
|
||||
type: string
|
||||
description: 'User password, MUST contain a mix of upper and lower case letters, as well as digits'
|
||||
format: password
|
||||
minLength: 8
|
||||
pattern: '(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])'
|
||||
example: drowssaP123
|
||||
phone:
|
||||
description: User phone number in international format
|
||||
type: string
|
||||
pattern: "^\\+(?:[0-9]-?){6,14}[0-9]$"
|
||||
example: +1-202-555-0192
|
||||
userStatus:
|
||||
description: User status
|
||||
type: integer
|
||||
format: int32
|
||||
description: User Status
|
||||
xml:
|
||||
name: User
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
# ReDoc vendor extensions
|
||||
ReDoc makes use of the following [vendor extensions](http://swagger.io/specification/#vendorExtensions)
|
||||
|
||||
### <a name="infoObject"></a>[Info Object](http://swagger.io/specification/#infoObject) vendor extensions
|
||||
|
||||
#### <a name="x-logo"></a> x-logo
|
||||
### Info Object vendor extensions
|
||||
Extends OpenAPI [Info Object](http://swagger.io/specification/#infoObject)
|
||||
#### x-logo
|
||||
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :-----------: | :---------- |
|
||||
|
@ -47,9 +47,9 @@ info:
|
|||
|
||||
|
||||
|
||||
### [Tag object](http://swagger.io/specification/#tagObject) vendor extensions
|
||||
|
||||
#### <a name="x-traitTag"></a> x-traitTag
|
||||
### Tag Object vendor extensions
|
||||
Extends OpenAPI [Tag Object](http://swagger.io/specification/#tagObject)
|
||||
#### x-traitTag
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-traitTag | boolean | In Swagger two operations can have multiply tags. This property distinguish between tags that are used to group operations (default) from tags that are used to mark operation with certain trait (`true` value) |
|
||||
|
@ -74,9 +74,9 @@ description: Pagination description (can use markdown syntax)
|
|||
x-traitTag: true
|
||||
```
|
||||
|
||||
### [Operation Object](http://swagger.io/specification/#operationObject) vendor extensions
|
||||
|
||||
#### <a name="x-code-samples"></a> x-code-samples
|
||||
### Operation Object vendor extensions
|
||||
Extends OpenAPI [Operation Object](http://swagger.io/specification/#operationObject)
|
||||
#### x-code-samples
|
||||
| Field Name | Type | Description |
|
||||
| :------------- | :------: | :---------- |
|
||||
| x-code-samples | [ [Code Sample Object](#codeSampleObject) ] | A list of code samples associated with operation |
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<div>
|
||||
<h1 class="api-info-header">{{data.title}} ({{data.version}})</h1>
|
||||
<p *ngIf="data.description" [innerHtml]="data.description | marked"> </p>
|
||||
<p>
|
||||
<!-- TODO: create separate components for contact and license ? -->
|
||||
<span *ngIf="data.contact"> Contact:
|
||||
<a *ngIf="data.contact.url" href="{{data.contact.url}}">
|
||||
{{data.contact.name || data.contact.url}}</a>
|
||||
<a *ngIf="data.contact.email" href="mailto:{{data.contact.email}}">
|
||||
{{data.contact.email}}</a>
|
||||
</span>
|
||||
<span *ngIf="data.license"> License:
|
||||
<a *ngIf="data.license.url" href="{{data.license.url}}"> {{data.license.name}} </a>
|
||||
<span *ngIf="!data.license.url"> {{data.license.name}} </span>
|
||||
</span>
|
||||
</p>
|
||||
<h1 class="api-info-header">{{info.title}} ({{info.version}})</h1>
|
||||
<p>
|
||||
Download OpenAPI (fka Swagger) specification:
|
||||
<a class="openapi-button" target="_blank" attr.href='{{specUrl}}'> Download </a>
|
||||
</p>
|
||||
<p *ngIf="info.description" [innerHtml]="info['x-redoc-html-description'] | safe"> </p>
|
||||
<p>
|
||||
<!-- TODO: create separate components for contact and license ? -->
|
||||
<span *ngIf="info.contact"> Contact:
|
||||
<a *ngIf="info.contact.url" href="{{info.contact.url}}">
|
||||
{{info.contact.name || info.contact.url}}</a>
|
||||
<a *ngIf="info.contact.email" href="mailto:{{info.contact.email}}">
|
||||
{{info.contact.email}}</a>
|
||||
</span>
|
||||
<span *ngIf="info.license"> License:
|
||||
<a *ngIf="info.license.url" href="{{info.license.url}}"> {{info.license.name}} </a>
|
||||
<span *ngIf="!info.license.url"> {{info.license.name}} </span>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
:host > div {
|
||||
width: 60%;
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a.openapi-button {
|
||||
|
@ -15,3 +17,8 @@ a.openapi-button {
|
|||
margin-left: 0.5em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:host [section] {
|
||||
padding-top: 60px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { getChildDebugElement } from '../../../tests/helpers';
|
||||
import { Component } from '@angular/core';
|
||||
import { OptionsService } from '../../services/index';
|
||||
|
||||
import {
|
||||
inject,
|
||||
|
@ -17,8 +18,14 @@ describe('Redoc components', () => {
|
|||
let builder;
|
||||
let component;
|
||||
let fixture;
|
||||
let opts;
|
||||
|
||||
beforeEach(async(inject([TestComponentBuilder, SpecManager], (tcb, specMgr) => {
|
||||
beforeEach(async(inject([TestComponentBuilder, SpecManager, OptionsService], (tcb, specMgr, _opts) => {
|
||||
opts = _opts;
|
||||
opts.options = {
|
||||
scrollYOffset: () => 0,
|
||||
$scrollParent: window
|
||||
};
|
||||
builder = tcb;
|
||||
return specMgr.load('/tests/schemas/api-info-test.json');
|
||||
})));
|
||||
|
@ -32,8 +39,8 @@ describe('Redoc components', () => {
|
|||
|
||||
it('should init component data', () => {
|
||||
expect(component).not.toBeNull();
|
||||
expect(component.data).not.toBeNull();
|
||||
component.data.title.should.be.equal('Swagger Petstore');
|
||||
expect(component.info).not.toBeNull();
|
||||
component.info.title.should.be.equal('Swagger Petstore');
|
||||
});
|
||||
|
||||
it('should render api name and version', () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import { SpecManager, RedocComponent, BaseComponent } from '../base';
|
||||
import { OptionsService } from '../../services/index';
|
||||
import { OptionsService, MenuService } from '../../services/index';
|
||||
|
||||
@RedocComponent({
|
||||
selector: 'api-info',
|
||||
|
@ -9,17 +9,17 @@ import { OptionsService } from '../../services/index';
|
|||
templateUrl: './api-info.html'
|
||||
})
|
||||
export class ApiInfo extends BaseComponent {
|
||||
data: any;
|
||||
info: any;
|
||||
specUrl: String;
|
||||
constructor(specMgr:SpecManager, private optionsService:OptionsService) {
|
||||
constructor(specMgr:SpecManager, private optionsService:OptionsService, private menuServ: MenuService) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
this.data = this.componentSchema.info;
|
||||
init() {
|
||||
this.info = this.componentSchema.info;
|
||||
this.specUrl = this.optionsService.options.specUrl;
|
||||
if (parseInt(this.data.version.substring(0, 1)) !== NaN) {
|
||||
this.data.version = 'v' + this.data.version;
|
||||
if (parseInt(this.info.version.substring(0, 1)) !== NaN) {
|
||||
this.info.version = 'v' + this.info.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
expect(component).not.toBeNull();
|
||||
expect(component.data).not.toBeNull();
|
||||
expect(component.logo).not.toBeNull();
|
||||
});
|
||||
|
||||
it('should not display image when no x-logo', () => {
|
||||
component.data.should.be.empty();
|
||||
component.logo.should.be.empty();
|
||||
let nativeElement = getChildDebugElement(fixture.debugElement, 'api-logo').nativeElement;
|
||||
let imgElement = nativeElement.querySelector('img');
|
||||
expect(imgElement).toBeNull();
|
||||
|
@ -49,8 +49,8 @@ describe('Redoc components', () => {
|
|||
});
|
||||
|
||||
it('should load values from spec and use transparent bgColor by default', () => {
|
||||
component.data.imgUrl.should.endWith('petstore-logo.png');
|
||||
component.data.bgColor.should.be.equal('transparent');
|
||||
component.logo.imgUrl.should.endWith('petstore-logo.png');
|
||||
component.logo.bgColor.should.be.equal('transparent');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,16 +8,16 @@ import {RedocComponent, BaseComponent, SpecManager} from '../base';
|
|||
templateUrl: './api-logo.html'
|
||||
})
|
||||
export class ApiLogo extends BaseComponent {
|
||||
data:any = {};
|
||||
logo:any = {};
|
||||
|
||||
constructor(specMgr:SpecManager) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
init() {
|
||||
let logoInfo = this.componentSchema.info['x-logo'];
|
||||
if (!logoInfo) return;
|
||||
this.data.imgUrl = logoInfo.url;
|
||||
this.data.bgColor = logoInfo.backgroundColor || 'transparent';
|
||||
this.logo.imgUrl = logoInfo.url;
|
||||
this.logo.bgColor = logoInfo.backgroundColor || 'transparent';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,9 +23,11 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
|||
@Input() nestOdd: boolean;
|
||||
@Input() childFor: string;
|
||||
@Input() isArray: boolean;
|
||||
disableLazy: boolean = false;
|
||||
loaded: boolean = false;
|
||||
constructor(private specMgr:SpecManager, private location:ViewContainerRef, private elementRef:ElementRef,
|
||||
private resolver:ComponentResolver, private optionsService:OptionsService, private _renderer: Renderer) {
|
||||
this.disableLazy = this.optionsService.options.disableLazySchemas;
|
||||
}
|
||||
|
||||
normalizePointer() {
|
||||
|
@ -67,7 +69,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
|||
|
||||
// skip caching view with tabs inside (discriminator)
|
||||
// as it needs attached controller
|
||||
if (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas) {
|
||||
if (!this.disableLazy && (compRef.instance.hasDescendants || compRef.instance._hasSubSchemas)) {
|
||||
this._loadAfterSelf();
|
||||
return;
|
||||
}
|
||||
|
@ -85,11 +87,7 @@ export class JsonSchemaLazy implements OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (this.optionsService.options.disableLazySchemas) {
|
||||
this._loadAfterSelf();
|
||||
return;
|
||||
}
|
||||
if (!this.auto) return;
|
||||
if (!this.auto && !this.disableLazy) return;
|
||||
this.loadCached();
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ describe('Redoc components', () => {
|
|||
builder = tcb;
|
||||
specMgr = _spec;
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = builder.createSync(TestAppComponent);
|
||||
let debugEl = getChildDebugElement(fixture.debugElement, 'json-schema');
|
||||
|
@ -37,14 +38,14 @@ describe('Redoc components', () => {
|
|||
});
|
||||
|
||||
it('should set isTrivial for non-object/array types', () => {
|
||||
component.pointer = '';
|
||||
component.pointer = '#';
|
||||
(<any>specMgr)._schema = {type: 'string'};
|
||||
fixture.detectChanges();
|
||||
component.schema.isTrivial.should.be.true();
|
||||
});
|
||||
|
||||
it('should use < * > notation for prop without type', () => {
|
||||
component.pointer = '';
|
||||
component.pointer = '#';
|
||||
(<any>specMgr)._schema = {type: 'object', properties: {
|
||||
test: {}
|
||||
}};
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Zippy } from '../../shared/components/Zippy/zippy';
|
|||
detect: true
|
||||
})
|
||||
export class JsonSchema extends BaseComponent {
|
||||
schema: any;
|
||||
schema: any = {};
|
||||
activeDescendant:any = {};
|
||||
hasDescendants: boolean = false;
|
||||
_hasSubSchemas: boolean = false;
|
||||
|
@ -68,7 +68,8 @@ export class JsonSchema extends BaseComponent {
|
|||
this.selectDescendant(0);
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
init() {
|
||||
if (!this.pointer) return;
|
||||
if (this.nestOdd) {
|
||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'nestodd', 'true');
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<div class="method">
|
||||
<div class="method-content">
|
||||
<h2 class="method-header sharable-header">
|
||||
<a class="share-link" href="#{{data.methodAnchor}}"></a>{{data.summary}}
|
||||
<a class="share-link" href="#{{method.anchor}}"></a>{{method.summary}}
|
||||
</h2>
|
||||
<div class="method-tags" *ngIf="data.methodInfo.tags.length">
|
||||
<a *ngFor="let tag of data.methodInfo.tags" attr.href="#{{tag}}"> {{tag}} </a>
|
||||
<div class="method-tags" *ngIf="method.info.tags.length">
|
||||
<a *ngFor="let tag of method.info.tags" attr.href="#tag/{{tag}}"> {{tag}} </a>
|
||||
</div>
|
||||
<p *ngIf="data.methodInfo.description" class="method-description"
|
||||
[innerHtml]="data.methodInfo.description | marked">
|
||||
<p *ngIf="method.info.description" class="method-description"
|
||||
[innerHtml]="method.info.description | marked">
|
||||
</p>
|
||||
<params-list pointer="{{pointer}}/parameters"> </params-list>
|
||||
<responses-list pointer="{{pointer}}/responses"> </responses-list>
|
||||
|
@ -16,15 +16,15 @@
|
|||
<h5>Definition</h5>
|
||||
|
||||
<div class="method-endpoint">
|
||||
<h5 class="http-method" [ngClass]="data.httpMethod">{{data.httpMethod}}</h5>
|
||||
<h5 class="http-method" [ngClass]="method.httpMethod">{{method.httpMethod}}</h5>
|
||||
<span select-on-click><!--
|
||||
--><span class="api-url">{{data.apiUrl}}</span><span class="path">{{data.path}}</span><!--
|
||||
--><span class="api-url">{{method.apiUrl}}</span><span class="path">{{method.path}}</span><!--
|
||||
--></span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="data.bodyParam">
|
||||
<div *ngIf="method.bodyParam">
|
||||
<br>
|
||||
<request-samples [pointer]="pointer" [schemaPointer]="data.bodyParam._pointer">
|
||||
<request-samples [pointer]="pointer" [schemaPointer]="method.bodyParam._pointer">
|
||||
</request-samples>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@import '../../shared/styles/variables';
|
||||
@import '../../shared/styles/share-link';
|
||||
|
||||
|
||||
:host {
|
||||
padding-bottom: 100px;
|
||||
|
@ -11,6 +11,10 @@
|
|||
border-bottom: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
responses-list, params-list {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -34,14 +34,14 @@ describe('Redoc components', () => {
|
|||
});
|
||||
|
||||
it('should init basic component data', () => {
|
||||
component.data.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
|
||||
component.data.httpMethod.should.be.equal('put');
|
||||
component.data.path.should.be.equal('/user/{username}');
|
||||
component.method.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
|
||||
component.method.httpMethod.should.be.equal('put');
|
||||
component.method.path.should.be.equal('/user/{username}');
|
||||
});
|
||||
|
||||
|
||||
it('should main tag', () => {
|
||||
component.data.methodInfo.tags.should.be.empty();
|
||||
component.method.info.tags.should.be.empty();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,25 +20,25 @@ import { SchemaHelper } from '../../services/schema-helper.service';
|
|||
detect: true
|
||||
})
|
||||
export class Method extends BaseComponent {
|
||||
data:any;
|
||||
method:any;
|
||||
@Input() tag:string;
|
||||
constructor(specMgr:SpecManager) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
this.data = {};
|
||||
this.data.apiUrl = this.specMgr.apiUrl;
|
||||
this.data.httpMethod = JsonPointer.baseName(this.pointer);
|
||||
this.data.path = JsonPointer.baseName(this.pointer, 2);
|
||||
this.data.methodInfo = this.componentSchema;
|
||||
this.data.methodInfo.tags = this.filterMainTags(this.data.methodInfo.tags);
|
||||
this.data.bodyParam = this.findBodyParam();
|
||||
this.data.summary = SchemaHelper.methodSummary(this.componentSchema);
|
||||
init() {
|
||||
this.method = {};
|
||||
this.method.apiUrl = this.specMgr.apiUrl;
|
||||
this.method.httpMethod = JsonPointer.baseName(this.pointer);
|
||||
this.method.path = JsonPointer.baseName(this.pointer, 2);
|
||||
this.method.info = this.componentSchema;
|
||||
this.method.info.tags = this.filterMainTags(this.method.info.tags);
|
||||
this.method.bodyParam = this.findBodyParam();
|
||||
this.method.summary = SchemaHelper.methodSummary(this.componentSchema);
|
||||
if (this.componentSchema.operationId) {
|
||||
this.data.methodAnchor = 'operation/' + encodeURIComponent(this.componentSchema.operationId);
|
||||
this.method.anchor = 'operation/' + encodeURIComponent(this.componentSchema.operationId);
|
||||
} else {
|
||||
this.data.methodAnchor = 'tag/' + encodeURIComponent(this.tag + this.pointer);
|
||||
this.method.anchor = this.tag + encodeURIComponent(this.pointer);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<div class="methods">
|
||||
<div class="tag" *ngFor="let tag of data.tags;trackBy:trackByTagName">
|
||||
<div class="tag-info" [attr.tag]="tag.name" *ngIf="!tag.empty">
|
||||
<div class="tag" *ngFor="let tag of tags;trackBy:trackByTagName">
|
||||
<div class="tag-info" [attr.section]="tag.id" *ngIf="!tag.virtual">
|
||||
<h1 class="sharable-header"> <a class="share-link" href="#tag/{{tag.name | encodeURIComponent}}"></a>{{tag.name}} </h1>
|
||||
<p *ngIf="tag.description" [innerHtml]="tag.description | marked"> </p>
|
||||
</div>
|
||||
<method *ngFor="let method of tag.methods;trackBy:trackByPointer" [pointer]="method.pointer" [attr.pointer]="method.pointer"
|
||||
[attr.tag]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
|
||||
[attr.section]="method.tag" [tag]="method.tag" [attr.operation-id]="method.operationId"></method>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
@import '../../shared/styles/variables';
|
||||
@import '../../shared/styles/share-link';
|
||||
|
||||
.tag-info {
|
||||
padding: 40px;
|
||||
|
@ -21,6 +20,7 @@
|
|||
color: $headers-color;
|
||||
text-transform: capitalize;
|
||||
font-weight: normal;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.methods {
|
||||
|
|
|
@ -35,12 +35,12 @@ describe('Redoc components', () => {
|
|||
});
|
||||
|
||||
it('should get correct tags list', () => {
|
||||
expect(component.data.tags).not.toBeNull();
|
||||
component.data.tags.should.have.lengthOf(2);
|
||||
component.data.tags[0].name.should.be.equal('traitTag');
|
||||
component.data.tags[0].methods.should.be.empty();
|
||||
component.data.tags[1].name.should.be.equal('tag1');
|
||||
component.data.tags[1].methods.should.have.lengthOf(2);
|
||||
expect(component.tags).not.toBeNull();
|
||||
component.tags.should.have.lengthOf(2);
|
||||
component.tags[0].name.should.be.equal('traitTag');
|
||||
component.tags[0].methods.should.be.empty();
|
||||
component.tags[1].name.should.be.equal('tag1');
|
||||
component.tags[1].methods.should.have.lengthOf(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,26 +15,21 @@ import { SchemaHelper } from '../../services/index';
|
|||
detect: true
|
||||
})
|
||||
export class MethodsList extends BaseComponent {
|
||||
data:any;
|
||||
tags:Array<any> = [];
|
||||
constructor(specMgr:SpecManager) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
this.data = {};
|
||||
// follow SwaggerUI behavior for cases when one method has more than one tag:
|
||||
// duplicate methods
|
||||
|
||||
init() {
|
||||
let tags = SchemaHelper.buildMenuTree(this.specMgr.schema);
|
||||
tags.forEach(tagInfo => {
|
||||
this.tags = tags.filter(tagInfo => !tagInfo.virtual);
|
||||
this.tags.forEach(tagInfo => {
|
||||
// inject tag name into method info
|
||||
tagInfo.methods = tagInfo.methods || [];
|
||||
tagInfo.methods.forEach(method => {
|
||||
method.tag = tagInfo.name;
|
||||
method.tag = tagInfo.id;
|
||||
});
|
||||
});
|
||||
this.data.tags = tags;
|
||||
// TODO: check $ref field
|
||||
}
|
||||
|
||||
trackByPointer(idx, el) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<h5 class="param-list-header" *ngIf="data.params.length"> Parameters </h5>
|
||||
<template ngFor [ngForOf]="data.params" let-paramType="$implicit">
|
||||
<h5 class="param-list-header" *ngIf="params.length"> Parameters </h5>
|
||||
<template ngFor [ngForOf]="params" let-paramType="$implicit">
|
||||
<header class="paramType">
|
||||
{{paramType.place}} Parameters
|
||||
<span class="hint--top-right hint--large" [attr.data-hint]="paramType.placeHint">?</span>
|
||||
|
@ -14,6 +14,7 @@
|
|||
<div>
|
||||
<span class="param-type {{param.type}}" [ngClass]="{'with-hint': param._displayTypeHint}"
|
||||
title="{{param._displayTypeHint}}"> {{param._displayType}} {{param._displayFormat}}</span>
|
||||
<span class="param-range" *ngIf="param._range"> {{param._range}} </span>
|
||||
<span *ngIf="param.required" class="param-required">Required</span>
|
||||
<div class="default" *ngIf="param.default">Default: {{param.default | json}}</div>
|
||||
<div *ngIf="param.enum" class="param-enum">
|
||||
|
@ -26,13 +27,13 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<div *ngIf="data.bodyParam">
|
||||
<h5 class="param-list-header" *ngIf="data.bodyParam"> Request Body </h5>
|
||||
<div *ngIf="bodyParam">
|
||||
<h5 class="param-list-header" *ngIf="bodyParam"> Request Body </h5>
|
||||
|
||||
<div class="body-param-description" [innerHtml]="data.bodyParam.description | marked"></div>
|
||||
<div class="body-param-description" [innerHtml]="bodyParam.description | marked"></div>
|
||||
<div>
|
||||
<br>
|
||||
<json-schema-lazy [isRequestSchema]="true" [auto]="true" pointer="{{data.bodyParam.pointer}}/schema">
|
||||
<json-schema-lazy [isRequestSchema]="true" [auto]="true" pointer="{{bodyParam.pointer}}/schema">
|
||||
</json-schema-lazy>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -18,21 +18,23 @@ function safePush(obj, prop, item) {
|
|||
})
|
||||
export class ParamsList extends BaseComponent {
|
||||
|
||||
data:any;
|
||||
params: Array<any>;
|
||||
empty: boolean;
|
||||
bodyParam: any;
|
||||
|
||||
constructor(specMgr:SpecManager) {
|
||||
super(specMgr);
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
this.data = {};
|
||||
init() {
|
||||
this.params = [];
|
||||
let paramsList = this.specMgr.getMethodParams(this.pointer, true);
|
||||
|
||||
paramsList = paramsList.map(paramSchema => {
|
||||
let propPointer = paramSchema._pointer;
|
||||
if (paramSchema.in === 'body') return paramSchema;
|
||||
paramSchema._name = paramSchema.name;
|
||||
return SchemaHelper.preprocess(paramSchema,propPointer, this.pointer);
|
||||
return SchemaHelper.preprocess(paramSchema, propPointer, this.pointer);
|
||||
});
|
||||
|
||||
let paramsMap = this.orderParams(paramsList);
|
||||
|
@ -40,11 +42,11 @@ export class ParamsList extends BaseComponent {
|
|||
if (paramsMap.body && paramsMap.body.length) {
|
||||
let bodyParam = paramsMap.body[0];
|
||||
bodyParam.pointer = bodyParam._pointer;
|
||||
this.data.bodyParam = bodyParam;
|
||||
this.bodyParam = bodyParam;
|
||||
paramsMap.body = undefined;
|
||||
}
|
||||
|
||||
this.data.noParams = !(Object.keys(paramsMap).length || this.data.bodyParam);
|
||||
this.empty = !(Object.keys(paramsMap).length || this.bodyParam);
|
||||
|
||||
let paramsPlaces = ['path', 'query', 'formData', 'header', 'body'];
|
||||
let placeHint = {
|
||||
|
@ -64,7 +66,7 @@ export class ParamsList extends BaseComponent {
|
|||
params.push({place: place, placeHint: placeHint[place], params: paramsMap[place]});
|
||||
}
|
||||
});
|
||||
this.data.params = params;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
orderParams(params):any {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
@import url('//fonts.googleapis.com/css?family=Roboto:300,400,700');
|
||||
@import url('//fonts.googleapis.com/css?family=Montserrat:400,700');
|
||||
|
||||
redoc.loading {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
<side-menu> </side-menu>
|
||||
</div>
|
||||
<div id="api-content">
|
||||
<api-info> </api-info>
|
||||
<warnings></warnings>
|
||||
<api-info></api-info>
|
||||
<methods-list> </methods-list>
|
||||
<footer>
|
||||
<div class="powered-by-badge">
|
||||
|
|
|
@ -51,10 +51,6 @@ api-info, .side-bar {
|
|||
padding: 10px 0;
|
||||
}
|
||||
|
||||
api-info {
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
api-logo {
|
||||
display: block;
|
||||
text-align: center;
|
||||
|
@ -150,6 +146,8 @@ api-logo {
|
|||
color: #383838;
|
||||
}
|
||||
}
|
||||
|
||||
@import '../../shared/styles/share-link';
|
||||
}
|
||||
|
||||
footer {
|
||||
|
|
|
@ -12,10 +12,12 @@ import { ApiInfo } from '../ApiInfo/api-info';
|
|||
import { ApiLogo } from '../ApiLogo/api-logo';
|
||||
import { MethodsList } from '../MethodsList/methods-list';
|
||||
import { SideMenu } from '../SideMenu/side-menu';
|
||||
import { Warnings } from '../Warnings/warnings';
|
||||
|
||||
import { StickySidebar } from '../../shared/components/index';
|
||||
import {SpecManager} from '../../utils/SpecManager';
|
||||
import { OptionsService, RedocEventsService } from '../../services/index';
|
||||
import { OptionsService, RedocEventsService, MenuService,
|
||||
ScrollService, Hash, WarningsService } from '../../services/index';
|
||||
|
||||
var dom = new BrowserDomAdapter();
|
||||
var _modeLocked = false;
|
||||
|
@ -25,11 +27,15 @@ var _modeLocked = false;
|
|||
providers: [
|
||||
SpecManager,
|
||||
BrowserDomAdapter,
|
||||
RedocEventsService
|
||||
RedocEventsService,
|
||||
ScrollService,
|
||||
Hash,
|
||||
MenuService,
|
||||
WarningsService
|
||||
],
|
||||
templateUrl: './redoc.html',
|
||||
styleUrls: ['./redoc.css'],
|
||||
directives: [ ApiInfo, ApiLogo, MethodsList, SideMenu, StickySidebar ],
|
||||
directives: [ ApiInfo, ApiLogo, MethodsList, SideMenu, StickySidebar, Warnings ],
|
||||
detect: true,
|
||||
onPushOnly: false
|
||||
})
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<header *ngIf="data.schemaPointer || data.samples.length"> Request samples </header>
|
||||
<schema-sample *ngIf="!data.samples.length" [skipReadOnly]="true" [pointer]="data.schemaPointer"> </schema-sample>
|
||||
<tabs *ngIf="data.samples.length" [selected] = "selectedLang" (change)=changeLangNotify($event)>
|
||||
<header *ngIf="schemaPointer || samples.length"> Request samples </header>
|
||||
<schema-sample *ngIf="!samples.length" [skipReadOnly]="true" [pointer]="schemaPointer"> </schema-sample>
|
||||
<tabs *ngIf="samples.length" [selected] = "selectedLang" (change)=changeLangNotify($event)>
|
||||
<tab tabTitle="JSON">
|
||||
<schema-sample [pointer]="data.schemaPointer" [skipReadOnly]="true"> </schema-sample>
|
||||
<schema-sample [pointer]="schemaPointer" [skipReadOnly]="true"> </schema-sample>
|
||||
</tab>
|
||||
<tab *ngFor="let sample of data.samples" [tabTitle]="sample.lang">
|
||||
<tab *ngFor="let sample of samples" [tabTitle]="sample.lang">
|
||||
<div class="code-sample">
|
||||
<div class="action-buttons">
|
||||
<span copy-button [copyText]="sample.source" class="hint--top-left hint--inversed"><a>Copy</a></span>
|
||||
|
|
|
@ -24,7 +24,9 @@ import { CopyButton } from '../../shared/components/CopyButton/copy-button.direc
|
|||
export class RequestSamples extends BaseComponent {
|
||||
childTabs: Tabs;
|
||||
selectedLang: EventEmitter<any>;
|
||||
data: any;
|
||||
|
||||
samples: Array<any>;
|
||||
|
||||
@Input() schemaPointer:string;
|
||||
@ViewChildren(Tabs) childQuery:QueryList<Tabs>;
|
||||
constructor(specMgr:SpecManager, public events:RedocEventsService) {
|
||||
|
@ -38,9 +40,8 @@ export class RequestSamples extends BaseComponent {
|
|||
this.events.samplesLanguageChanged.next(lang);
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
this.data = {};
|
||||
this.data.schemaPointer = JsonPointer.join(this.schemaPointer, 'schema');
|
||||
this.data.samples = this.componentSchema['x-code-samples'] || [];
|
||||
init() {
|
||||
this.schemaPointer = JsonPointer.join(this.schemaPointer, 'schema');;
|
||||
this.samples = this.componentSchema['x-code-samples'] || [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<h2 class="responses-list-header" *ngIf="data.responses.length"> Responses </h2>
|
||||
<zippy *ngFor="let response of data.responses;trackBy:trackByCode" title="{{response.code}} {{response.description}}"
|
||||
<h2 class="responses-list-header" *ngIf="responses.length"> Responses </h2>
|
||||
<zippy *ngFor="let response of responses;trackBy:trackByCode" title="{{response.code}} {{response.description}}"
|
||||
[type]="response.type" [empty]="response.empty" (open)="lazySchema.load()">
|
||||
<div *ngIf="response.headers" class="response-headers">
|
||||
<header>
|
||||
|
@ -7,8 +7,13 @@
|
|||
</header>
|
||||
<div class="header" *ngFor="let header of response.headers">
|
||||
<div class="header-name"> {{header.name}} </div>
|
||||
<div class="header-type"> {{header.type}} </div>
|
||||
<div class="header-type {{header.type}}"> {{header._displayType}} {{header._displayFormat}}
|
||||
<span class="header-range" *ngIf="header._range"> {{header._range}} </span>
|
||||
</div>
|
||||
<div *ngIf="header.default" class="header-default"> Default: {{header.default}} </div>
|
||||
<div *ngIf="header.enum" class="header-enum">
|
||||
<span *ngFor="let enumItem of header.enum" class="enum-value {{enumItem.type}}"> {{enumItem.val | json}} </span>
|
||||
</div>
|
||||
<div class="header-description" [innerHtml]="header.description | marked"> </div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -38,3 +38,20 @@ header {
|
|||
.header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header-range {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-right: 6px;
|
||||
margin-left: 6px;
|
||||
border-radius: $border-radius;
|
||||
background-color: rgba($primary-color, .1);
|
||||
padding: 0 4px;
|
||||
color: rgba($primary-color, 0.7);
|
||||
}
|
||||
|
||||
.header-type.array:before {
|
||||
content: "Array of ";
|
||||
color: $black;
|
||||
font-weight: $base-font-weight;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { JsonSchemaLazy } from '../JsonSchema/json-schema-lazy';
|
|||
import { Zippy } from '../../shared/components/index';
|
||||
import { statusCodeType } from '../../utils/helpers';
|
||||
import { OptionsService } from '../../services/index';
|
||||
import { SchemaHelper } from '../../services/schema-helper.service';
|
||||
|
||||
function isNumeric(n) {
|
||||
return (!isNaN(parseFloat(n)) && isFinite(n));
|
||||
|
@ -20,16 +21,15 @@ function isNumeric(n) {
|
|||
detect: true
|
||||
})
|
||||
export class ResponsesList extends BaseComponent {
|
||||
data: any;
|
||||
responses: Array<any>;
|
||||
options: any;
|
||||
constructor(specMgr:SpecManager, optionsMgr:OptionsService) {
|
||||
super(specMgr);
|
||||
this.options = optionsMgr.options;
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
this.data = {};
|
||||
this.data.responses = [];
|
||||
init() {
|
||||
this.responses = [];
|
||||
|
||||
let responses = this.componentSchema;
|
||||
if (!responses) return;
|
||||
|
@ -53,14 +53,14 @@ export class ResponsesList extends BaseComponent {
|
|||
resp.headers = Object.keys(resp.headers).map((k) => {
|
||||
let respInfo = resp.headers[k];
|
||||
respInfo.name = k;
|
||||
return respInfo;
|
||||
return SchemaHelper.preprocess(respInfo, this.pointer, this.pointer);
|
||||
});
|
||||
resp.empty = false;
|
||||
}
|
||||
resp.extendable = resp.headers || resp.length;
|
||||
return resp;
|
||||
});
|
||||
this.data.responses = responses;
|
||||
this.responses = responses;
|
||||
}
|
||||
|
||||
trackByCode(idx, el) {
|
||||
|
|
|
@ -29,7 +29,7 @@ export class ResponsesSamples extends BaseComponent {
|
|||
super(specMgr);
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
init() {
|
||||
this.data = {};
|
||||
this.data.responses = [];
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<div class="snippet">
|
||||
<!-- in case sample is not available for some reason -->
|
||||
<pre *ngIf="data.sample == undefined"> Sample unavailable </pre>
|
||||
<pre *ngIf="sample == undefined"> Sample unavailable </pre>
|
||||
<div class="action-buttons">
|
||||
<span> <a *ngIf="enableButtons" (click)="collapseAll()">Collapse all</a> </span>
|
||||
<span> <a *ngIf="enableButtons" (click)="expandAll()">Expand all</a> </span>
|
||||
<span copy-button [copyText]="data.sample | json" class="hint--top hint--inversed"> <a>Copy</a> </span>
|
||||
<span copy-button [copyText]="sample | json" class="hint--top hint--inversed"> <a>Copy</a> </span>
|
||||
</div>
|
||||
<pre [innerHtml]="data.sample | jsonFormatter"></pre>
|
||||
<pre [innerHtml]="sample | jsonFormatter"></pre>
|
||||
</div>
|
||||
|
|
|
@ -19,7 +19,7 @@ import { CopyButton } from '../../shared/components/CopyButton/copy-button.direc
|
|||
})
|
||||
export class SchemaSample extends BaseComponent {
|
||||
element: any;
|
||||
data: any;
|
||||
sample: any;
|
||||
enableButtons: boolean = false;
|
||||
@Input() skipReadOnly:boolean;
|
||||
|
||||
|
@ -33,7 +33,6 @@ export class SchemaSample extends BaseComponent {
|
|||
|
||||
init() {
|
||||
this.bindEvents();
|
||||
this.data = {};
|
||||
|
||||
let base:any = {};
|
||||
let sample;
|
||||
|
@ -78,12 +77,12 @@ export class SchemaSample extends BaseComponent {
|
|||
}
|
||||
}
|
||||
this.cache(sample);
|
||||
this.data.sample = sample;
|
||||
this.sample = sample;
|
||||
this.initButtons();
|
||||
}
|
||||
|
||||
initButtons() {
|
||||
if (typeof this.data.sample === 'object') {
|
||||
if (typeof this.sample === 'object') {
|
||||
this.enableButtons = true;
|
||||
}
|
||||
}
|
||||
|
@ -98,10 +97,10 @@ export class SchemaSample extends BaseComponent {
|
|||
|
||||
fromCache() {
|
||||
if (this.skipReadOnly && this.componentSchema['x-redoc-ro-sample']) {
|
||||
this.data.sample = this.componentSchema['x-redoc-ro-sample'];
|
||||
this.sample = this.componentSchema['x-redoc-ro-sample'];
|
||||
return true;
|
||||
} else if (this.componentSchema['x-redoc-rw-sample']) {
|
||||
this.data.sample = this.componentSchema['x-redoc-rw-sample'];
|
||||
this.sample = this.componentSchema['x-redoc-rw-sample'];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -7,12 +7,12 @@
|
|||
</div>
|
||||
<div #desktop id="resources-nav">
|
||||
<h5 class="menu-header"> API reference </h5>
|
||||
<div *ngFor="let cat of data.menu; let idx = index" class="menu-cat">
|
||||
<div *ngFor="let cat of categories; let idx = index" class="menu-cat">
|
||||
|
||||
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [hidden]="cat.empty"
|
||||
<label class="menu-cat-header" (click)="activateAndScroll(idx, -1)" [hidden]="cat.headless"
|
||||
[ngClass]="{active: cat.active}"> {{cat.name}}</label>
|
||||
<ul class="menu-subitems" @itemAnimation="cat.active ? 'expanded' : 'collapsed'">
|
||||
<li *ngFor="let method of cat.methods; let methIdx = index"
|
||||
<li *ngFor="let method of cat.methods; trackBy:summary; let methIdx = index"
|
||||
[ngClass]="{active: method.active}"
|
||||
(click)="activateAndScroll(idx, methIdx)">
|
||||
{{method.summary}}
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('Redoc components', () => {
|
|||
testOptions = opts;
|
||||
testOptions.options = {
|
||||
scrollYOffset: () => 0,
|
||||
scrollParent: window
|
||||
$scrollParent: window
|
||||
};
|
||||
return specMgr.load('/tests/schemas/extended-petstore.yml');
|
||||
})));
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
'use strict';
|
||||
|
||||
import { ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||
|
||||
import { BrowserDomAdapter } from '@angular/platform-browser/src/browser/browser_adapter';
|
||||
import { global } from '@angular/core/src/facade/lang';
|
||||
import { trigger, state, animate, transition, style } from '@angular/core';
|
||||
import { RedocComponent, BaseComponent, SpecManager } from '../base';
|
||||
import { ScrollService, Hash, MenuService, OptionsService } from '../../services/index';
|
||||
|
||||
import { MenuCategory } from '../../services/schema-helper.service';
|
||||
|
||||
@RedocComponent({
|
||||
selector: 'side-menu',
|
||||
templateUrl: './side-menu.html',
|
||||
providers: [ScrollService, MenuService, Hash],
|
||||
styleUrls: ['./side-menu.css'],
|
||||
detect: true,
|
||||
onPushOnly: false,
|
||||
|
@ -27,14 +29,16 @@ import { ScrollService, Hash, MenuService, OptionsService } from '../../services
|
|||
],
|
||||
})
|
||||
export class SideMenu extends BaseComponent {
|
||||
$element: any;
|
||||
$mobileNav: any;
|
||||
$resourcesNav: any;
|
||||
$scrollParent: any;
|
||||
activeCatCaption: string;
|
||||
activeItemCaption: string;
|
||||
options: any;
|
||||
data: any;
|
||||
categories: Array<MenuCategory>;
|
||||
|
||||
private options: any;
|
||||
private $element: any;
|
||||
private $mobileNav: any;
|
||||
private $resourcesNav: any;
|
||||
private $scrollParent: any;
|
||||
|
||||
constructor(specMgr:SpecManager, elementRef:ElementRef, private dom:BrowserDomAdapter,
|
||||
private scrollService:ScrollService, private menuService:MenuService, private hash:Hash,
|
||||
optionsService:OptionsService, private detectorRef:ChangeDetectorRef) {
|
||||
|
@ -66,6 +70,8 @@ export class SideMenu extends BaseComponent {
|
|||
}
|
||||
|
||||
init() {
|
||||
this.categories = this.menuService.categories;
|
||||
|
||||
this.$mobileNav = this.dom.querySelector(this.$element, '.mobile-nav');
|
||||
this.$resourcesNav = this.dom.querySelector(this.$element, '#resources-nav');
|
||||
|
||||
|
@ -76,11 +82,6 @@ export class SideMenu extends BaseComponent {
|
|||
};
|
||||
}
|
||||
|
||||
prepareModel() {
|
||||
this.data = {};
|
||||
this.data.menu = this.menuService.categories;
|
||||
}
|
||||
|
||||
mobileMode() {
|
||||
return this.$mobileNav.clientHeight > 0;
|
||||
}
|
||||
|
@ -88,7 +89,7 @@ export class SideMenu extends BaseComponent {
|
|||
toggleMobileNav() {
|
||||
let dom = this.dom;
|
||||
let $overflowParent = (this.options.$scrollParent === global) ? dom.defaultDoc().body
|
||||
: this.$scrollParent.$scrollParent;
|
||||
: this.$scrollParent;
|
||||
if (dom.hasStyle(this.$resourcesNav, 'height')) {
|
||||
dom.removeStyle(this.$resourcesNav, 'height');
|
||||
dom.removeStyle($overflowParent, 'overflow-y');
|
||||
|
|
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);
|
||||
});
|
||||
|
||||
it('should call prepareModel and init virtual methods after init', () => {
|
||||
spyOn(component, 'prepareModel');
|
||||
it('should call init virtual methods after init', () => {
|
||||
spyOn(component, 'init');
|
||||
component.ngOnInit();
|
||||
|
||||
component.prepareModel.calls.count().should.be.equal(1);
|
||||
component.init.calls.count().should.be.equal(1);
|
||||
component.prepareModel.and.callThrough();
|
||||
component.init.and.callThrough();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Component, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core';
|
||||
import { CORE_DIRECTIVES, JsonPipe, AsyncPipe } from '@angular/common';
|
||||
import { SpecManager } from '../utils/SpecManager';
|
||||
import { MarkedPipe, JsonPointerEscapePipe } from '../utils/pipes';
|
||||
import { MarkedPipe, JsonPointerEscapePipe, SafePipe } from '../utils/pipes';
|
||||
|
||||
export { SpecManager };
|
||||
|
||||
|
@ -50,7 +50,7 @@ function snapshot(obj) {
|
|||
export function RedocComponent(options) {
|
||||
let inputs = safeConcat(options.inputs, commonInputs);
|
||||
let directives = safeConcat(options.directives, CORE_DIRECTIVES);
|
||||
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe, AsyncPipe]);
|
||||
let pipes = safeConcat(options.pipes, [JsonPointerEscapePipe, MarkedPipe, JsonPipe, AsyncPipe, SafePipe]);
|
||||
if (options.onPushOnly === undefined) options.onPushOnly = true;
|
||||
|
||||
return function decorator(target) {
|
||||
|
@ -79,7 +79,7 @@ export function RedocComponent(options) {
|
|||
*/
|
||||
export class BaseComponent implements OnInit, OnDestroy {
|
||||
componentSchema: any = null;
|
||||
pointer: String;
|
||||
pointer: string;
|
||||
dereferencedCache = {};
|
||||
|
||||
constructor(public specMgr: SpecManager) {
|
||||
|
@ -90,7 +90,6 @@ export class BaseComponent implements OnInit, OnDestroy {
|
|||
*/
|
||||
ngOnInit() {
|
||||
this.componentSchema = this.specMgr.byPointer(this.pointer || '');
|
||||
this.prepareModel();
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
@ -99,15 +98,7 @@ export class BaseComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Used to prepare model based on component schema
|
||||
* @abstract
|
||||
*/
|
||||
prepareModel():any {
|
||||
// emtpy
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to initialize component. Run after prepareModel
|
||||
* Used to initialize component
|
||||
* @abstract
|
||||
*/
|
||||
init() {
|
||||
|
|
|
@ -7,3 +7,4 @@ export * from './scroll.service';
|
|||
export * from './hash.service';
|
||||
export * from './schema-normalizer.service';
|
||||
export * from './schema-helper.service';
|
||||
export * from './warnings.service';
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Injectable, EventEmitter } from '@angular/core';
|
|||
import { ScrollService, INVIEW_POSITION } from './scroll.service';
|
||||
import { Hash } from './hash.service';
|
||||
import { SpecManager } from '../utils/SpecManager';
|
||||
import { SchemaHelper } from './schema-helper.service';
|
||||
import { SchemaHelper, MenuCategory } from './schema-helper.service';
|
||||
|
||||
const CHANGE = {
|
||||
NEXT : 1,
|
||||
|
@ -14,7 +14,7 @@ const CHANGE = {
|
|||
@Injectable()
|
||||
export class MenuService {
|
||||
changed: EventEmitter<any> = new EventEmitter();
|
||||
categories: any;
|
||||
categories: Array<MenuCategory>;
|
||||
|
||||
activeCatIdx: number = 0;
|
||||
activeMethodIdx: number = -1;
|
||||
|
@ -55,11 +55,11 @@ export class MenuService {
|
|||
|
||||
getCurrentMethodEl() {
|
||||
return this.getMethodElByPtr(this.activeMethodPtr,
|
||||
this.categories[this.activeCatIdx].name);
|
||||
this.categories[this.activeCatIdx].id);
|
||||
}
|
||||
|
||||
getMethodElByPtr(ptr, tag) {
|
||||
let selector = ptr ? `[pointer="${ptr}"][tag="${tag}"]` : `[tag="${tag}"]`;
|
||||
getMethodElByPtr(ptr, section) {
|
||||
let selector = ptr ? `[pointer="${ptr}"][section="${section}"]` : `[section="${section}"]`;
|
||||
return document.querySelector(selector);
|
||||
}
|
||||
|
||||
|
@ -95,6 +95,7 @@ export class MenuService {
|
|||
_calcActiveIndexes(offset) {
|
||||
let menu = this.categories;
|
||||
let catCount = menu.length;
|
||||
if (!catCount) return [0, -1];
|
||||
let catLength = menu[this.activeCatIdx].methods.length;
|
||||
|
||||
let resMethodIdx = this.activeMethodIdx + offset;
|
||||
|
@ -140,10 +141,11 @@ export class MenuService {
|
|||
let ptr = decodeURIComponent(hash.substr(namespace.length + 1));
|
||||
if (namespace === 'operation') {
|
||||
$el = this.getMethodElByOperId(ptr);
|
||||
} else if (namespace === 'tag') {
|
||||
let tag = ptr.split('/')[0];
|
||||
ptr = ptr.substr(tag.length);
|
||||
$el = this.getMethodElByPtr(ptr, tag);
|
||||
} else {
|
||||
let sectionId = ptr.split('/')[0];
|
||||
ptr = ptr.substr(sectionId.length) || null;
|
||||
sectionId = namespace + (sectionId ? '/' + sectionId : '');
|
||||
$el = this.getMethodElByPtr(ptr, sectionId);
|
||||
}
|
||||
|
||||
if ($el) this.scrollService.scrollTo($el);
|
||||
|
|
|
@ -9,7 +9,7 @@ const defaults = {
|
|||
debugMode: false//global && global.redocDebugMode
|
||||
};
|
||||
|
||||
const OPTION_NAMES = new Set(['scrollYOffset', 'disableLazySchemas', 'specUrl']);
|
||||
const OPTION_NAMES = new Set(['scrollYOffset', 'disableLazySchemas', 'specUrl', 'suppressWarnings']);
|
||||
|
||||
@Injectable()
|
||||
export class OptionsService {
|
||||
|
@ -70,5 +70,6 @@ export class OptionsService {
|
|||
}
|
||||
|
||||
if (isString(this._options.disableLazySchemas)) this._options.disableLazySchemas = true;
|
||||
if (isString(this._options.suppressWarnings)) this._options.suppressWarnings = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,9 +59,13 @@ describe('Spec Helper', () => {
|
|||
info.methods[0].summary.should.be.equal('test post');
|
||||
});
|
||||
|
||||
it('should map x-traitTag to empty section', () => {
|
||||
let info = menuTree[0];
|
||||
info.empty.should.be.true();
|
||||
});
|
||||
|
||||
it('should map x-traitTag to empty methods list', () => {
|
||||
let info = menuTree[0];
|
||||
info['x-traitTag'].should.be.true();
|
||||
info.methods.should.be.empty();
|
||||
});
|
||||
|
||||
|
@ -81,4 +85,19 @@ describe('Spec Helper', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectors', () => {
|
||||
it('should autodetect type if not-specified', () => {
|
||||
spyOn(console, 'warn').and.stub();
|
||||
let schema = {
|
||||
type: undefined,
|
||||
properties: {}
|
||||
};
|
||||
|
||||
SchemaHelper.runInjectors(schema, schema, '#/');
|
||||
schema.type.should.be.equal('object');
|
||||
expect(console.warn).toHaveBeenCalled();
|
||||
(<jasmine.Spy>console.warn).and.callThrough();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +1,44 @@
|
|||
'use strict';
|
||||
import { JsonPointer } from '../utils/JsonPointer';
|
||||
import { SpecManager } from '../utils/SpecManager';
|
||||
import {methods as swaggerMethods} from '../utils/swagger-defs';
|
||||
import {methods as swaggerMethods, keywordTypes} from '../utils/swagger-defs';
|
||||
import { WarningsService } from './warnings.service';
|
||||
import slugify from 'slugify';
|
||||
|
||||
interface PropertyPreprocessOptions {
|
||||
childFor: string;
|
||||
skipReadOnly?: boolean;
|
||||
}
|
||||
|
||||
export interface MenuMethod {
|
||||
active: boolean;
|
||||
summary: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
export interface MenuCategory {
|
||||
name: string;
|
||||
id: string;
|
||||
|
||||
active?: boolean;
|
||||
methods?: Array<MenuMethod>;
|
||||
description?: string;
|
||||
empty?: string;
|
||||
virtual?: boolean;
|
||||
}
|
||||
|
||||
const injectors = {
|
||||
notype: {
|
||||
check: (propertySchema) => !propertySchema.type,
|
||||
inject: (injectTo, propertySchema, pointer) => {
|
||||
injectTo.type = SchemaHelper.detectType(propertySchema);
|
||||
propertySchema.type = injectTo.type;
|
||||
if (injectTo.type) {
|
||||
let message = `No "type" specified at "${pointer}". Automatically detected: "${injectTo.type}"`;
|
||||
WarningsService.warn(message);
|
||||
}
|
||||
}
|
||||
},
|
||||
general: {
|
||||
check: () => true,
|
||||
inject: (injectTo, propertySchema, pointer) => {
|
||||
|
@ -225,17 +255,36 @@ export class SchemaHelper {
|
|||
(method.description && method.description.substring(0, 50)) || '<no description>';
|
||||
}
|
||||
|
||||
static buildMenuTree(schema) {
|
||||
static detectType(schema) {
|
||||
let keywords = Object.keys(keywordTypes);
|
||||
for (var i=0; i < keywords.length; i++) {
|
||||
let keyword = keywords[i];
|
||||
let type = keywordTypes[keyword];
|
||||
if (schema[keyword]) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static buildMenuTree(schema):Array<MenuCategory> {
|
||||
let tag2MethodMapping = {};
|
||||
|
||||
let definedTags = schema.tags || [];
|
||||
// add tags into map to preserve order
|
||||
for (let tag of definedTags) {
|
||||
tag2MethodMapping[tag.name] = {
|
||||
'description': tag.description,
|
||||
'name': tag.name,
|
||||
'x-traitTag': tag['x-traitTag'],
|
||||
'methods': []
|
||||
for (let header of (<Array<string>>(schema.info && schema.info['x-redoc-markdown-headers'] || []))) {
|
||||
let id = 'section/' + slugify(header);
|
||||
tag2MethodMapping[id] = {
|
||||
name: header, id: id, virtual: true, methods: []
|
||||
};
|
||||
}
|
||||
|
||||
for (let tag of schema.tags || []) {
|
||||
let id = 'tag/' + slugify(tag.name);
|
||||
tag2MethodMapping[id] = {
|
||||
name: tag.name,
|
||||
id: id,
|
||||
description: tag.description,
|
||||
headless: tag.name === '',
|
||||
empty: !!tag['x-traitTag'],
|
||||
methods: [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -252,15 +301,17 @@ export class SchemaHelper {
|
|||
let methodPointer = JsonPointer.compile(['paths', path, method]);
|
||||
let methodSummary = SchemaHelper.methodSummary(methodInfo);
|
||||
for (let tag of tags) {
|
||||
let tagDetails = tag2MethodMapping[tag];
|
||||
if (!tag2MethodMapping[tag]) {
|
||||
let id = 'tag/' + slugify(tag);
|
||||
let tagDetails = tag2MethodMapping[id];
|
||||
if (!tagDetails) {
|
||||
tagDetails = {
|
||||
name: tag,
|
||||
empty: tag === ''
|
||||
id: id,
|
||||
headless: tag === ''
|
||||
};
|
||||
tag2MethodMapping[tag] = tagDetails;
|
||||
tag2MethodMapping[id] = tagDetails;
|
||||
}
|
||||
if (tagDetails['x-traitTag']) continue;
|
||||
if (tagDetails.empty) continue;
|
||||
if (!tagDetails.methods) tagDetails.methods = [];
|
||||
tagDetails.methods.push({
|
||||
pointer: methodPointer,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Injectable } from '@angular/core';
|
|||
import { SpecManager } from '../utils/SpecManager';
|
||||
import { JsonPointer } from '../utils/JsonPointer';
|
||||
import { defaults } from '../utils/helpers';
|
||||
import { WarningsService } from './warnings.service';
|
||||
|
||||
interface Reference {
|
||||
$ref: string;
|
||||
|
@ -29,6 +30,7 @@ export class SchemaNormalizer {
|
|||
let resolved = this._dereferencer.dereference(subSchema, ptr);
|
||||
if (resolved.allOf) {
|
||||
resolved._pointer = resolved._pointer || ptr;
|
||||
resolved = Object.assign({}, resolved);
|
||||
AllOfMerger.merge(resolved, resolved.allOf, {omitParent: opts.omitParent});
|
||||
}
|
||||
return resolved;
|
||||
|
@ -127,20 +129,22 @@ class AllOfMerger {
|
|||
private static checkCanMerge(subSchema, into) {
|
||||
// TODO: add support for merge array schemas
|
||||
if (typeof subSchema !== 'object') {
|
||||
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found
|
||||
${subSchema}`;
|
||||
let errMessage = `Items of allOf should be Object: ${typeof subSchema} found ` +
|
||||
`${subSchema} at "#${into._pointer}"`;
|
||||
throw new Error(errMessage);
|
||||
}
|
||||
|
||||
if (into.type && subSchema.type && into.type !== subSchema.type) {
|
||||
let errMessage = `allOf merging error: schemas with different types can't be merged`;
|
||||
let errMessage = `allOf merging error: schemas with different types can't be merged: ` +
|
||||
`"${into.type}" and "${subSchema.type}" at "#${into._pointer}"`;
|
||||
throw new Error(errMessage);
|
||||
}
|
||||
|
||||
if (into.type === 'array') {
|
||||
console.warn('allOf: subschemas with type array are not supported yet');
|
||||
WarningsService.warn('allOf: subschemas with type "array" are not supported yet');
|
||||
}
|
||||
// TODO: add check if can be merged correctly (no different properties with the same name)
|
||||
// TODO: merge properties
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -191,8 +195,8 @@ class SchemaDereferencer {
|
|||
|
||||
let keysCount = Object.keys(schema).length;
|
||||
if ( keysCount > 2 || (keysCount === 2 && !schema.description) ) {
|
||||
console.warn(`other properties defined at the same level as $ref at '${pointer}'.
|
||||
They are IGNORRED according to JsonSchema spec`);
|
||||
WarningsService.warn(`Other properties are defined at the same level as $ref at "#${pointer}". ` +
|
||||
'They are IGNORRED according to the JsonSchema spec');
|
||||
resolved.description = resolved.description || schema.description;
|
||||
}
|
||||
|
||||
|
|
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;
|
||||
}
|
||||
|
||||
.sharable-header {
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
.sharable-header:hover .share-link:before, .share-link:hover:before {
|
||||
visibility: visible;
|
||||
}
|
||||
|
|
|
@ -7,8 +7,6 @@ $green : #00aa13;
|
|||
$yellow : #f1c400;
|
||||
$red : #e53935;
|
||||
|
||||
// Font
|
||||
// ---------------------------
|
||||
$em-size : 14px;
|
||||
|
||||
// Font weights
|
||||
|
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
import JsonSchemaRefParser from 'json-schema-ref-parser';
|
||||
import JsonPointer from './JsonPointer';
|
||||
import { renderMd, safePush } from './helpers';
|
||||
import slugify from 'slugify';
|
||||
import { parse as urlParse } from 'url';
|
||||
|
||||
export class SpecManager {
|
||||
public _schema:any = {};
|
||||
public _schema: any = {};
|
||||
public apiUrl: string;
|
||||
private _instance:any;
|
||||
private _instance: any;
|
||||
private _url: string;
|
||||
|
||||
static instance() {
|
||||
return new SpecManager();
|
||||
|
@ -26,6 +30,7 @@ export class SpecManager {
|
|||
|
||||
JsonSchemaRefParser.bundle(url, {http: {withCredentials: false}})
|
||||
.then(schema => {
|
||||
this._url = url;
|
||||
this._schema = schema;
|
||||
resolve(this._schema);
|
||||
this.init();
|
||||
|
@ -37,11 +42,36 @@ export class SpecManager {
|
|||
|
||||
/* calculate common used values */
|
||||
init() {
|
||||
if (!this._schema || !this._schema.schemes) return;
|
||||
this.apiUrl = this._schema.schemes[0] + '://' + this._schema.host + this._schema.basePath;
|
||||
let protocol;
|
||||
if (!this._schema.schemes || !this._schema.schemes.length) {
|
||||
protocol = this._url ? urlParse(this._url).protocol : 'http';
|
||||
} else {
|
||||
protocol = this._schema.schemes[0];
|
||||
if (protocol === 'http' && this._schema.schemes.indexOf('https') >= 0) {
|
||||
protocol = 'https';
|
||||
}
|
||||
}
|
||||
this.apiUrl = protocol + '://' + this._schema.host + this._schema.basePath;
|
||||
if (this.apiUrl.endsWith('/')) {
|
||||
this.apiUrl = this.apiUrl.substr(0, this.apiUrl.length - 1);
|
||||
}
|
||||
|
||||
this.preprocess();
|
||||
}
|
||||
|
||||
preprocess() {
|
||||
this._schema.info['x-redoc-html-description'] = renderMd( this._schema.info.description, {
|
||||
open: (tokens, idx) => {
|
||||
let content = tokens[idx + 1].content;
|
||||
safePush(this._schema.info, 'x-redoc-markdown-headers', content);
|
||||
content = slugify(content);
|
||||
return `<h${tokens[idx].hLevel} section="section/${content}">` +
|
||||
`<a class="share-link" href="#section/${content}"></a>`;
|
||||
},
|
||||
close: (tokens, idx) => {
|
||||
return `</h${tokens[idx].hLevel}>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get schema() {
|
||||
|
|
|
@ -1,4 +1,59 @@
|
|||
'use strict';
|
||||
import Remarkable from 'remarkable';
|
||||
declare var Prism: any;
|
||||
|
||||
const md = new Remarkable({
|
||||
html: true,
|
||||
linkify: true,
|
||||
breaks: false,
|
||||
typographer: false,
|
||||
highlight: (str, lang) => {
|
||||
if (lang === 'json') lang = 'js';
|
||||
let grammar = Prism.languages[lang];
|
||||
//fallback to clike
|
||||
if (!grammar) return str;
|
||||
return Prism.highlight(str, grammar);
|
||||
}
|
||||
});
|
||||
|
||||
interface HeadersHandler {
|
||||
open: Function;
|
||||
close: Function;
|
||||
}
|
||||
|
||||
export function renderMd(rawText:string, headersHandler?:HeadersHandler) {
|
||||
let _origRule;
|
||||
if (headersHandler) {
|
||||
_origRule = {
|
||||
open: md.renderer.rules.heading_open,
|
||||
close: md.renderer.rules.heading_close
|
||||
};
|
||||
md.renderer.rules.heading_open = (tokens, idx) => {
|
||||
if (tokens[idx].hLevel !== 1 ) {
|
||||
return _origRule.open(tokens, idx);
|
||||
} else {
|
||||
return headersHandler.open(tokens, idx);
|
||||
}
|
||||
};
|
||||
|
||||
md.renderer.rules.heading_close = (tokens, idx) => {
|
||||
if (tokens[idx].hLevel !== 1 ) {
|
||||
return _origRule.close(tokens, idx);
|
||||
} else {
|
||||
return headersHandler.close(tokens, idx);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let res = md.render(rawText);
|
||||
|
||||
if (headersHandler) {
|
||||
md.renderer.rules.heading_open = _origRule.open;
|
||||
md.renderer.rules.heading_close = _origRule.close;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export function statusCodeType(statusCode) {
|
||||
if (statusCode < 100 || statusCode > 599) {
|
||||
|
@ -29,3 +84,8 @@ export function defaults(target, src) {
|
|||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
export function safePush(obj, prop, val) {
|
||||
if (!obj[prop]) obj[prop] = [];
|
||||
obj[prop].push(val);
|
||||
}
|
||||
|
|
|
@ -5,24 +5,11 @@ import { DomSanitizationService } from '@angular/platform-browser';
|
|||
import { isString, stringify, isBlank } from '@angular/core/src/facade/lang';
|
||||
import { BaseException } from '@angular/core/src/facade/exceptions';
|
||||
import JsonPointer from './JsonPointer';
|
||||
import { renderMd } from './helpers';
|
||||
|
||||
declare var Prism: any;
|
||||
|
||||
import Remarkable from 'remarkable';
|
||||
|
||||
const md = new Remarkable({
|
||||
html: true,
|
||||
linkify: true,
|
||||
breaks: false,
|
||||
typographer: false,
|
||||
highlight: (str, lang) => {
|
||||
if (lang === 'json') lang = 'js';
|
||||
let grammar = Prism.languages[lang];
|
||||
//fallback to clike
|
||||
if (!grammar) return str;
|
||||
return Prism.highlight(str, grammar);
|
||||
}
|
||||
});
|
||||
|
||||
class InvalidPipeArgumentException extends BaseException {
|
||||
constructor(type, value) {
|
||||
|
@ -73,11 +60,24 @@ export class MarkedPipe implements PipeTransform {
|
|||
}
|
||||
|
||||
return this.sanitizer.bypassSecurityTrustHtml(
|
||||
`<span class="redoc-markdown-block">${md.render(value)}</span>`
|
||||
`<span class="redoc-markdown-block">${renderMd(value)}</span>`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({ name: 'safe' })
|
||||
export class SafePipe implements PipeTransform {
|
||||
constructor(private sanitizer: DomSanitizationService) {}
|
||||
transform(value) {
|
||||
if (isBlank(value)) return value;
|
||||
if (!isString(value)) {
|
||||
throw new InvalidPipeArgumentException(JsonPointerEscapePipe, value);
|
||||
}
|
||||
|
||||
return this.sanitizer.bypassSecurityTrustHtml(value);
|
||||
}
|
||||
}
|
||||
|
||||
const langMap = {
|
||||
'c++': 'cpp',
|
||||
'c#': 'csharp',
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
export var methods = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch']);
|
||||
export const methods = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch']);
|
||||
|
||||
export const keywordTypes = {
|
||||
multipleOf: 'number',
|
||||
maximum: 'number',
|
||||
exclusiveMaximum: 'number',
|
||||
minimum: 'number',
|
||||
exclusiveMinimum: 'number',
|
||||
|
||||
maxLength: 'string',
|
||||
minLength: 'string',
|
||||
pattern: 'string',
|
||||
|
||||
items: 'array',
|
||||
maxItems: 'array',
|
||||
minItems: 'array',
|
||||
uniqueItems: 'array',
|
||||
|
||||
maxProperties: 'object',
|
||||
minProperties: 'object',
|
||||
required: 'object',
|
||||
additionalProperties: 'object',
|
||||
properties: 'object'
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "redoc",
|
||||
"description": "Swagger-generated API Reference Documentation",
|
||||
"version": "0.16.1",
|
||||
"version": "1.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Rebilly/ReDoc"
|
||||
|
@ -48,7 +48,9 @@
|
|||
"remarkable": "npm:remarkable@^1.6.2",
|
||||
"rxjs": "npm:rxjs@5.0.0-beta.6",
|
||||
"scrollparent": "npm:scrollparent@^0.1.0",
|
||||
"slugify": "npm:slugify@^0.1.1",
|
||||
"stream-http": "npm:stream-http@^2.3.0",
|
||||
"url": "github:jspm/nodelibs-url@^0.1.0",
|
||||
"zone.js": "npm:zone.js@0.6.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -132,6 +134,7 @@
|
|||
"shelljs": "^0.7.0",
|
||||
"should": "^9.0.2",
|
||||
"sinon": "^1.17.2",
|
||||
"slugify": "^0.1.1",
|
||||
"systemjs-builder": "^0.15.16",
|
||||
"tslint": "^3.13.0",
|
||||
"tslint-stylish": "^2.1.0-beta",
|
||||
|
|
|
@ -38,8 +38,10 @@ System.config({
|
|||
"remarkable": "npm:remarkable@1.6.2",
|
||||
"rxjs": "npm:rxjs@5.0.0-beta.6",
|
||||
"scrollparent": "npm:scrollparent@0.1.0",
|
||||
"slugify": "npm:slugify@0.1.1",
|
||||
"stream-http": "npm:stream-http@2.3.0",
|
||||
"systemjs/plugin-json": "github:systemjs/plugin-json@0.1.2",
|
||||
"url": "github:jspm/nodelibs-url@0.1.0",
|
||||
"zone.js": "npm:zone.js@0.6.12",
|
||||
"github:jspm/nodelibs-assert@0.1.0": {
|
||||
"assert": "npm:assert@1.4.1"
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('Scroll sync', () => {
|
|||
});
|
||||
|
||||
it('should update active menu entries on page scroll forwards', () => {
|
||||
scrollToEl('[tag="store"]').then(() => {
|
||||
scrollToEl('[section="tag/store"]').then(() => {
|
||||
expect($('.menu-cat-header.active').getInnerHtml()).toContain('store');
|
||||
expect($('.selected-tag').getInnerHtml()).toContain('store');
|
||||
});
|
||||
|
|
|
@ -16,5 +16,16 @@
|
|||
"host": "petstore.swagger.io",
|
||||
"basePath": "/v2/",
|
||||
"schemes": ["http"],
|
||||
"paths": {}
|
||||
"paths": {
|
||||
"/pet": {
|
||||
"post": {
|
||||
"tags": ["tag1"],
|
||||
"summary": "tag1"
|
||||
},
|
||||
"put": {
|
||||
"tags": ["tag1", "traitTag"],
|
||||
"summary": "two tags",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,12 @@ describe('Utils', () => {
|
|||
specMgr.apiUrl.should.be.equal('http://petstore.swagger.io/v2');
|
||||
});
|
||||
|
||||
it('should correctly init api url if both http and https', ()=> {
|
||||
specMgr._schema.schemes.push('https');
|
||||
specMgr.init();
|
||||
specMgr.apiUrl.should.be.equal('https://petstore.swagger.io/v2');
|
||||
});
|
||||
|
||||
describe('byPointer method', () => {
|
||||
it('should return correct schema part', ()=> {
|
||||
let part = specMgr.byPointer('/tags/3');
|
||||
|
|
2590
typings/globals/node/index.d.ts
vendored
Normal file
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/json-pointer/index.d.ts" />
|
||||
/// <reference path="globals/node/index.d.ts" />
|
||||
/// <reference path="globals/should/index.d.ts" />
|
||||
|
|
4
typings/slugify.d.ts
vendored
Normal file
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